From d9f72c18936b73f3b174ff6666a7dd2c325407e9 Mon Sep 17 00:00:00 2001
From: Tilghman Lesher <tilghman@meg.abyt.es>
Date: Thu, 22 Oct 2009 19:10:04 +0000
Subject: [PATCH] Permit storage of voicemail secrets in a separate file,
 located within the spool directory. (closes issue #14276)  Reported by:
 klaus3000  Patches:        app_voicemail.c-svn-trunk-r214898.txt uploaded by
 klaus3000 (license 65)  Tested by: jamesgolovich

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@225406 65c4cc65-6c06-0410-ace0-fbb531ad65f3
---
 CHANGES                       |   6 +-
 apps/app_voicemail.c          | 228 ++++++++++++++++++++++++++--------
 configs/voicemail.conf.sample |  25 +++-
 3 files changed, 200 insertions(+), 59 deletions(-)

diff --git a/CHANGES b/CHANGES
index 32c304aaa9..e6e003b506 100644
--- a/CHANGES
+++ b/CHANGES
@@ -9,7 +9,7 @@
 ======================================================================
 
 ------------------------------------------------------------------------------
---- Functionality changes from Asterisk 1.6.2 to Asterisk 1.6.3  -------------
+--- Functionality changes from Asterisk 1.6.2 to Asterisk 1.8 ----------------
 ------------------------------------------------------------------------------
 
 SIP Changes
@@ -83,6 +83,10 @@ Applications
  * The MeetMe application now turns on the DENOISE() function by default, for
    each participant.  In our tests, this has significantly decreased background
    noise (especially noisy data centers).
+ * Voicemail now permits storage of secrets in a separate file, located in the
+   spool directory of each individual user.  The control for this is located in
+   the "passwordlocation" option in voicemail.conf.  Please see the sample
+   configuration for more information.
 
 Dialplan Functions
 ------------------
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 422590fe88..9fad4744d7 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -471,6 +471,12 @@ enum vm_option_args {
 	OPT_ARG_ARRAY_SIZE = 3,
 };
 
+enum vm_passwordlocation {
+	OPT_PWLOC_VOICEMAILCONF = 0,
+	OPT_PWLOC_SPOOLDIR      = 1,
+	OPT_PWLOC_USERSCONF     = 2,
+};
+
 AST_APP_OPTIONS(vm_app_options, {
 	AST_APP_OPTION('s', OPT_SILENT),
 	AST_APP_OPTION('b', OPT_BUSY_GREETING),
@@ -601,6 +607,7 @@ struct ast_vm_user {
 	int maxmsg;                      /*!< Maximum number of msgs per folder for this mailbox */
 	int maxdeletedmsg;               /*!< Maximum number of deleted msgs saved for this mailbox */
 	int maxsecs;                     /*!< Maximum number of seconds per message for this mailbox */
+	int passwordlocation;            /*!< Storage location of the password */
 #ifdef IMAP_STORAGE
 	char imapuser[80];               /*!< IMAP server login */
 	char imappassword[80];           /*!< IMAP server password if authpassword not defined */
@@ -738,6 +745,7 @@ static int maxgreet;
 static int skipms;
 static int maxlogins;
 static int minpassword;
+static int passwordlocation;
 
 /*! Poll mailboxes for changes since there is something external to
  *  app_voicemail that may change them. */
@@ -838,6 +846,8 @@ static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, in
 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);
 
 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
@@ -875,6 +885,7 @@ static char *strip_control(const char *input, char *buf, size_t buflen)
 static void populate_defaults(struct ast_vm_user *vmu)
 {
 	ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);	
+	vmu->passwordlocation = passwordlocation;
 	if (saydurationminfo)
 		vmu->saydurationm = saydurationminfo;
 	ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
@@ -996,6 +1007,12 @@ static void apply_option(struct ast_vm_user *vmu, const char *var, const char *v
 		}
 	} else if (!strcasecmp(var, "volgain")) {
 		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);
 	}
@@ -1316,65 +1333,94 @@ static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
 	char *category = NULL, *value = NULL, *new = NULL;
 	const char *tmp = NULL;
 	struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
+	char secretfn[PATH_MAX] = "";
+	int found = 0;
+
 	if (!change_password_realtime(vmu, newpassword))
 		return;
 
-	/* check voicemail.conf */
-	if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
-		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) {
-					ast_log(AST_LOG_WARNING, "variable has bad format.\n");
-					break;
-				}
-				new = alloca((strlen(value)+strlen(newpassword)+1));
-				sprintf(new, "%s%s", newpassword, value);
-				if (!(cat = ast_category_get(cfg, category))) {
-					ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
-					break;
+	/* 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_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)) && cfg != CONFIG_STATUS_FILEINVALID) {
+			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) {
+						ast_log(AST_LOG_WARNING, "variable has bad format.\n");
+						break;
+					}
+					new = alloca((strlen(value) + strlen(newpassword) + 1));
+					sprintf(new, "%s%s", newpassword, value);
+					if (!(cat = ast_category_get(cfg, category))) {
+						ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
+						break;
+					}
+					ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
+					found = 1;
 				}
-				ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
+			}
+			/* save the results */
+			if (found) {
+				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, "AppVoicemail");
+				break;
 			}
 		}
