Skip to content
Snippets Groups Projects
app_voicemail.c 522 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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_new;
    	int old_old;
    
    	char *uniqueid;
    
    struct mwi_sub_task {
    	const char *mailbox;
    	const char *context;
    
    	const char *uniqueid;
    
    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);
    
    
    /* 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];
    
    
    Olle Johansson's avatar
    Olle Johansson committed
    /* custom password sounds */
    
    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;
    
    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 *emailsubject = NULL;
    
    static char *pagerbody = NULL;
    static char *pagersubject = NULL;
    
    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";
    
    static int adsiver = 1;
    
    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);
    
    static int is_valid_dtmf(const char *key);
    
    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
    
     * \retval other Failure
    
     */
    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
    
    
    /*!
     * \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
    
     * - volume gain.
    
     * - emailsubject, emailbody set to NULL
    
    static void populate_defaults(struct ast_vm_user *vmu)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
    
    		vmu->saydurationm = saydurationminfo;
    
    	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));
    
    	ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
    
    	if (vmminsecs) {
    		vmu->minsecs = vmminsecs;
    	}
    	if (vmmaxsecs) {
    
    		vmu->maxdeletedmsg = maxdeletedmsg;
    
    	ast_free(vmu->email);
    	vmu->email = NULL;
    
    	ast_free(vmu->emailsubject);
    
    	ast_free(vmu->emailbody);
    
    #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));
    
    /*!
     * \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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!strcasecmp(var, "attach")) {
    
    		ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
    	} else if (!strcasecmp(var, "attachfmt")) {
    		ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "serveremail")) {
    
    		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));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "language")) {
    
    		ast_copy_string(vmu->language, value, sizeof(vmu->language));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "tz")) {
    
    		ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
    
    	} 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")) {
    
    		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);	
    
    	} else if (!strcasecmp(var, "operator")) {
    
    		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")){
    
    Jason Parker's avatar
    Jason Parker committed
    		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")){
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (sscanf(value, "%30d", &x) == 1) {
    
    			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);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "callback")) {
    
    		ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "dialout")) {
    
    		ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "exitcontext")) {
    
    		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);
    
    			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);
    		}
    
    			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);
    
    	} else if (!strcasecmp(var, "nextaftercmd")) {
    		ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
    
    	} else if (!strcasecmp(var, "backupdeleted")) {
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		if (sscanf(value, "%30d", &x) == 1)
    
    			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's avatar
    Tilghman Lesher committed
    		sscanf(value, "%30lf", &vmu->volgain);
    
    	} 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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    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]);
    
    			if (read(fds[0], buf, len) < 0) {
    				ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
    			}
    
    			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);
    
    				ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
    
    /*! 
     * \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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void apply_options(struct ast_vm_user *vmu, const char *options)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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));
    
    #ifdef IMAP_STORAGE
    
    		} 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;
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    		} 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.
     */
    
    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);
    
    /*!
     * \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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
    
    	if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
    
    			memset(retval, 0, sizeof(*retval));
    
    		populate_defaults(retval);
    
    		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);
    
    			var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (var) {
    
    			apply_options_full(retval, var);
    
    			ast_variables_destroy(var);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		} else { 
    			if (!ivm) 
    
    				ast_free(retval);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    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);
    
    Josh Roberson's avatar
    Josh Roberson committed
    
    
    	if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
    
    Josh Roberson's avatar
    Josh Roberson committed
    		context = "default";
    
    
    	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)))
    
    		/* 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;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else
    		vmu = find_user_realtime(ivm, context, mailbox);
    
    	AST_LIST_UNLOCK(&users);
    
    /*!
     * \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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    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));
    
    	AST_LIST_UNLOCK(&users);
    
    /*!
     * \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.
     */
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	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 };
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!change_password_realtime(vmu, newpassword))
    		return;
    
    
    	/* 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");
    
    			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)) {
    
    			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);
    
    					if (!(cat = ast_category_get(cfg, category, NULL))) {
    
    						ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
    						break;
    					}
    					ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
    					found = 1;
    
    				ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
    
    				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);
    
    		/* 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)) {
    
    			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")) {
    
    						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);
    
    					if (!(cat = ast_category_get(cfg, category, NULL))) {
    
    						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;
    
    				ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
    
    				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");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
    {
    
    	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);
    	}
    
    /*! 
     * \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.
    
    Leif Madsen's avatar
    Leif Madsen committed
     * \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.
    
    Leif Madsen's avatar
    Leif Madsen committed
     * \param dir 
     * \param num 
    
     * 
     * 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 len     Length of dest.
    
     * \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;
    
    	make_dir(dest, len, context, ext, folder);
    	if ((res = ast_mkdir(dest, mode))) {