Newer
Older
static unsigned int poll_mailboxes;
/*! Polling frequency */
static unsigned int poll_freq;
/*! By default, poll every 30 seconds */
#define DEFAULT_POLL_FREQ 30
AST_MUTEX_DEFINE_STATIC(poll_lock);
static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
static pthread_t poll_thread = AST_PTHREADT_NULL;
static unsigned char poll_thread_run;
/*! Subscription to MWI event subscription changes */
static struct stasis_subscription *mwi_sub_sub;
/*!
* \brief An MWI subscription
*
* This is so we can keep track of which mailboxes are subscribed to.
* This way, we know which mailboxes to poll when the pollmailboxes
* option is being used.
*/
struct mwi_sub {
AST_RWLIST_ENTRY(mwi_sub) entry;
int old_urgent;
struct mwi_sub_task {
const char *mailbox;
const char *context;
};
static void mwi_sub_task_dtor(struct mwi_sub_task *mwist)
{
ast_free((void *) mwist->mailbox);
ast_free((void *) mwist->context);
ast_free((void *) mwist->uniqueid);
ast_free(mwist);
}
static struct ast_taskprocessor *mwi_subscription_tps;
static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
Mark Michelson
committed
/* custom audio control prompts for voicemail playback */
static char listen_control_forward_key[12];
static char listen_control_reverse_key[12];
static char listen_control_pause_key[12];
static char listen_control_restart_key[12];
static char listen_control_stop_key[12];
static char vm_login[80] = "vm-login";
static char vm_newuser[80] = "vm-newuser";
static char vm_password[80] = "vm-password";
static char vm_newpassword[80] = "vm-newpassword";
static char vm_passchanged[80] = "vm-passchanged";
static char vm_reenterpassword[80] = "vm-reenterpassword";
static char vm_mismatch[80] = "vm-mismatch";
static char vm_invalid_password[80] = "vm-invalid-password";
static char vm_pls_try_again[80] = "vm-pls-try-again";
/*
* XXX If we have the time, motivation, etc. to fix up this prompt, one of the following would be appropriate:
* 1. create a sound along the lines of "Please try again. When done, press the pound key" which could be spliced
* from existing sound clips. This would require some programming changes in the area of vm_forward options and also
* app.c's __ast_play_and_record function
* 2. create a sound prompt saying "Please try again. When done recording, press any key to stop and send the prepended
* message." At the time of this comment, I think this would require new voice work to be commissioned.
* 3. Something way different like providing instructions before a time out or a post-recording menu. This would require
* more effort than either of the other two.
*/
static char vm_prepend_timeout[80] = "vm-then-pound";
static struct ast_flags globalflags = {0};
static int saydurationminfo;
Joshua Colp
committed
static char dialcontext[AST_MAX_CONTEXT] = "";
static char callcontext[AST_MAX_CONTEXT] = "";
static char exitcontext[AST_MAX_CONTEXT] = "";
static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
static char *emailbody = NULL;
static char *emailsubject = NULL;
static char *pagerbody = NULL;
static char *pagersubject = NULL;
static char fromstring[100];
static char pagerfromstring[100];
static char charset[32] = "ISO-8859-1";
static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
Kevin P. Fleming
committed
static char emaildateformat[32] = "%A, %B %d, %Y at %r";
static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
/* Forward declarations - generic */
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id, int forwardintro);
static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id);
static void apply_options(struct ast_vm_user *vmu, const char *options);
static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
Mark Michelson
committed
static int is_valid_dtmf(const char *key);
Tilghman Lesher
committed
static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
static int write_password_to_file(const char *secretfn, const char *password);
static const char *substitute_escapes(const char *value);
static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
static void notify_new_state(struct ast_vm_user *vmu);
static int append_vmu_info_astman(struct mansession *s, struct ast_vm_user *vmu, const char* event_name, const char* actionid);
/*!
* Place a message in the indicated folder
*
* \param vmu Voicemail user
* \param vms Current voicemail state for the user
* \param msg The message number to save
* \param box The folder into which the message should be saved
* \param[out] newmsg The new message number of the saved message
* \param move Tells whether to copy or to move the message
*
* \note the "move" parameter is only honored for IMAP voicemail presently
* \retval 0 Success
*/
static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *mailbox, const char *context, const char *folder, int descending, enum ast_vm_snapshot_sort_val sort_val, int combine_INBOX_and_OLD);
static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
static int vm_msg_forward(const char *from_mailbox, const char *from_context, const char *from_folder, const char *to_mailbox, const char *to_context, const char *to_folder, size_t num_msgs, const char *msg_ids[], int delete_old);
static int vm_msg_move(const char *mailbox, const char *context, size_t num_msgs, const char *oldfolder, const char *old_msg_ids[], const char *newfolder);
static int vm_msg_remove(const char *mailbox, const char *context, size_t num_msgs, const char *folder, const char *msgs[]);
static int vm_msg_play(struct ast_channel *chan, const char *mailbox, const char *context, const char *folder, const char *msg_num, ast_vm_msg_play_cb cb);
#ifdef TEST_FRAMEWORK
static int vm_test_destroy_user(const char *context, const char *mailbox);
static int vm_test_create_user(const char *context, const char *mailbox);
#endif
Richard Mudgett
committed
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
/*!
* \internal
* \brief Parse the given mailbox_id into mailbox and context.
* \since 12.0.0
*
* \param mailbox_id The mailbox@context string to separate.
* \param mailbox Where the mailbox part will start.
* \param context Where the context part will start. ("default" if not present)
*
* \retval 0 on success.
* \retval -1 on error.
*/
static int separate_mailbox(char *mailbox_id, char **mailbox, char **context)
{
if (ast_strlen_zero(mailbox_id) || !mailbox || !context) {
return -1;
}
*context = mailbox_id;
*mailbox = strsep(context, "@");
if (ast_strlen_zero(*mailbox)) {
return -1;
}
if (ast_strlen_zero(*context)) {
*context = "default";
}
return 0;
}
struct ao2_container *inprocess_container;
struct inprocess {
int count;
char *context;
char mailbox[0];
};
static int inprocess_hash_fn(const void *obj, const int flags)
{
const struct inprocess *i = obj;
return atoi(i->mailbox);
}
static int inprocess_cmp_fn(void *obj, void *arg, int flags)
{
struct inprocess *i = obj, *j = arg;
if (strcmp(i->mailbox, j->mailbox)) {
return 0;
}
return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
}
static int inprocess_count(const char *context, const char *mailbox, int delta)
{
struct inprocess *i, *arg = ast_alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
arg->context = arg->mailbox + strlen(mailbox) + 1;
strcpy(arg->mailbox, mailbox); /* SAFE */
strcpy(arg->context, context); /* SAFE */
ao2_lock(inprocess_container);
if ((i = ao2_find(inprocess_container, arg, 0))) {
int ret = ast_atomic_fetchadd_int(&i->count, delta);
ao2_unlock(inprocess_container);
ao2_ref(i, -1);
if (delta < 0) {
ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
}
if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
ao2_unlock(inprocess_container);
return 0;
}
i->context = i->mailbox + strlen(mailbox) + 1;
strcpy(i->mailbox, mailbox); /* SAFE */
strcpy(i->context, context); /* SAFE */
i->count = delta;
ao2_link(inprocess_container, i);
ao2_unlock(inprocess_container);
ao2_ref(i, -1);
#if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
#endif
/*!
* \brief Strips control and non 7-bit clean characters from input string.
*
* \note To map control and none 7-bit characters to a 7-bit clean characters
* please use ast_str_encode_mine().
*/
static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
{
char *bufptr = buf;
for (; *input; input++) {
if (*input < 32) {
continue;
}
*bufptr++ = *input;
if (bufptr == buf + buflen - 1) {
break;
}
}
*bufptr = '\0';
return buf;
}
/*!
* \brief Sets default voicemail system options to a voicemail user.
*
* This applies select global settings to a newly created (dynamic) instance of a voicemail user.
* - all the globalflags
* - the saydurationminfo
* - the callcontext
* - the dialcontext
* - the exitcontext
* - vmmaxsecs, vmmaxmsg, maxdeletedmsg
* - emailsubject, emailbody set to NULL
static void populate_defaults(struct ast_vm_user *vmu)
ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
Tilghman Lesher
committed
vmu->passwordlocation = passwordlocation;
if (saydurationminfo) {
vmu->saydurationm = saydurationminfo;
Joshua Colp
committed
ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
Tilghman Lesher
committed
ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
if (vmminsecs) {
vmu->minsecs = vmminsecs;
}
if (vmmaxsecs) {
vmu->maxsecs = vmmaxsecs;
}
if (maxmsg) {
Kevin P. Fleming
committed
vmu->maxmsg = maxmsg;
}
if (maxdeletedmsg) {
vmu->maxdeletedmsg = maxdeletedmsg;
Tilghman Lesher
committed
vmu->volgain = volgain;
ast_free(vmu->email);
vmu->email = NULL;
ast_free(vmu->emailsubject);
vmu->emailsubject = NULL;
vmu->emailbody = NULL;
#ifdef IMAP_STORAGE
ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
ast_copy_string(vmu->imapserver, imapserver, sizeof(vmu->imapserver));
ast_copy_string(vmu->imapport, imapport, sizeof(vmu->imapport));
ast_copy_string(vmu->imapflags, imapflags, sizeof(vmu->imapflags));
#endif
/*!
* \brief Sets a a specific property value.
* \param vmu The voicemail user object to work with.
* \param var The name of the property to be set.
* \param value The value to be set to the property.
*
* The property name must be one of the understood properties. See the source for details.
*/
static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
} else if (!strcasecmp(var, "attachfmt")) {
ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
} else if (!strcasecmp(var, "fromstring")) {
ast_copy_string(vmu->fromstring, value, sizeof(vmu->fromstring));
} else if (!strcasecmp(var, "emailbody")) {
ast_free(vmu->emailbody);
vmu->emailbody = ast_strdup(substitute_escapes(value));
} else if (!strcasecmp(var, "emailsubject")) {
ast_free(vmu->emailsubject);
vmu->emailsubject = ast_strdup(substitute_escapes(value));
ast_copy_string(vmu->language, value, sizeof(vmu->language));
ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
Tilghman Lesher
committed
} else if (!strcasecmp(var, "locale")) {
ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
#ifdef IMAP_STORAGE
} else if (!strcasecmp(var, "imapuser")) {
ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imapserver")) {
ast_copy_string(vmu->imapserver, value, sizeof(vmu->imapserver));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imapport")) {
ast_copy_string(vmu->imapport, value, sizeof(vmu->imapport));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imapflags")) {
ast_copy_string(vmu->imapflags, value, sizeof(vmu->imapflags));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
Sean Bright
committed
ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imapfolder")) {
ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "imapvmshareid")) {
ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
vmu->imapversion = imapversion;
} else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
ast_set2_flag(vmu, ast_true(value), VM_DELETE);
} else if (!strcasecmp(var, "saycid")){
ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
} else if (!strcasecmp(var, "sendvoicemail")){
ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
} else if (!strcasecmp(var, "review")){
ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
} else if (!strcasecmp(var, "tempgreetwarn")){
ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
} else if (!strcasecmp(var, "messagewrap")){
ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
} else if (!strcasecmp(var, "envelope")){
ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
} else if (!strcasecmp(var, "moveheard")){
ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
} else if (!strcasecmp(var, "sayduration")){
ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
} else if (!strcasecmp(var, "saydurationm")){
vmu->saydurationm = x;
} else {
ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
} else if (!strcasecmp(var, "forcename")){
ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
} else if (!strcasecmp(var, "forcegreetings")){
ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
} else if (!strcasecmp(var, "minsecs")) {
if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
vmu->minsecs = x;
} else {
ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
vmu->minsecs = vmminsecs;
}
} else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
vmu->maxsecs = atoi(value);
if (vmu->maxsecs <= 0) {
ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
vmu->maxsecs = vmmaxsecs;
} else {
vmu->maxsecs = atoi(value);
}
if (!strcasecmp(var, "maxmessage"))
ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
} else if (!strcasecmp(var, "maxmsg")) {
vmu->maxmsg = atoi(value);
/* Accept maxmsg=0 (Greetings only voicemail) */
if (vmu->maxmsg < 0) {
ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
vmu->maxmsg = MAXMSG;
} else if (vmu->maxmsg > MAXMSGLIMIT) {
ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
vmu->maxmsg = MAXMSGLIMIT;
}
} else if (!strcasecmp(var, "nextaftercmd")) {
ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
} else if (!strcasecmp(var, "backupdeleted")) {
vmu->maxdeletedmsg = x;
else if (ast_true(value))
vmu->maxdeletedmsg = MAXMSG;
else
vmu->maxdeletedmsg = 0;
if (vmu->maxdeletedmsg < 0) {
ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
vmu->maxdeletedmsg = MAXMSG;
} else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
vmu->maxdeletedmsg = MAXMSGLIMIT;
}
Tilghman Lesher
committed
} else if (!strcasecmp(var, "volgain")) {
Tilghman Lesher
committed
} else if (!strcasecmp(var, "passwordlocation")) {
if (!strcasecmp(value, "spooldir")) {
vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
} else {
vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
}
} else if (!strcasecmp(var, "options")) {
apply_options(vmu, value);
static char *vm_check_password_shell(char *command, char *buf, size_t len)
{
int fds[2], pid = 0;
memset(buf, 0, len);
if (pipe(fds)) {
snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
} else {
/* good to go*/
pid = ast_safe_fork(0);
if (pid < 0) {
/* ok maybe not */
close(fds[0]);
close(fds[1]);
snprintf(buf, len, "FAILURE: Fork failed");
} else if (pid) {
/* parent */
close(fds[1]);
Kevin P. Fleming
committed
if (read(fds[0], buf, len) < 0) {
ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
}
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
close(fds[0]);
} else {
/* child */
AST_DECLARE_APP_ARGS(arg,
AST_APP_ARG(v)[20];
);
char *mycmd = ast_strdupa(command);
close(fds[0]);
dup2(fds[1], STDOUT_FILENO);
close(fds[1]);
ast_close_fds_above_n(STDOUT_FILENO);
AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
execv(arg.v[0], arg.v);
printf("FAILURE: %s", strerror(errno));
_exit(0);
}
}
return buf;
}
/*!
* \brief Check that password meets minimum required length
* \param vmu The voicemail user to change the password for.
* \param password The password string to check
*
* \return zero on ok, 1 on not ok.
*/
static int check_password(struct ast_vm_user *vmu, char *password)
{
/* check minimum length */
if (strlen(password) < minpassword)
return 1;
/* check that password does not contain '*' character */
if (!ast_strlen_zero(password) && password[0] == '*')
return 1;
if (!ast_strlen_zero(ext_pass_check_cmd)) {
char cmd[255], buf[255];
ast_debug(1, "Verify password policies for %s\n", password);
snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
ast_debug(5, "Result: %s\n", buf);
if (!strncasecmp(buf, "VALID", 5)) {
ast_debug(3, "Passed password check: '%s'\n", buf);
return 0;
} else if (!strncasecmp(buf, "FAILURE", 7)) {
ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
return 0;
} else {
ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
return 1;
}
}
}
return 0;
}
/*!
* \brief Performs a change of the voicemail passowrd in the realtime engine.
* \param vmu The voicemail user to change the password for.
* \param password The new value to be set to the password for this user.
*
* This only works if there is a realtime engine configured.
* This is called from the (top level) vm_change_password.
*
* \return zero on success, -1 on error.
*/
static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
int res = -1;
if (!strcmp(vmu->password, password)) {
/* No change (but an update would return 0 rows updated, so we opt out here) */
return 0;
}
if (strlen(password) > 10) {
ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
}
if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
ast_test_suite_event_notify("PASSWORDCHANGED", "Message: realtime engine updated with new password\r\nPasswordSource: realtime");
ast_copy_string(vmu->password, password, sizeof(vmu->password));
res = 0;
}
/*!
* \brief Destructively Parse options and apply.
*/
static void apply_options(struct ast_vm_user *vmu, const char *options)
char *stringp;
char *s;
char *var, *value;
stringp = ast_strdupa(options);
while ((s = strsep(&stringp, "|"))) {
value = s;
if ((var = strsep(&value, "=")) && value) {
apply_option(vmu, var, value);
}
}
/*!
* \brief Loads the options specific to a voicemail user.
*
* This is called when a vm_user structure is being set up, such as from load_options.
*/
static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
{
for (; var; var = var->next) {
if (!strcasecmp(var->name, "vmsecret")) {
ast_copy_string(retval->password, var->value, sizeof(retval->password));
} else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
if (ast_strlen_zero(retval->password)) {
if (!ast_strlen_zero(var->value) && var->value[0] == '*') {
ast_log(LOG_WARNING, "Invalid password detected for mailbox %s. The password"
"\n\tmust be reset in voicemail.conf.\n", retval->mailbox);
} else {
ast_copy_string(retval->password, var->value, sizeof(retval->password));
}
}
} else if (!strcasecmp(var->name, "uniqueid")) {
ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
} else if (!strcasecmp(var->name, "pager")) {
ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
} else if (!strcasecmp(var->name, "email")) {
ast_free(retval->email);
retval->email = ast_strdup(var->value);
} else if (!strcasecmp(var->name, "fullname")) {
ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
} else if (!strcasecmp(var->name, "context")) {
ast_copy_string(retval->context, var->value, sizeof(retval->context));
} else if (!strcasecmp(var->name, "emailsubject")) {
ast_free(retval->emailsubject);
retval->emailsubject = ast_strdup(substitute_escapes(var->value));
} else if (!strcasecmp(var->name, "emailbody")) {
ast_free(retval->emailbody);
retval->emailbody = ast_strdup(substitute_escapes(var->value));
} else if (!strcasecmp(var->name, "imapuser")) {
ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imapserver")) {
ast_copy_string(retval->imapserver, var->value, sizeof(retval->imapserver));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imapport")) {
ast_copy_string(retval->imapport, var->value, sizeof(retval->imapport));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imapflags")) {
ast_copy_string(retval->imapflags, var->value, sizeof(retval->imapflags));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imapfolder")) {
ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
retval->imapversion = imapversion;
} else if (!strcasecmp(var->name, "imapvmshareid")) {
ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
retval->imapversion = imapversion;
apply_option(retval, var->name, var->value);
}
/*!
* \brief Determines if a DTMF key entered is valid.
* \param key The character to be compared. expects a single character. Though is capable of handling a string, this is internally copies using ast_strdupa.
*
* Tests the character entered against the set of valid DTMF characters.
* \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
*/
Mark Michelson
committed
static int is_valid_dtmf(const char *key)
{
int i;
char *local_key = ast_strdupa(key);
for (i = 0; i < strlen(key); ++i) {
if (!strchr(VALID_DTMF, *local_key)) {
ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
Mark Michelson
committed
return 0;
}
local_key++;
}
return 1;
}
/*!
* \brief Finds a voicemail user from the realtime engine.
* \param ivm
* \param context
* \param mailbox
*
* This is called as a fall through case when the normal find_user() was not able to find a user. That is, the default it so look in the usual voicemail users file first.
*
* \return The ast_vm_user structure for the user that was found.
*/
static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
struct ast_variable *var;
struct ast_vm_user *retval;
BJ Weschke
committed
if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
Matthew Jordan
committed
if (ivm) {
Matthew Jordan
committed
}
populate_defaults(retval);
Matthew Jordan
committed
if (!ivm) {
ast_set_flag(retval, VM_ALLOCED);
}
if (mailbox) {
ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
}
if (!context && ast_test_flag((&globalflags), VM_SEARCH)) {
var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
Matthew Jordan
committed
} else {
var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
Matthew Jordan
committed
}
apply_options_full(retval, var);
retval = NULL;
}
}
return retval;
}
/*!
* \brief Finds a voicemail user from the users file or the realtime engine.
* \param ivm
* \param context
* \param mailbox
*
* \return The ast_vm_user structure for the user that was found.
*/
static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
{
/* This function could be made to generate one from a database, too */
struct ast_vm_user *vmu = NULL, *cur;
AST_LIST_LOCK(&users);
if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
AST_LIST_TRAVERSE(&users, cur, list) {
#ifdef IMAP_STORAGE
if (cur->imapversion != imapversion) {
continue;
}
#endif
if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
break;
if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
}
if (cur) {
BJ Weschke
committed
/* Make a copy, so that on a reload, we have no race */
if ((vmu = (ivm ? ivm : ast_calloc(1, sizeof(*vmu))))) {
ast_free(vmu->email);
ast_free(vmu->emailbody);
ast_free(vmu->emailsubject);
vmu->email = ast_strdup(cur->email);
vmu->emailbody = ast_strdup(cur->emailbody);
vmu->emailsubject = ast_strdup(cur->emailsubject);
ast_set2_flag(vmu, !ivm, VM_ALLOCED);
AST_LIST_NEXT(vmu, list) = NULL;
} else
vmu = find_user_realtime(ivm, context, mailbox);
AST_LIST_UNLOCK(&users);
return vmu;
}
/*!
* \brief Resets a user password to a specified password.
* \param context
* \param mailbox
* \param newpass
*
* This does the actual change password work, called by the vm_change_password() function.
*
* \return zero on success, -1 on error.
*/
static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
{
/* This function could be made to generate one from a database, too */
struct ast_vm_user *cur;
int res = -1;
AST_LIST_LOCK(&users);
AST_LIST_TRAVERSE(&users, cur, list) {
if ((!context || !strcasecmp(context, cur->context)) &&
(!strcasecmp(mailbox, cur->mailbox)))
break;
}
if (cur) {
ast_copy_string(cur->password, newpass, sizeof(cur->password));
res = 0;
}
AST_LIST_UNLOCK(&users);
return res;
/*!
* \brief Check if configuration file is valid
*/
static inline int valid_config(const struct ast_config *cfg)
{
return cfg && cfg != CONFIG_STATUS_FILEINVALID;
}
/*!
* \brief The handler for the change password option.
* \param vmu The voicemail user to work with.
* \param newpassword The new password (that has been gathered from the appropriate prompting).
* This is called when a new user logs in for the first time and the option to force them to change their password is set.
* It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
*/
static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
struct ast_config *cfg = NULL;
struct ast_variable *var = NULL;
struct ast_category *cat = NULL;
char *category = NULL, *value = NULL, *new = NULL;
const char *tmp = NULL;
struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
Tilghman Lesher
committed
char secretfn[PATH_MAX] = "";
int found = 0;
if (!change_password_realtime(vmu, newpassword))
return;
Tilghman Lesher
committed
/* check if we should store the secret in the spool directory next to the messages */
switch (vmu->passwordlocation) {
case OPT_PWLOC_SPOOLDIR:
snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
if (write_password_to_file(secretfn, newpassword) == 0) {
ast_test_suite_event_notify("PASSWORDCHANGED", "Message: secret.conf updated with new password\r\nPasswordSource: secret.conf");
Tilghman Lesher
committed
ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
reset_user_pw(vmu->context, vmu->mailbox, newpassword);
ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
break;
} else {
ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
}
/* Fall-through */
case OPT_PWLOC_VOICEMAILCONF:
if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && valid_config(cfg)) {
Tilghman Lesher
committed
while ((category = ast_category_browse(cfg, category))) {
if (!strcasecmp(category, vmu->context)) {
if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
break;
}
value = strstr(tmp, ",");
if (!value) {
new = ast_alloca(strlen(newpassword)+1);
sprintf(new, "%s", newpassword);
} else {
new = ast_alloca((strlen(value) + strlen(newpassword) + 1));
sprintf(new, "%s%s", newpassword, value);
Tilghman Lesher
committed
}
if (!(cat = ast_category_get(cfg, category, NULL))) {
Tilghman Lesher
committed
ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
break;
}
ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
found = 1;
Matt O'Gorman
committed
}
Tilghman Lesher
committed
}
/* save the results */
if (found) {
ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
Tilghman Lesher
committed
reset_user_pw(vmu->context, vmu->mailbox, newpassword);
ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "app_voicemail");
ast_config_destroy(cfg);
Tilghman Lesher
committed
break;
ast_config_destroy(cfg);
Tilghman Lesher
committed
/* Fall-through */
case OPT_PWLOC_USERSCONF:
/* check users.conf and update the password stored for the mailbox */
/* if no vmsecret entry exists create one. */
if ((cfg = ast_config_load("users.conf", config_flags)) && valid_config(cfg)) {
Tilghman Lesher
committed
ast_debug(4, "we are looking for %s\n", vmu->mailbox);
for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
ast_debug(4, "users.conf: %s\n", category);
if (!strcasecmp(category, vmu->mailbox)) {
if (!ast_variable_retrieve(cfg, category, "vmsecret")) {
Tilghman Lesher
committed
ast_debug(3, "looks like we need to make vmsecret!\n");
var = ast_variable_new("vmsecret", newpassword, "");
} else {
var = NULL;
}
new = ast_alloca(strlen(newpassword) + 1);
Tilghman Lesher
committed
sprintf(new, "%s", newpassword);
if (!(cat = ast_category_get(cfg, category, NULL))) {
Tilghman Lesher
committed
ast_debug(4, "failed to get category!\n");
ast_free(var);
break;
}
if (!var) {
ast_variable_update(cat, "vmsecret", new, NULL, 0);
} else {
ast_variable_append(cat, var);
}
found = 1;
Matt O'Gorman
committed
}
Tilghman Lesher
committed
}
/* save the results and clean things up */
if (found) {
ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
Tilghman Lesher
committed
reset_user_pw(vmu->context, vmu->mailbox, newpassword);
ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
ast_config_text_file_save("users.conf", cfg, "app_voicemail");
ast_config_destroy(cfg);
}
Mark Spencer
committed
static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
{
Olle Johansson
committed
char buf[255];
snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
ast_debug(1, "External password: %s\n",buf);
if (!ast_safe_system(buf)) {
ast_test_suite_event_notify("PASSWORDCHANGED", "Message: external script updated with new password\r\nPasswordSource: external");
ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
/* Reset the password in memory, too */
reset_user_pw(vmu->context, vmu->mailbox, newpassword);
}
Mark Spencer
committed
}
/*!
* \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
* \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
* \param len The length of the path string that was written out.
* \param context
* \param ext
* \param folder
*
* The path is constructed as
* VM_SPOOL_DIRcontext/ext/folder
*
* \return zero on success, -1 on error.
*/
static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
}
/*!
* \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
* \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
* \param len The length of the path string that was written out.
*
* The path is constructed as
* VM_SPOOL_DIRcontext/ext/folder
*
* \return zero on success, -1 on error.
*/
static int make_file(char *dest, const int len, const char *dir, const int num)
return snprintf(dest, len, "%s/msg%04d", dir, num);
/* same as mkstemp, but return a FILE * */
static FILE *vm_mkftemp(char *template)
FILE *p = NULL;
int pfd = mkstemp(template);
chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
if (pfd > -1) {
p = fdopen(pfd, "w+");
if (!p) {
close(pfd);
pfd = -1;
}
/*! \brief basically mkdir -p $dest/$context/$ext/$folder
* \param dest String. base directory.
* \param context String. Ignored if is null or empty string.
* \param ext String. Ignored if is null or empty string.
* \param folder String. Ignored if is null or empty string.
* \return -1 on failure, 0 on success.
static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
{
mode_t mode = VOICEMAIL_DIR_MODE;
Tilghman Lesher
committed
int res;
Tilghman Lesher
committed
make_dir(dest, len, context, ext, folder);
if ((res = ast_mkdir(dest, mode))) {