-		/* save the results */
-		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, "AppVoicemail");
-	}
-	category = NULL;
-	var = NULL;
-	/* 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)) && cfg != CONFIG_STATUS_FILEINVALID) {
-		ast_debug(4, "we are looking for %s\n", vmu->mailbox);
-		while ((category = ast_category_browse(cfg, category))) {
-			ast_debug(4, "users.conf: %s\n", category);
-			if (!strcasecmp(category, vmu->mailbox)) {
-				if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
-					ast_debug(3, "looks like we need to make vmsecret!\n");
-					var = ast_variable_new("vmsecret", newpassword, "");
-				} 
-				new = alloca(strlen(newpassword)+1);
-				sprintf(new, "%s", newpassword);
-				if (!(cat = ast_category_get(cfg, category))) {
-					ast_debug(4, "failed to get category!\n");
+		/* 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)) && cfg != CONFIG_STATUS_FILEINVALID) {
+			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 (!(tmp = 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 = alloca(strlen(newpassword) + 1);
+					sprintf(new, "%s", newpassword);
+					if (!(cat = ast_category_get(cfg, category))) {
+						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;
 					break;
 				}
-				if (!var)		
-					ast_variable_update(cat, "vmsecret", new, NULL, 0);
-				else
-					ast_variable_append(cat, var);
+			}
+			/* save the results and clean things up */
+			if (found) {
+				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, "AppVoicemail");
 			}
 		}
-		/* save the results and clean things up */
-		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, "AppVoicemail");
 	}
 }
 
@@ -9860,25 +9906,37 @@ static int append_mailbox(const char *context, const char *box, const char *data
 	struct ast_vm_user *vmu;
 	char *mailbox_full;
 	int new = 0, old = 0, urgent = 0;
+	char secretfn[PATH_MAX] = "";
 
 	tmp = ast_strdupa(data);
 
 	if (!(vmu = find_or_create(context, box)))
 		return -1;
-	
+
 	populate_defaults(vmu);
 
 	stringp = tmp;
-	if ((s = strsep(&stringp, ","))) 
+	if ((s = strsep(&stringp, ","))) {
 		ast_copy_string(vmu->password, s, sizeof(vmu->password));
-	if (stringp && (s = strsep(&stringp, ","))) 
+	}
+	if (stringp && (s = strsep(&stringp, ","))) {
 		ast_copy_string(vmu->fullname, s, sizeof(vmu->fullname));
-	if (stringp && (s = strsep(&stringp, ","))) 
+	}
+	if (stringp && (s = strsep(&stringp, ","))) {
 		ast_copy_string(vmu->email, s, sizeof(vmu->email));
-	if (stringp && (s = strsep(&stringp, ","))) 
+	}
+	if (stringp && (s = strsep(&stringp, ","))) {
 		ast_copy_string(vmu->pager, s, sizeof(vmu->pager));
-	if (stringp && (s = strsep(&stringp, ","))) 
+	}
+	if (stringp && (s = strsep(&stringp, ","))) {
 		apply_options(vmu, s);
+	}
+
+	switch (vmu->passwordlocation) {
+	case OPT_PWLOC_SPOOLDIR:
+		snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
+		read_password_from_file(secretfn, vmu->password, sizeof(vmu->password));
+	}
 
 	mailbox_full = alloca(strlen(box) + strlen(context) + 1);
 	strcpy(mailbox_full, box);
@@ -10563,6 +10621,7 @@ static int load_config(int reload)
 	int x;
 	int tmpadsi[4];
 	struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
+	char secretfn[PATH_MAX] = "";
 
 	ast_unload_realtime("voicemail");
 	ast_unload_realtime("voicemail_data");
@@ -11061,6 +11120,15 @@ static int load_config(int reload)
 			val = "no";
 		ast_set2_flag((&globalflags), ast_true(val), VM_DIRECFORWARD);	
 
+		if (!(val = ast_variable_retrieve(cfg, "general", "passwordlocation"))) {
+			val = "voicemail.conf";
+		}
+		if (!(strcmp(val, "spooldir"))) {
+			passwordlocation = OPT_PWLOC_SPOOLDIR;
+		} else {
+			passwordlocation = OPT_PWLOC_VOICEMAILCONF;
+		}
+
 		poll_freq = DEFAULT_POLL_FREQ;
 		if ((val = ast_variable_retrieve(cfg, "general", "pollfreq"))) {
 			if (sscanf(val, "%30u", &poll_freq) != 1) {
@@ -11081,6 +11149,15 @@ static int load_config(int reload)
 					populate_defaults(current);
 					apply_options_full(current, ast_variable_browse(ucfg, cat));
 					ast_copy_string(current->context, userscontext, sizeof(current->context));
+					if (!ast_strlen_zero(current->password) && current->passwordlocation == OPT_PWLOC_VOICEMAILCONF) {
+						current->passwordlocation = OPT_PWLOC_USERSCONF;
+					}
+
+					switch (current->passwordlocation) {
+					case OPT_PWLOC_SPOOLDIR:
+						snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, current->context, current->mailbox);
+						read_password_from_file(secretfn, current->password, sizeof(current->password));
+					}
 				}
 			}
 			ast_config_destroy(ucfg);
