allow channel receive gain to be adjusted while recording messages/greetings in voice...
authorKevin P. Fleming <kpfleming@digium.com>
Wed, 14 Sep 2005 20:00:54 +0000 (20:00 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Wed, 14 Sep 2005 20:00:54 +0000 (20:00 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@6595 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_voicemail.c

index 70b32b5..c08424f 100755 (executable)
@@ -97,6 +97,23 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 #define ERROR_LOCK_PATH                -100
 
+#define OPT_SILENT             (1 << 0)
+#define OPT_BUSY_GREETING      (1 << 1)
+#define OPT_UNAVAIL_GREETING   (1 << 2)
+#define OPT_RECORDGAIN         (1 << 3)
+#define OPT_PREPEND_MAILBOX    (1 << 4)
+
+#define OPT_ARG_RECORDGAIN     0
+#define OPT_ARG_ARRAY_SIZE     1
+
+AST_DECLARE_OPTIONS(vm_app_options, {
+       ['s'] = { .flag = OPT_SILENT },
+       ['b'] = { .flag = OPT_BUSY_GREETING },
+       ['u'] = { .flag = OPT_UNAVAIL_GREETING },
+       ['g'] = { .flag = OPT_RECORDGAIN, .arg_index = OPT_ARG_RECORDGAIN + 1},
+       ['p'] = { .flag = OPT_PREPEND_MAILBOX },
+});
+
 static int load_config(void);
 
 /* Syntaxes supported, not really language codes.
@@ -210,10 +227,13 @@ struct vm_state {
        int starting;
        int repeats;
 };
-static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option);
+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, const char *unlockdir);
-static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc);
+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, const char *unlockdir,
+                             signed char record_gain);
+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 void apply_options(struct ast_vm_user *vmu, const char *options);
@@ -250,13 +270,14 @@ static char *synopsis_vm =
 "Leave a voicemail message";
 
 static char *descrip_vm =
-"  VoiceMail(extension[@context][&extension[@context]][...][|options]):  Leaves"
-"voicemail for a given extension (must be configured in voicemail.conf).\n"
+"  VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]):  Leaves"
+"voicemail for a given mailbox (must be configured in voicemail.conf).\n"
 " If the options contain: \n"
-"* 's' then instructions for leaving the message will be skipped.\n"
-"* 'u' then the \"unavailable\" message will be played.\n"
-"  (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it exists.\n"
-"* 'b' then the the busy message will be played (that is, busy instead of unavail).\n"
+"* 's'    instructions for leaving the message will be skipped.\n"
+"* 'u'    the \"unavailable\" greeting will be played.\n"
+"* 'b'    the \"busy\" greeting will be played.\n"
+"* 'g(#)' the specified amount of gain will be requested during message\n"
+"         recording (units are whole-number decibels (dB))\n"
 "If the caller presses '0' (zero) during the prompt, the call jumps to\n"
 "extension 'o' in the current context.\n"
 "If the caller presses '*' during the prompt, the call jumps to\n"
@@ -277,11 +298,14 @@ static char *descrip_vmain =
 "  VoiceMailMain([mailbox][@context][|options]): Enters the main voicemail system\n"
 "for the checking of voicemail. The mailbox can be passed in,\n"
 "which will stop the voicemail system from prompting the user for the mailbox.\n"
-"If the options contain 's' then the password check will be skipped.  If\n"
-"the options contain 'p' then the supplied mailbox is prepended to the\n"
-"user's entry and the resulting string is used as the mailbox number.  This is\n"
-"useful for virtual hosting of voicemail boxes.  If a context is specified,\n"
-"logins are considered in that voicemail context only.\n"
+"If the options contain: \n"
+"* 's'    the password check will be skipped.\n"
+"* 'p'    the supplied mailbox is prepended to the user's entry and\n"
+"         the resulting string is used as the mailbox number. This can\n"
+"         be useful for virtual hosting of voicemail boxes.\n"
+"* 'g(#)' the specified amount of gain will be requested during message\n"
+"         recording (units are whole-number decibels (dB))\n"
+"If a context is specified, mailboxes are considered in that voicemail context only.\n"
 "Returns -1 if the user hangs up or 0 otherwise.\n";
 
 static char *synopsis_vm_box_exists =
@@ -2319,8 +2343,12 @@ static void run_externnotify(char *context, char *extension)
        }
 }
 
+struct leave_vm_options {
+       unsigned int flags;
+       signed char record_gain;
+};
 
-static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int busy, int unavail)
+static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
 {
        char txtfile[256];
        char callerid[256];
@@ -2362,252 +2390,250 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
 
        category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
 
-       if ((vmu = find_user(&svm, context, ext))) {
-               /* Setup pre-file if appropriate */
-               if (strcmp(vmu->context, "default"))
-                       snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
-               else
-                       ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
-               if (busy)
-                       snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
-               else if (unavail)
-                       snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
-               snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
-               RETRIEVE(tempfile, -1);
-               if (ast_fileexists(tempfile, NULL, NULL) > 0)
-                       ast_copy_string(prefile, tempfile, sizeof(prefile));
-               DISPOSE(tempfile, -1);
-               make_dir(dir, sizeof(dir), vmu->context, "", "");
-               /* It's easier just to try to make it than to check for its existence */
-               if (mkdir(dir, 0700) && (errno != EEXIST))
-                       ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
-               make_dir(dir, sizeof(dir), vmu->context, ext, "");
-               /* It's easier just to try to make it than to check for its existence */
-               if (mkdir(dir, 0700) && (errno != EEXIST))
-                       ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
-               make_dir(dir, sizeof(dir), vmu->context, ext, "INBOX");
-               if (mkdir(dir, 0700) && (errno != EEXIST))
-                       ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
-
-               /* Check current or macro-calling context for special extensions */
-               if (!ast_strlen_zero(vmu->exit)) {
-                       if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num))
-                               strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
-               } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num))
-                       strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
-               else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
+       if (!(vmu = find_user(&svm, context, ext))) {
+               ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
+               ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
+               return res;
+       }
+
+       /* Setup pre-file if appropriate */
+       if (strcmp(vmu->context, "default"))
+               snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
+       else
+               ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
+       if (ast_test_flag(options, OPT_BUSY_GREETING))
+               snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
+       else if (ast_test_flag(options, OPT_UNAVAIL_GREETING))
+               snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
+       snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
+       RETRIEVE(tempfile, -1);
+       if (ast_fileexists(tempfile, NULL, NULL) > 0)
+               ast_copy_string(prefile, tempfile, sizeof(prefile));
+       DISPOSE(tempfile, -1);
+       make_dir(dir, sizeof(dir), vmu->context, "", "");
+       /* It's easier just to try to make it than to check for its existence */
+       if (mkdir(dir, 0700) && (errno != EEXIST))
+               ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
+       make_dir(dir, sizeof(dir), vmu->context, ext, "");
+       /* It's easier just to try to make it than to check for its existence */
+       if (mkdir(dir, 0700) && (errno != EEXIST))
+               ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
+       make_dir(dir, sizeof(dir), vmu->context, ext, "INBOX");
+       if (mkdir(dir, 0700) && (errno != EEXIST))
+               ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
+
+       /* Check current or macro-calling context for special extensions */
+       if (!ast_strlen_zero(vmu->exit)) {
+               if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num))
                        strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
-                       ousemacro = 1;
-               }
+       } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num))
+               strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
+       else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
+               strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
+               ousemacro = 1;
+       }
 
-               if (!ast_strlen_zero(vmu->exit)) {
-                       if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
-                               strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
-               } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
-                       strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
-               else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
+       if (!ast_strlen_zero(vmu->exit)) {
+               if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
                        strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
-                       ausemacro = 1;
-               }
-
+       } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
+               strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
+       else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
+               strncat(ecodes, "*", sizeof(ecodes) -  strlen(ecodes) - 1);
+               ausemacro = 1;
+       }
 
-               /* Play the beginning intro if desired */
-               if (!ast_strlen_zero(prefile)) {
-                       RETRIEVE(prefile, -1);
-                       if (ast_fileexists(prefile, NULL, NULL) > 0) {
-                               if (ast_streamfile(chan, prefile, chan->language) > -1) 
-                                       res = ast_waitstream(chan, ecodes);
-                       } else {
-                               ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
-                               res = invent_message(chan, vmu->context, ext, busy, ecodes);
-                       }
-                       DISPOSE(prefile, -1);
-                       if (res < 0) {
-                               ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
-                               free_user(vmu);
-                               return -1;
-                       }
+       /* Play the beginning intro if desired */
+       if (!ast_strlen_zero(prefile)) {
+               RETRIEVE(prefile, -1);
+               if (ast_fileexists(prefile, NULL, NULL) > 0) {
+                       if (ast_streamfile(chan, prefile, chan->language) > -1) 
+                               res = ast_waitstream(chan, ecodes);
+               } else {
+                       ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
+                       res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
                }
+               DISPOSE(prefile, -1);
+               if (res < 0) {
+                       ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
+                       free_user(vmu);
+                       return -1;
+               }
+       }
+       if (res == '#') {
+               /* On a '#' we skip the instructions */
+               ast_set_flag(options, OPT_SILENT);
+               res = 0;
+       }
+       if (!res && !ast_test_flag(options, OPT_SILENT)) {
+               res = ast_streamfile(chan, INTRO, chan->language);
+               if (!res)
+                       res = ast_waitstream(chan, ecodes);
                if (res == '#') {
-                       /* On a '#' we skip the instructions */
-                       silent = 1;
+                       ast_set_flag(options, OPT_SILENT);
                        res = 0;
                }
-               if (!res && !silent) {
-                       res = ast_streamfile(chan, INTRO, chan->language);
-                       if (!res)
-                               res = ast_waitstream(chan, ecodes);
-                       if (res == '#') {
-                               silent = 1;
-                               res = 0;
-                       }
+       }
+       if (res > 0)
+               ast_stopstream(chan);
+       /* Check for a '*' here in case the caller wants to escape from voicemail to something
+          other than the operator -- an automated attendant or mailbox login for example */
+       if (res == '*') {
+               chan->exten[0] = 'a';
+               chan->exten[1] = '\0';
+               if (!ast_strlen_zero(vmu->exit)) {
+                       ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
+               } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
+                       ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
                }
-               if (res > 0)
-                       ast_stopstream(chan);
-               /* Check for a '*' here in case the caller wants to escape from voicemail to something
-               other than the operator -- an automated attendant or mailbox login for example */
-               if (res == '*') {
-                       chan->exten[0] = 'a';
+               chan->priority = 0;
+               free_user(vmu);
+               return 0;
+       }
+       /* Check for a '0' here */
+       if (res == '0') {
+       transfer:
+               if (ast_test_flag(vmu, VM_OPERATOR)) {
+                       chan->exten[0] = 'o';
                        chan->exten[1] = '\0';
                        if (!ast_strlen_zero(vmu->exit)) {
                                ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
-                       } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
+                       } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
                                ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
                        }
+                       ast_play_and_wait(chan, "transfer");
                        chan->priority = 0;
                        free_user(vmu);
                        return 0;
+               } else {
+                       ast_play_and_wait(chan, "vm-sorry");
+                       return 0;
                }
-               /* Check for a '0' here */
-               if (res == '0') {
-                       transfer:
-                       if (ast_test_flag(vmu, VM_OPERATOR)) {
-                               chan->exten[0] = 'o';
-                               chan->exten[1] = '\0';
-                               if (!ast_strlen_zero(vmu->exit)) {
-                                       ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
-                               } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
-                                       ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
-                               }
-                               ast_play_and_wait(chan, "transfer");
-                               chan->priority = 0;
-                               free_user(vmu);
-                               return 0;
-                       } else {
-                               ast_play_and_wait(chan, "vm-sorry");
-                               return 0;
-                       }
-               }
-               if (res < 0) {
+       }
+       if (res < 0) {
+               free_user(vmu);
+               return -1;
+       }
+       /* The meat of recording the message...  All the announcements and beeps have been played*/
+       ast_copy_string(fmt, vmfmts, sizeof(fmt));
+       if (!ast_strlen_zero(fmt)) {
+               msgnum = 0;
+
+               if (vm_lock_path(dir)) {
                        free_user(vmu);
-                       return -1;
+                       return ERROR_LOCK_PATH;
                }
-               /* The meat of recording the message...  All the announcements and beeps have been played*/
-               ast_copy_string(fmt, vmfmts, sizeof(fmt));
-               if (!ast_strlen_zero(fmt)) {
-                       msgnum = 0;
-
-                       if (vm_lock_path(dir)) {
-                               free_user(vmu);
-                               return ERROR_LOCK_PATH;
-                       }
 
-                       /* 
-                        * This operation can be very expensive if done say over NFS or if the mailbox has 100+ messages
-                        * in the mailbox.  So we should get this first so we don't cut off the first few seconds of the 
-                        * message.  
-                        */
-                       do {
-                               make_file(fn, sizeof(fn), dir, msgnum);
-                               if (!EXISTS(dir,msgnum,fn,chan->language))
-                                       break;
-                               msgnum++;
-                       } while (msgnum < vmu->maxmsg);
+               /* 
+                * This operation can be very expensive if done say over NFS or if the mailbox has 100+ messages
+                * in the mailbox.  So we should get this first so we don't cut off the first few seconds of the 
+                * message.  
+                */
+               do {
+                       make_file(fn, sizeof(fn), dir, msgnum);
+                       if (!EXISTS(dir,msgnum,fn,chan->language))
+                               break;
+                       msgnum++;
+               } while (msgnum < vmu->maxmsg);
 
-                       /* Now play the beep once we have the message number for our next message. */
-                       if (res >= 0) {
-                               /* Unless we're *really* silent, try to send the beep */
-                               res = ast_streamfile(chan, "beep", chan->language);
-                               if (!res)
-                                       res = ast_waitstream(chan, "");
-                       }
-                       if (msgnum < vmu->maxmsg) {
-                               /* assign a variable with the name of the voicemail file */       
-                               pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
+               /* Now play the beep once we have the message number for our next message. */
+               if (res >= 0) {
+                       /* Unless we're *really* silent, try to send the beep */
+                       res = ast_streamfile(chan, "beep", chan->language);
+                       if (!res)
+                               res = ast_waitstream(chan, "");
+               }
+               if (msgnum < vmu->maxmsg) {
+                       /* assign a variable with the name of the voicemail file */       
+                       pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
                                
-                               /* Store information */
-                               snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
-                               txt = fopen(txtfile, "w+");
-                               if (txt) {
-                                       get_date(date, sizeof(date));
-                                       fprintf(txt, 
-                                               ";\n"
-                                               "; Message Information file\n"
-                                               ";\n"
-                                               "[message]\n"
-                                               "origmailbox=%s\n"
-                                               "context=%s\n"
-                                               "macrocontext=%s\n"
-                                               "exten=%s\n"
-                                               "priority=%d\n"
-                                               "callerchan=%s\n"
-                                               "callerid=%s\n"
-                                               "origdate=%s\n"
-                                               "origtime=%ld\n"
-                                               "category=%s\n",
-                                               ext,
-                                               chan->context,
-                                               chan->macrocontext, 
-                                               chan->exten,
-                                               chan->priority,
-                                               chan->name,
-                                               ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
-                                               date, (long)time(NULL),
-                                               category ? category : ""); 
-                               } else
-                                       ast_log(LOG_WARNING, "Error opening text file for output\n");
-                               res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir);
-                               if (res == '0') {
-                                       if (txt)
-                                               fclose(txt);
-                                       goto transfer;
-                               }
-                               if (res > 0)
-                                       res = 0;
-                               if (txt) {
-                                       fprintf(txt, "duration=%d\n", duration);
+                       /* Store information */
+                       snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
+                       txt = fopen(txtfile, "w+");
+                       if (txt) {
+                               get_date(date, sizeof(date));
+                               fprintf(txt, 
+                                       ";\n"
+                                       "; Message Information file\n"
+                                       ";\n"
+                                       "[message]\n"
+                                       "origmailbox=%s\n"
+                                       "context=%s\n"
+                                       "macrocontext=%s\n"
+                                       "exten=%s\n"
+                                       "priority=%d\n"
+                                       "callerchan=%s\n"
+                                       "callerid=%s\n"
+                                       "origdate=%s\n"
+                                       "origtime=%ld\n"
+                                       "category=%s\n",
+                                       ext,
+                                       chan->context,
+                                       chan->macrocontext, 
+                                       chan->exten,
+                                       chan->priority,
+                                       chan->name,
+                                       ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
+                                       date, (long)time(NULL),
+                                       category ? category : ""); 
+                       } else
+                               ast_log(LOG_WARNING, "Error opening text file for output\n");
+                       res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir, options->record_gain);
+                       if (res == '0') {
+                               if (txt)
                                        fclose(txt);
-                               }
+                               goto transfer;
+                       }
+                       if (res > 0)
+                               res = 0;
+                       if (txt) {
+                               fprintf(txt, "duration=%d\n", duration);
+                               fclose(txt);
+                       }
                                
-                               if (duration < vmminmessage) {
-                                       if (option_verbose > 2) 
-                                               ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
-                                       DELETE(dir,msgnum,fn);
-                                       /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
-                                       goto leave_vm_out;
-                               }
-                               /* Are there to be more recipients of this message? */
-                               while (tmpptr) {
-                                       struct ast_vm_user recipu, *recip;
-                                       char *exten, *context;
+                       if (duration < vmminmessage) {
+                               if (option_verbose > 2) 
+                                       ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
+                               DELETE(dir,msgnum,fn);
+                               /* XXX We should really give a prompt too short/option start again, with leave_vm_out called only after a timeout XXX */
+                               goto leave_vm_out;
+                       }
+                       /* Are there to be more recipients of this message? */
+                       while (tmpptr) {
+                               struct ast_vm_user recipu, *recip;
+                               char *exten, *context;
                                        
-                                       exten = strsep(&tmpptr, "&");
-                                       context = strchr(exten, '@');
-                                       if (context) {
-                                               *context = '\0';
-                                               context++;
-                                       }
-                                       if ((recip = find_user(&recipu, context, exten))) {
-                                               copy_message(chan, vmu, 0, msgnum, duration, recip, fmt);
-                                               free_user(recip);
-                                       }
+                               exten = strsep(&tmpptr, "&");
+                               context = strchr(exten, '@');
+                               if (context) {
+                                       *context = '\0';
+                                       context++;
                                }
-                               if (ast_fileexists(fn, NULL, NULL)) {
-                                       notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
-                                       STORE(dir, vmu->mailbox, vmu->context, msgnum);
-                                       DISPOSE(dir, msgnum);
+                               if ((recip = find_user(&recipu, context, exten))) {
+                                       copy_message(chan, vmu, 0, msgnum, duration, recip, fmt);
+                                       free_user(recip);
                                }
-                       } else {
-                               ast_unlock_path(dir);
-                               res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
-                               if (!res)
-                                       res = ast_waitstream(chan, "");
-                               ast_log(LOG_WARNING, "No more messages possible\n");
                        }
-               } else
-                       ast_log(LOG_WARNING, "No format for saving voicemail?\n");
-leave_vm_out:
-               free_user(vmu);
-       } else {
-               ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
-               /*Send the call to n+101 priority, where n is the current priority*/
-               ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
-       }
+                       if (ast_fileexists(fn, NULL, NULL)) {
+                               notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
+                               STORE(dir, vmu->mailbox, vmu->context, msgnum);
+                               DISPOSE(dir, msgnum);
+                       }
+               } else {
+                       ast_unlock_path(dir);
+                       res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
+                       if (!res)
+                               res = ast_waitstream(chan, "");
+                       ast_log(LOG_WARNING, "No more messages possible\n");
+               }
+       } else
+               ast_log(LOG_WARNING, "No format for saving voicemail?\n");
+ leave_vm_out:
+       free_user(vmu);
 
        return res;
 }
 