@@ -11216,6 +11293,47 @@ static int sayname(struct ast_channel *chan, const char *mailbox, const char *co
 	return res;
 }
 
+static void read_password_from_file(const char *secretfn, char *password, int passwordlen) {
+	struct ast_config *pwconf;
+	struct ast_flags config_flags = { 0 };
+
+	pwconf = ast_config_load(secretfn, config_flags);
+	if (pwconf) {
+		const char *val = ast_variable_retrieve(pwconf, "general", "password");
+		if (val) {
+			ast_copy_string(password, val, passwordlen);
+ 			return;
+		}
+	}
+	ast_log(LOG_NOTICE, "Failed reading voicemail password from %s, using secret from config file\n", secretfn);
+}
+
+static int write_password_to_file(const char *secretfn, const char *password) {
+	struct ast_config *conf;
+	struct ast_category *cat;
+	struct ast_variable *var;
+
+	if (!(conf=ast_config_new())) {
+		ast_log(LOG_ERROR, "Error creating new config structure\n");
+		return -1;
+	}
+	if (!(cat=ast_category_new("general","",1))) {
+		ast_log(LOG_ERROR, "Error creating new category structure\n");
+		return -1;
+	}
+	if (!(var=ast_variable_new("password",password,""))) {
+		ast_log(LOG_ERROR, "Error creating new variable structure\n");
+		return -1;
+	}
+	ast_category_append(conf,cat);
+	ast_variable_append(cat,var);
+	if (ast_config_text_file_save(secretfn, conf, "app_voicemail")) {
+		ast_log(LOG_ERROR, "Error writing voicemail password to %s\n", secretfn);
+		return -1;
+	}
+	return 0;
+}
+
 static int reload(void)
 {
 	return load_config(1);
diff --git a/configs/voicemail.conf.sample b/configs/voicemail.conf.sample
index 18615dcd81..024f158cdb 100644
--- a/configs/voicemail.conf.sample
+++ b/configs/voicemail.conf.sample
@@ -274,9 +274,28 @@ sendvoicemail=yes ; Allow the user to compose and send a voicemail while inside
 			;     The default is "no".
 ; tempgreetwarn=yes	; Remind the user that their temporary greeting is set
 
-;messagewrap=no	; Enable next/last message to wrap around to
-			; first (from last) and last (from first) message
-			; The default is "no".
+; passwordlocation=spooldir
+                    ; Usually the voicemail password (vmsecret) is stored in
+                    ; this configuration file.  By setting this option you can
+                    ; specify where Asterisk should read/write the vmsecret.
+                    ; Supported options:
+                    ;   voicemail.conf:
+                    ;     This is the default option.  The secret is read from
+                    ;     and written to voicemail.conf (or users.conf).
+                    ;   spooldir:
+                    ;     The secret is stored in a separate file in the user's
+                    ;     voicemail spool directory in a file named secret.conf.
+                    ;     Please ensure that normal Linux users are not
+                    ;     permitted to access Asterisk's spool directory as the
+                    ;     secret is stored in plain text.  If a secret is not
+                    ;     found in this directory, the password in
+                    ;     voicemail.conf (or users.conf) will be used.
+                    ; Note that this option does not affect password storage for
+                    ; realtime users, which are still stored in the realtime
+                    ; backend.
+; messagewrap=no    ; Enable next/last message to wrap around to
+                    ; first (from last) and last (from first) message
+                    ; The default is "no".
 ; minpassword=0 ; Enforce minimum password length
 
 ; vm-password=custom_sound
-- 
GitLab