-
 static int resequence_mailbox(struct ast_vm_user *vmu, char *dir)
 {
        /* we know max messages, so stop process when number is hit */
@@ -3207,11 +3233,13 @@ static int get_folder2(struct ast_channel *chan, char *fn, int start)
        return res;
 }
 
-static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char *curdir, int curmsg, char *vmfts, char *context)
+static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu, char *curdir, int curmsg, char *vmfts,
+                            char *context, signed char record_gain)
 {
        int cmd = 0;
        int retries = 0;
        int duration = 0;
+       signed char zero_gain = 0;
 
        while ((cmd >= 0) && (cmd != 't') && (cmd != '*')) {
                if (cmd)
@@ -3222,7 +3250,11 @@ static int vm_forwardoptions(struct ast_channel *chan, struct ast_vm_user *vmu,
                {
                        char file[200];
                        snprintf(file, sizeof(file), "%s/msg%04d", curdir, curmsg);
+                       if (record_gain)
+                               ast_channel_setoption(chan, AST_OPTION_TXGAIN, &record_gain, sizeof(record_gain), 0);
                        cmd = ast_play_and_prepend(chan, NULL, file, 0, vmfmts, &duration, 1, silencethreshold, maxsilence);
+                       if (record_gain)
+                               ast_channel_setoption(chan, AST_OPTION_TXGAIN, &zero_gain, sizeof(zero_gain), 0);
                        break;
                }
                case '2': 
@@ -3297,7 +3329,8 @@ static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu,
        return 0;
 }
 
-static int forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender, char *fmt,int flag)
+static int forward_message(struct ast_channel *chan, char *context, char *dir, int curmsg, struct ast_vm_user *sender,
+                          char *fmt, int flag, signed char record_gain)
 {
        char username[70]="";
        char sys[256];
@@ -3317,7 +3350,6 @@ static int forward_message(struct ast_channel *chan, char *context, char *dir, i
        int valid_extensions = 0;
        
        while (!res && !valid_extensions) {
-               
                int use_directory = 0;
                if(ast_test_flag((&globalflags), VM_DIRECFORWARD)) {
                        int done = 0;
@@ -3431,11 +3463,15 @@ static int forward_message(struct ast_channel *chan, char *context, char *dir, i
                return res;
        vmtmp = extensions;
        if (flag==1) {
+               struct leave_vm_options leave_options;
+
                /* Send VoiceMail */
-               cmd=leave_voicemail(chan,username,0,0,0);
+               memset(&leave_options, 0, sizeof(leave_options));
+               leave_options.record_gain = record_gain;
+               cmd = leave_voicemail(chan, username, &leave_options);
        } else {
                /* Forward VoiceMail */
-               cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context);
+               cmd = vm_forwardoptions(chan, sender, dir, curmsg, vmfmts, context, record_gain);
                if (!cmd) {
                        while (!res && vmtmp) {
                                /* if (ast_play_and_wait(chan, "vm-savedto"))
@@ -4523,7 +4559,7 @@ static int vm_instructions(struct ast_channel *chan, struct vm_state *vms, int s
        return res;
 }
 
-static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc)
+static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
 {
        int cmd = 0;
        int duration = 0;
@@ -4576,7 +4612,7 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
        /* If forcename is set, have the user record their name */      
        if (ast_test_flag(vmu, VM_FORCENAME)) {
                snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
-               cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+               cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                if (cmd < 0 || cmd == 't' || cmd == '#')
                        return cmd;
        }
@@ -4584,11 +4620,11 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
        /* If forcegreetings is set, have the user record their greetings */
        if (ast_test_flag(vmu, VM_FORCEGREET)) {
                snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
-               cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+               cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                if (cmd < 0 || cmd == 't' || cmd == '#')
                        return cmd;
                snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
-               cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+               cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                if (cmd < 0 || cmd == 't' || cmd == '#')
                        return cmd;
        }
@@ -4596,7 +4632,7 @@ static int vm_newuser(struct ast_channel *chan, struct ast_vm_user *vmu, struct
        return cmd;
 }
 
-static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc)
+static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
 {
        int cmd = 0;
        int retries = 0;
@@ -4622,18 +4658,18 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct
                switch (cmd) {
                case '1':
                        snprintf(prefile,sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, vms->username);
-                       cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+                       cmd = play_record_review(chan,"vm-rec-unv",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                        break;
                case '2': 
                        snprintf(prefile,sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, vms->username);
-                       cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+                       cmd = play_record_review(chan,"vm-rec-busy",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                        break;
                case '3': 
                        snprintf(prefile,sizeof(prefile), "%s%s/%s/greet", VM_SPOOL_DIR, vmu->context, vms->username);
-                       cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+                       cmd = play_record_review(chan,"vm-rec-name",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                        break;
                case '4': 
-                       cmd = vm_tempgreeting(chan, vmu, vms, fmtc);
+                       cmd = vm_tempgreeting(chan, vmu, vms, fmtc, record_gain);
                        break;
                case '5':
                        if (vmu->password[0] == '-') {
@@ -4693,7 +4729,7 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct
        return cmd;
 }
 
-static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc)
+static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain)
 {
        int cmd = 0;
        int retries = 0;
@@ -4718,7 +4754,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
                if (ast_fileexists(prefile, NULL, NULL) > 0) {
                        switch (cmd) {
                        case '1':
-                               cmd = play_record_review(chan,"vm-rec-temp",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+                               cmd = play_record_review(chan,"vm-rec-temp",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                                break;
                        case '2':
                                ast_filedelete(prefile, NULL);
@@ -4742,7 +4778,7 @@ static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, st
                                }
                        }
                } else {
-                       play_record_review(chan,"vm-rec-temp",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL);
+                       play_record_review(chan,"vm-rec-temp",prefile, maxgreet, fmtc, 0, vmu, &duration, NULL, record_gain);
                        cmd = 't';      
                }
        }
@@ -4980,88 +5016,93 @@ static int vm_execmain(struct ast_channel *chan, void *data)
           reason it just seemed a lot easier to do with GOTO's.  I feel
           like I'm back in my GWBASIC days. XXX */
        int res=-1;
-       int valid = 0;
-       int prefix = 0;
        int cmd=0;
+       int valid = 0;
        struct localuser *u;
        char prefixstr[80] ="";
        char ext_context[256]="";
        int box;
        int useadsi = 0;
        int skipuser = 0;
-       char tmp[256], *ext;
-       char fmtc[256] = "";
        struct vm_state vms;
        struct ast_vm_user *vmu = NULL, vmus;
        char *context=NULL;
        int silentexit = 0;
-       char *options;
+       struct ast_flags flags = { 0 };
+       signed char record_gain = 0;
 
        LOCAL_USER_ADD(u);
+
        memset(&vms, 0, sizeof(vms));
        vms.lastmsg = -1;
+
        memset(&vmus, 0, sizeof(vmus));
-       ast_copy_string(fmtc, vmfmts, sizeof(fmtc));
+
        if (chan->_state != AST_STATE_UP)
                ast_answer(chan);
 
        if (data && !ast_strlen_zero(data)) {
-               ast_copy_string(tmp, data, sizeof(tmp));
-               ext = tmp;
-
-               /* option 's': don't request a password */
-               /* option 'p': the supplied name is actually a prefix to be added to the mailbox
-                  number entered by the user */
-               if ((options = strchr(ext, '|'))) {
-                       *options++ = '\0';
-                       while (*options)
-                               switch (*options++) {
-                               case 's':
-                                       valid++;
-                                       break;
-                               case 'p':
-                                       prefix++;
-                                       break;
+               char *tmp;
+               int argc;
+               char *argv[2];
+               char *opts[OPT_ARG_ARRAY_SIZE];
+
+               tmp = ast_strdupa(data);
+               argc = ast_separate_app_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
+               if (argc == 2) {
+                       if (ast_parseoptions(vm_app_options, &flags, opts, argv[1])) {
+                               LOCAL_USER_REMOVE(u);
+                               return -1;
+                       }
+                       if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
+                               int gain;
+
+                               if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
+                                       ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
+                                       LOCAL_USER_REMOVE(u);
+                                       return -1;
+                               } else {
+                                       record_gain = (signed char) gain;
                                }
+                       }
                } else {
                        /* old style options parsing */
-                       while (*ext) {
-                               if (*ext == 's') {
-                                       valid++;
-                                       ext++;
-                               } else if (*ext == 'p') {
-                                       prefix++;
-                                       ext++;
-                               } else
+                       while (*argv[0]) {
+                               if (*argv[0] == 's') {
+                                       ast_set_flag(&flags, OPT_SILENT);
+                                       argv[0]++;
+                               } else if (*argv[0] == 'p') {
+                                       ast_set_flag(&flags, OPT_PREPEND_MAILBOX);
+                                       argv[0]++;
+                               } else 
                                        break;
                        }
-               }
 
-               context = strchr(ext, '@');
-               if (context) {
-                       *context = '\0';
-                       context++;
                }
 
-               if (prefix)
-                       ast_copy_string(prefixstr, ext, sizeof(prefixstr));
+               valid = ast_test_flag(&flags, OPT_SILENT);
+
+               if ((context = strchr(argv[0], '@')))
+                       *context++ = '\0';
+
+               if (ast_test_flag(&flags, OPT_PREPEND_MAILBOX))
+                       ast_copy_string(prefixstr, argv[0], sizeof(prefixstr));
                else
-                       ast_copy_string(vms.username, ext, sizeof(vms.username));
+                       ast_copy_string(vms.username, argv[0], sizeof(vms.username));
+
                if (!ast_strlen_zero(vms.username) && (vmu = find_user(&vmus, context ,vms.username)))
                        skipuser++;
                else
                        valid = 0;
        }
 
-       if (!valid) {
+       if (!valid)
                res = vm_authenticate(chan, vms.username, sizeof(vms.username), &vmus, context, prefixstr, skipuser, maxlogins, 0);
-       }
 
        if (!res) {
                valid = 1;
-               if (!skipuser) {
+               if (!skipuser)
                        vmu = &vmus;
-               }
        } else {
                res = 0;
        }
@@ -5069,323 +5110,318 @@ static int vm_execmain(struct ast_channel *chan, void *data)
        /* If ADSI is supported, setup login screen */
        adsi_begin(chan, &useadsi);
 
-       if (valid) {
-               vms.deleted = calloc(vmu->maxmsg, sizeof(int));
-               vms.heard = calloc(vmu->maxmsg, sizeof(int));
+       if (!valid)
+               goto out;
+
+       vms.deleted = calloc(vmu->maxmsg, sizeof(int));
+       vms.heard = calloc(vmu->maxmsg, sizeof(int));
        
-               /* Set language from config to override channel language */
-               if (vmu->language && !ast_strlen_zero(vmu->language))
-                       ast_copy_string(chan->language, vmu->language, sizeof(chan->language));
-               snprintf(vms.curdir, sizeof(vms.curdir), "%s/%s", VM_SPOOL_DIR, vmu->context);
-               mkdir(vms.curdir, 0700);
-               snprintf(vms.curdir, sizeof(vms.curdir), "%s/%s/%s", VM_SPOOL_DIR, vmu->context, vms.username);
-               mkdir(vms.curdir, 0700);
-               /* Retrieve old and new message counts */
+       /* Set language from config to override channel language */
+       if (vmu->language && !ast_strlen_zero(vmu->language))
+               ast_copy_string(chan->language, vmu->language, sizeof(chan->language));
+       snprintf(vms.curdir, sizeof(vms.curdir), "%s/%s", VM_SPOOL_DIR, vmu->context);
+       mkdir(vms.curdir, 0700);
+       snprintf(vms.curdir, sizeof(vms.curdir), "%s/%s/%s", VM_SPOOL_DIR, vmu->context, vms.username);
+       mkdir(vms.curdir, 0700);
+       /* Retrieve old and new message counts */
+       res = open_mailbox(&vms, vmu, 1);
+       if (res == ERROR_LOCK_PATH)
+               goto out;
+       vms.oldmessages = vms.lastmsg + 1;
+       /* Start in INBOX */
+       res = open_mailbox(&vms, vmu, 0);
+       if (res == ERROR_LOCK_PATH)
+               goto out;
+       vms.newmessages = vms.lastmsg + 1;
+               
+       /* Select proper mailbox FIRST!! */
+       if (!vms.newmessages && vms.oldmessages) {
+               /* If we only have old messages start here */
                res = open_mailbox(&vms, vmu, 1);
                if (res == ERROR_LOCK_PATH)
                        goto out;
-               vms.oldmessages = vms.lastmsg + 1;
-               /* Start in INBOX */
-               res = open_mailbox(&vms, vmu, 0);
-               if (res == ERROR_LOCK_PATH)
-                       goto out;
-               vms.newmessages = vms.lastmsg + 1;
-               
-               /* Select proper mailbox FIRST!! */
-               if (!vms.newmessages && vms.oldmessages) {
-                       /* If we only have old messages start here */
-                       res = open_mailbox(&vms, vmu, 1);
-                       if (res == ERROR_LOCK_PATH)
-                               goto out;
-               }
+       }
 
-               if (useadsi)
-                       adsi_status(chan, &vms);
-               res = 0;
+       if (useadsi)
+               adsi_status(chan, &vms);
+       res = 0;
 
-               /* Check to see if this is a new user */
-               if (!strcasecmp(vmu->mailbox, vmu->password) && 
-                       (ast_test_flag(vmu, VM_FORCENAME | VM_FORCEGREET))) {
-                       if (ast_play_and_wait(chan, "vm-newuser") == -1)
-                               ast_log(LOG_WARNING, "Couldn't stream new user file\n");
-                       cmd = vm_newuser(chan, vmu, &vms, vmfmts);
-                       if ((cmd == 't') || (cmd == '#')) {
-                               /* Timeout */
-                               res = 0;
-                               goto out;
-                       } else if (cmd < 0) {
-                               /* Hangup */
-                               res = -1;
-                               goto out;
-                       }
+       /* Check to see if this is a new user */
+       if (!strcasecmp(vmu->mailbox, vmu->password) && 
+           (ast_test_flag(vmu, VM_FORCENAME | VM_FORCEGREET))) {
+               if (ast_play_and_wait(chan, "vm-newuser") == -1)
+                       ast_log(LOG_WARNING, "Couldn't stream new user file\n");
+               cmd = vm_newuser(chan, vmu, &vms, vmfmts, record_gain);
+               if ((cmd == 't') || (cmd == '#')) {
+                       /* Timeout */
+                       res = 0;
+                       goto out;
+               } else if (cmd < 0) {
+                       /* Hangup */
+                       res = -1;
+                       goto out;
                }
+       }
 
-               cmd = vm_intro(chan, &vms);
-
-               vms.repeats = 0;
-               vms.starting = 1;
-               while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
-                       /* Run main menu */
-                       switch(cmd) {
-                       case '1':
-                               vms.curmsg = 0;
-                               /* Fall through */
-                       case '5':
-                               cmd = vm_browse_messages(chan, &vms, vmu);
-                               break;
-                       case '2': /* Change folders */
-                               if (useadsi)
-                                       adsi_folders(chan, 0, "Change to folder...");
-                               cmd = get_folder2(chan, "vm-changeto", 0);
-                               if (cmd == '#') {
-                                       cmd = 0;
-                               } else if (cmd > 0) {
-                                       cmd = cmd - '0';
-                                       res = close_mailbox(&vms, vmu);
-                                       if (res == ERROR_LOCK_PATH)
-                                               goto out;
-                                       res = open_mailbox(&vms, vmu, cmd);
-                                       if (res == ERROR_LOCK_PATH)
-                                               goto out;
-                                       cmd = 0;
-                               }
-                               if (useadsi)
-                                       adsi_status2(chan, &vms);
-                               
-                               if (!cmd)
-                                       cmd = vm_play_folder_name(chan, vms.vmbox);
+       cmd = vm_intro(chan, &vms);
 
-                               vms.starting = 1;
-                               break;
-                       case '3': /* Advanced options */
+       vms.repeats = 0;
+       vms.starting = 1;
+       while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
+               /* Run main menu */
+               switch(cmd) {
+               case '1':
+                       vms.curmsg = 0;
+                       /* Fall through */
+               case '5':
+                       cmd = vm_browse_messages(chan, &vms, vmu);
+                       break;
+               case '2': /* Change folders */
+                       if (useadsi)
+                               adsi_folders(chan, 0, "Change to folder...");
+                       cmd = get_folder2(chan, "vm-changeto", 0);
+                       if (cmd == '#') {
                                cmd = 0;
-                               vms.repeats = 0;
-                               while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
-                                       switch(cmd) {
-                                       case '1': /* Reply */
-                                               if (vms.lastmsg > -1) {
-                                                       cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 1);
-                                                       if (cmd == ERROR_LOCK_PATH) {
-                                                               res = cmd;
-                                                               goto out;
-                                                       }
-                                               } else
-                                                       cmd = ast_play_and_wait(chan, "vm-sorry");
-                                               cmd = 't';
-                                               break;
-                                       case '2': /* Callback */
-                                               ast_verbose( VERBOSE_PREFIX_3 "Callback Requested\n");
-                                               if (!ast_strlen_zero(vmu->callback) && vms.lastmsg > -1) {
-                                                       cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 2);
-                                                       if (cmd == 9) {
-                                                               silentexit = 1;
-                                                               goto out;
-                                                       } else if (cmd == ERROR_LOCK_PATH) {
-                                                               res = cmd;
-                                                               goto out;
-                                                       }
-                                               }
-                                               else 
-                                                       cmd = ast_play_and_wait(chan, "vm-sorry");
-                                               cmd = 't';
-                                               break;
-                                       case '3': /* Envelope */
-                                               if (vms.lastmsg > -1) {
-                                                       cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 3);
-                                                       if (cmd == ERROR_LOCK_PATH) {
-                                                               res = cmd;
-                                                               goto out;
-                                                       }
-                                               } else
-                                                       cmd = ast_play_and_wait(chan, "vm-sorry");
-                                               cmd = 't';
-                                               break;
-                                       case '4': /* Dialout */
-                                               if (!ast_strlen_zero(vmu->dialout)) {
-                                                       cmd = dialout(chan, vmu, NULL, vmu->dialout);
-                                                       if (cmd == 9) {
-                                                               silentexit = 1;
-                                                               goto out;
-                                                       }
-                                               }
-                                               else 
-                                                       cmd = ast_play_and_wait(chan, "vm-sorry");
-                                               cmd = 't';
-                                               break;
-
-                                       case '5': /* Leave VoiceMail */
-                                               if (ast_test_flag(vmu, VM_SVMAIL)) {
-                                                       cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts,1);
-                                                       if (cmd == ERROR_LOCK_PATH) {
-                                                               res = cmd;
-                                                               goto out;
-                                                       }
-                                               } else
-                                                       cmd = ast_play_and_wait(chan,"vm-sorry");
-                                               cmd='t';
-                                               break;
-                                       
-                                       case '*': /* Return to main menu */
-                                               cmd = 't';
-                                               break;
+                       } else if (cmd > 0) {
+                               cmd = cmd - '0';
+                               res = close_mailbox(&vms, vmu);
+                               if (res == ERROR_LOCK_PATH)
+                                       goto out;
+                               res = open_mailbox(&vms, vmu, cmd);
+                               if (res == ERROR_LOCK_PATH)
+                                       goto out;
+                               cmd = 0;
+                       }
+                       if (useadsi)
+                               adsi_status2(chan, &vms);
+                               
+                       if (!cmd)
+                               cmd = vm_play_folder_name(chan, vms.vmbox);
 
-                                       default:
-                                               cmd = 0;
-                                               if (!vms.starting) {
-                                                       cmd = ast_play_and_wait(chan, "vm-toreply");
+                       vms.starting = 1;
+                       break;
+               case '3': /* Advanced options */
+                       cmd = 0;
+                       vms.repeats = 0;
+                       while ((cmd > -1) && (cmd != 't') && (cmd != '#')) {
+                               switch(cmd) {
+                               case '1': /* Reply */
+                                       if (vms.lastmsg > -1) {
+                                               cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 1, record_gain);
+                                               if (cmd == ERROR_LOCK_PATH) {
+                                                       res = cmd;
+                                                       goto out;
                                                }
-                                               if (!ast_strlen_zero(vmu->callback) && !vms.starting && !cmd) {
-                                                       cmd = ast_play_and_wait(chan, "vm-tocallback");
+                                       } else
+                                               cmd = ast_play_and_wait(chan, "vm-sorry");
+                                       cmd = 't';
+                                       break;
+                               case '2': /* Callback */
+                                       ast_verbose( VERBOSE_PREFIX_3 "Callback Requested\n");
+                                       if (!ast_strlen_zero(vmu->callback) && vms.lastmsg > -1) {
+                                               cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 2, record_gain);
+                                               if (cmd == 9) {
+                                                       silentexit = 1;
+                                                       goto out;
+                                               } else if (cmd == ERROR_LOCK_PATH) {
+                                                       res = cmd;
+                                                       goto out;
                                                }
-
-                                               if (!cmd && !vms.starting) {
-                                                       cmd = ast_play_and_wait(chan, "vm-tohearenv");
+                                       }
+                                       else 
+                                               cmd = ast_play_and_wait(chan, "vm-sorry");
+                                       cmd = 't';
+                                       break;
+                               case '3': /* Envelope */
+                                       if (vms.lastmsg > -1) {
+                                               cmd = advanced_options(chan, vmu, &vms, vms.curmsg, 3, record_gain);
+                                               if (cmd == ERROR_LOCK_PATH) {
+                                                       res = cmd;
+                                                       goto out;
                                                }
-                                               if (!ast_strlen_zero(vmu->dialout) && !cmd) {
-                                                       cmd = ast_play_and_wait(chan, "vm-tomakecall");
+                                       } else
+                                               cmd = ast_play_and_wait(chan, "vm-sorry");
+                                       cmd = 't';
+                                       break;
+                               case '4': /* Dialout */
+                                       if (!ast_strlen_zero(vmu->dialout)) {
+                                               cmd = dialout(chan, vmu, NULL, vmu->dialout);
+                                               if (cmd == 9) {
+                                                       silentexit = 1;
+                                                       goto out;
                                                }
-                                               if (ast_test_flag(vmu, VM_SVMAIL) && !cmd)
-                                                       cmd=ast_play_and_wait(chan, "vm-leavemsg");
-                                               if (!cmd)
-                                                       cmd = ast_play_and_wait(chan, "vm-starmain");
-                                               if (!cmd)
-                                                       cmd = ast_waitfordigit(chan,6000);
-                                               if (!cmd)
-                                                       vms.repeats++;
-                                               if (vms.repeats > 3)
-                                                       cmd = 't';
                                        }
-                               }
-                               if (cmd == 't') {
-                                       cmd = 0;
-                                       vms.repeats = 0;
-                               }
-                               break;
-
-
-
+                                       else 
+                                               cmd = ast_play_and_wait(chan, "vm-sorry");
+                                       cmd = 't';
+                                       break;
 
+                               case '5': /* Leave VoiceMail */
+                                       if (ast_test_flag(vmu, VM_SVMAIL)) {
+                                               cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 1, record_gain);
+                                               if (cmd == ERROR_LOCK_PATH) {
+                                                       res = cmd;
+                                                       goto out;
+                                               }
+                                       } else
+                                               cmd = ast_play_and_wait(chan,"vm-sorry");
+                                       cmd='t';
+                                       break;
+                                       
+                               case '*': /* Return to main menu */
+                                       cmd = 't';
+                                       break;
 
-                       case '4':
-                               if (vms.curmsg) {
-                                       vms.curmsg--;
-                                       cmd = play_message(chan, vmu, &vms);
-                               } else {
-                                       cmd = ast_play_and_wait(chan, "vm-nomore");
+                               default:
+                                       cmd = 0;
+                                       if (!vms.starting) {
+                                               cmd = ast_play_and_wait(chan, "vm-toreply");
+                                       }
+                                       if (!ast_strlen_zero(vmu->callback) && !vms.starting && !cmd) {
+                                               cmd = ast_play_and_wait(chan, "vm-tocallback");
+                                       }
+                                       if (!cmd && !vms.starting) {
+                                               cmd = ast_play_and_wait(chan, "vm-tohearenv");
+                                       }
+                                       if (!ast_strlen_zero(vmu->dialout) && !cmd) {
+                                               cmd = ast_play_and_wait(chan, "vm-tomakecall");
+                                       }
+                                       if (ast_test_flag(vmu, VM_SVMAIL) && !cmd)
+                                               cmd=ast_play_and_wait(chan, "vm-leavemsg");
+                                       if (!cmd)
+                                               cmd = ast_play_and_wait(chan, "vm-starmain");
+                                       if (!cmd)
+                                               cmd = ast_waitfordigit(chan,6000);
+                                       if (!cmd)
+                                               vms.repeats++;
+                                       if (vms.repeats > 3)
+                                               cmd = 't';
                                }
-                               break;
-                       case '6':
+                       }
+                       if (cmd == 't') {
+                               cmd = 0;
+                               vms.repeats = 0;
+                       }
+                       break;
+               case '4':
+                       if (vms.curmsg) {
+                               vms.curmsg--;
+                               cmd = play_message(chan, vmu, &vms);
+                       } else {
+                               cmd = ast_play_and_wait(chan, "vm-nomore");
+                       }
+                       break;
+               case '6':
+                       if (vms.curmsg < vms.lastmsg) {
+                               vms.curmsg++;
+                               cmd = play_message(chan, vmu, &vms);
+                       } else {
+                               cmd = ast_play_and_wait(chan, "vm-nomore");
+                       }
+                       break;
+               case '7':
+                       vms.deleted[vms.curmsg] = !vms.deleted[vms.curmsg];
+                       if (useadsi)
+                               adsi_delete(chan, &vms);
+                       if (vms.deleted[vms.curmsg]) 
+                               cmd = ast_play_and_wait(chan, "vm-deleted");
+                       else
+                               cmd = ast_play_and_wait(chan, "vm-undeleted");
+                       if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
                                if (vms.curmsg < vms.lastmsg) {
                                        vms.curmsg++;
                                        cmd = play_message(chan, vmu, &vms);
                                } else {
                                        cmd = ast_play_and_wait(chan, "vm-nomore");
                                }
-                               break;
-                       case '7':
-                               vms.deleted[vms.curmsg] = !vms.deleted[vms.curmsg];
-                               if (useadsi)
-                                       adsi_delete(chan, &vms);
-                               if (vms.deleted[vms.curmsg]) 
-                                       cmd = ast_play_and_wait(chan, "vm-deleted");
-                               else
-                                       cmd = ast_play_and_wait(chan, "vm-undeleted");
-                               if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
-                                       if (vms.curmsg < vms.lastmsg) {
-                                               vms.curmsg++;
-                                               cmd = play_message(chan, vmu, &vms);
-                                       } else {
-                                               cmd = ast_play_and_wait(chan, "vm-nomore");
-                                       }
-                               }
-                               break;
+                       }
+                       break;
        
-                       case '8':
-                               if (vms.lastmsg > -1) {
-                                       cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts,0);
-                                       if (cmd == ERROR_LOCK_PATH) {
-                                               res = cmd;
-                                               goto out;
-                                       }
-                               } else
-                                       cmd = ast_play_and_wait(chan, "vm-nomore");
-                               break;
-                       case '9':
-                               if (useadsi)
-                                       adsi_folders(chan, 1, "Save to folder...");
-                               cmd = get_folder2(chan, "vm-savefolder", 1);
-                               box = 0;        /* Shut up compiler */
-                               if (cmd == '#') {
-                                       cmd = 0;
-                                       break;
-                               } else if (cmd > 0) {
-                                       box = cmd = cmd - '0';
-                                       cmd = save_to_folder(vmu, vms.curdir, vms.curmsg, vmu->context, vms.username, cmd);
-                                       if (cmd == ERROR_LOCK_PATH) {
-                                               res = cmd;
-                                               goto out;
-                                       } else if (!cmd) {
-                                               vms.deleted[vms.curmsg] = 1;
-                                       } else {
-                                               vms.deleted[vms.curmsg] = 0;
-                                               vms.heard[vms.curmsg] = 0;
-                                       }
+               case '8':
+                       if (vms.lastmsg > -1) {
+                               cmd = forward_message(chan, context, vms.curdir, vms.curmsg, vmu, vmfmts, 0, record_gain);
+                               if (cmd == ERROR_LOCK_PATH) {
+                                       res = cmd;
+                                       goto out;
                                }
-                               make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg);
-                               if (useadsi)
-                                       adsi_message(chan, &vms);
-                               snprintf(vms.fn, sizeof(vms.fn), "vm-%s", mbox(box));
-                               if (!cmd) {
-                                       cmd = ast_play_and_wait(chan, "vm-message");
-                                       if (!cmd)
-                                               cmd = say_and_wait(chan, vms.curmsg + 1, chan->language);
-                                       if (!cmd)
-                                               cmd = ast_play_and_wait(chan, "vm-savedto");
-                                       if (!cmd)
-                                               cmd = vm_play_folder_name(chan, vms.fn);
+                       } else
+                               cmd = ast_play_and_wait(chan, "vm-nomore");
+                       break;
+               case '9':
+                       if (useadsi)
+                               adsi_folders(chan, 1, "Save to folder...");
+                       cmd = get_folder2(chan, "vm-savefolder", 1);
+                       box = 0;        /* Shut up compiler */
+                       if (cmd == '#') {
+                               cmd = 0;
+                               break;
+                       } else if (cmd > 0) {
+                               box = cmd = cmd - '0';
+                               cmd = save_to_folder(vmu, vms.curdir, vms.curmsg, vmu->context, vms.username, cmd);
+                               if (cmd == ERROR_LOCK_PATH) {
+                                       res = cmd;
+                                       goto out;
+                               } else if (!cmd) {
+                                       vms.deleted[vms.curmsg] = 1;
                                } else {
-                                       cmd = ast_play_and_wait(chan, "vm-mailboxfull");
+                                       vms.deleted[vms.curmsg] = 0;
+                                       vms.heard[vms.curmsg] = 0;
                                }
-                               if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
-                                       if (vms.curmsg < vms.lastmsg) {
-                                               vms.curmsg++;
-                                               cmd = play_message(chan, vmu, &vms);
-                                       } else {
-                                               cmd = ast_play_and_wait(chan, "vm-nomore");
-                                       }
+                       }
+                       make_file(vms.fn, sizeof(vms.fn), vms.curdir, vms.curmsg);
+                       if (useadsi)
+                               adsi_message(chan, &vms);
+                       snprintf(vms.fn, sizeof(vms.fn), "vm-%s", mbox(box));
+                       if (!cmd) {
+                               cmd = ast_play_and_wait(chan, "vm-message");
+                               if (!cmd)
+                                       cmd = say_and_wait(chan, vms.curmsg + 1, chan->language);
+                               if (!cmd)
+                                       cmd = ast_play_and_wait(chan, "vm-savedto");
+                               if (!cmd)
+                                       cmd = vm_play_folder_name(chan, vms.fn);
+                       } else {
+                               cmd = ast_play_and_wait(chan, "vm-mailboxfull");
+                       }
+                       if (ast_test_flag((&globalflags), VM_SKIPAFTERCMD)) {
+                               if (vms.curmsg < vms.lastmsg) {
+                                       vms.curmsg++;
+                                       cmd = play_message(chan, vmu, &vms);
+                               } else {
+                                       cmd = ast_play_and_wait(chan, "vm-nomore");
                                }
-                               break;
-
-                       case '*':
-                               if (!vms.starting) {
-                                       cmd = ast_play_and_wait(chan, "vm-onefor");
-                                       if (!cmd)
-                                               cmd = vm_play_folder_name(chan, vms.vmbox);
-                                       if (!cmd)
-                                               cmd = ast_play_and_wait(chan, "vm-opts");
-                                       if (!cmd)
-                                               cmd = vm_instructions(chan, &vms, 1);
-                               } else
-                                       cmd = 0;
-                               break;
-                       case '0':
-                               cmd = vm_options(chan, vmu, &vms, vmfmts);
-                               if (useadsi)
-                                       adsi_status(chan, &vms);
-                               break;
-                       default:        /* Nothing */
-                               cmd = vm_instructions(chan, &vms, 0);
-                               break;
                        }
+                       break;
+               case '*':
+                       if (!vms.starting) {
+                               cmd = ast_play_and_wait(chan, "vm-onefor");
+                               if (!cmd)
+                                       cmd = vm_play_folder_name(chan, vms.vmbox);
+                               if (!cmd)
+                                       cmd = ast_play_and_wait(chan, "vm-opts");
+                               if (!cmd)
+                                       cmd = vm_instructions(chan, &vms, 1);
+                       } else
+                               cmd = 0;
+                       break;
+               case '0':
+                       cmd = vm_options(chan, vmu, &vms, vmfmts, record_gain);
+                       if (useadsi)
+                               adsi_status(chan, &vms);
+                       break;
+               default:        /* Nothing */
+                       cmd = vm_instructions(chan, &vms, 0);
+                       break;
                }
-               if ((cmd == 't') || (cmd == '#')) {
-                       /* Timeout */
-                       res = 0;
-               } else {
-                       /* Hangup */
-                       res = -1;
-               }
        }
+       if ((cmd == 't') || (cmd == '#')) {
+               /* Timeout */
+               res = 0;
+       } else {
+               /* Hangup */
+               res = -1;
+       }
+
 out:
        if (res > -1) {
                ast_stopstream(chan);
@@ -5421,17 +5457,59 @@ out:
 
 static int vm_exec(struct ast_channel *chan, void *data)
 {
-       int res=0, silent=0, busy=0, unavail=0;
+       int res = 0;
        struct localuser *u;
        char tmp[256];
-       char *ext, *options;
+       struct leave_vm_options leave_options;
+       int argc;
+       char *argv[2];
+       struct ast_flags flags = { 0 };
+       char *opts[OPT_ARG_ARRAY_SIZE];
        
+       memset(&leave_options, 0, sizeof(leave_options));
+
        LOCAL_USER_ADD(u);
+
        if (chan->_state != AST_STATE_UP)
                ast_answer(chan);
-       if (data && !ast_strlen_zero(data))
+
+       if (data && !ast_strlen_zero(data)) {
                ast_copy_string(tmp, data, sizeof(tmp));
-       else {
+               argc = ast_separate_app_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
+               if (argc == 2) {
+                       if (ast_parseoptions(vm_app_options, &flags, opts, argv[1])) {
+                               LOCAL_USER_REMOVE(u);
+                               return -1;
+                       }
+                       ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING);
+                       if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
+                               int gain;
+
+                               if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
+                                       ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
+                                       LOCAL_USER_REMOVE(u);
+                                       return -1;
+                               } else {
+                                       leave_options.record_gain = (signed char) gain;
+                               }
+                       }
+               } else {
+                       /* old style options parsing */
+                       while (*argv[0]) {
+                               if (*argv[0] == 's') {
+                                       ast_set_flag(&leave_options, OPT_SILENT);
+                                       argv[0]++;
+                               } else if (*argv[0] == 'b') {
+                                       ast_set_flag(&leave_options, OPT_BUSY_GREETING);
+                                       argv[0]++;
+                               } else if (*argv[0] == 'u') {
+                                       ast_set_flag(&leave_options, OPT_UNAVAIL_GREETING);
+                                       argv[0]++;
+                               } else 
+                                       break;
+                       }
+               }
+       } else {
                res = ast_app_getdata(chan, "vm-whichbox", tmp, sizeof(tmp) - 1, 0);
                if (res < 0)
                        return res;
@@ -5439,41 +5517,8 @@ static int vm_exec(struct ast_channel *chan, void *data)
                        return 0;
        }
 
-       ext = tmp;      
-       /* option 's': skip intro message with instructions */
-       /* option 'b': play 'busy' greeting */
-       /* option 'u': play 'unavailable' greeting */
-       if ((options = strchr(ext, '|'))) {
-               *options++ = '\0';
-               while (*options)
-                       switch (*options++) {
-                       case 's':
-                               silent = 2;
-                               break;
-                       case 'b':
-                               busy = 1;
-                               break;
-                       case 'u':
-                               unavail = 1;
-                               break;
-                       }
-       } else {
-               /* old style options parsing */
-               while (*ext) {
-                       if (*ext == 's') {
-                               silent = 2;
-                               ext++;
-                       } else if (*ext == 'b') {
-                               busy = 1;
-                               ext++;
-                       } else if (*ext == 'u') {
-                               unavail = 1;
-                               ext++;
-                       } else 
-                               break;
-               }
-       }       
-       res = leave_voicemail(chan, ext, silent, busy, unavail);
+       res = leave_voicemail(chan, argv[0], &leave_options);
+
        LOCAL_USER_REMOVE(u);
        
        if (res == ERROR_LOCK_PATH) {
@@ -6260,7 +6305,8 @@ static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num,
        return 0;
 }
 
-static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option)
+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)
 {
        int res = 0;
        char filename[256],*origtime, *cid, *context, *name, *num;
@@ -6388,8 +6434,13 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
                                return res;
                        } else {
                                if (find_user(NULL, vmu->context, num)) {
+                                       struct leave_vm_options leave_options;
+
                                        ast_verbose(VERBOSE_PREFIX_3 "Leaving voicemail for '%s' in context '%s'\n", num, vmu->context);
-                                       res = leave_voicemail(chan, num, 1, 0, 1);
+                                       
+                                       memset(&leave_options, 0, sizeof(leave_options));
+                                       leave_options.record_gain = record_gain;
+                                       res = leave_voicemail(chan, num, &leave_options);
                                        if (!res)
                                                res = 't';
                                        return res;
@@ -6414,15 +6465,10 @@ static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, s
        }
        return res;
 }
-
-
-
-
-
-
  
-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, const char *unlockdir)
+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, const char *unlockdir,
+                             signed char record_gain)
 {
        /* Record message & let caller review or re-record it, or set options if applicable */
        int res = 0;
@@ -6431,6 +6477,7 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
        int attempts = 0;
        int recorded = 0;
        int message_exists = 0;
+       signed char zero_gain = 0;
        /* Note that urgent and private are for flagging messages as such in the future */
  
        /* barf if no pointer passed to store duration in */
@@ -6477,7 +6524,11 @@ static int play_record_review(struct ast_channel *chan, char *playfile, char *re
                        }
                        recorded = 1;
                        /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */
+                       if (record_gain)
+                               ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
                        cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, silencethreshold, maxsilence, unlockdir);
+                       if (record_gain)
+                               ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
                        if (cmd == -1) {
                        /* User has hung up, no options to give */
                                return cmd;