Merge Vonage's MySQL Voicemail stuff
[asterisk/asterisk.git] / apps / app_voicemail2.c
index 1a46481..50cbd02 100755 (executable)
@@ -33,6 +33,9 @@
 #include <sys/time.h>
 #include <sys/stat.h>
 #include <time.h>
+#ifdef USEMYSQLVM
+#include <mysql/mysql.h>
+#endif
 
 #include <pthread.h>
 #include "../asterisk.h"
@@ -136,23 +139,134 @@ static char vmfmts[80];
 static int vmmaxmessage;
 static int maxgreet;
 static int skipms;
+static int maxlogins;
 
 STANDARD_LOCAL_USER;
 
 LOCAL_USER_DECL;
 
-static int make_dir(char *dest, int len, char *context, char *ext, char *mailbox)
+#ifdef USEMYSQLVM
+MYSQL *dbhandler=NULL;
+pthread_mutex_t mysqllock;
+char dbuser[80];
+char dbpass[80];
+char dbname[80];
+
+static int mysql_login(void)
 {
-       return snprintf(dest, len, "%s/voicemail/%s/%s/%s", (char *)ast_config_AST_SPOOL_DIR,context, ext, mailbox);
+       ast_verbose( VERBOSE_PREFIX_3 "Logging into database with user %s, password %s, and database %s\n", dbuser, dbpass, dbname);
+
+       dbhandler=mysql_init(NULL);
+       if (!mysql_real_connect(dbhandler, NULL, dbuser, dbpass, dbname, 0, NULL, 0)) {
+               ast_log(LOG_WARNING, "Error Logging into database\n");
+               return(-1);
+       }
+       pthread_mutex_init(&mysqllock, NULL);
+       return(0);
 }
 
-static int make_file(char *dest, int len, char *dir, int num)
+static void mysql_logout(void)
 {
-       return snprintf(dest, len, "%s/msg%04d", dir, num);
+       mysql_close(dbhandler);
 }
 
 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, char *context, char *mailbox)
 {
+       MYSQL_RES *result;
+       MYSQL_ROW rowval;
+       MYSQL_FIELD *fields;
+       int numFields, i;
+       char query[240];
+       struct ast_vm_user *retval;
+
+       retval=malloc(sizeof(struct ast_vm_user));
+
+       *retval->mailbox='\0';
+       *retval->context='\0';
+       *retval->password='\0';
+       *retval->fullname='\0';
+       *retval->email='\0';
+       *retval->pager='\0';
+       retval->alloced=1;
+       retval->next=NULL;
+       if (mailbox) {
+               strcpy(retval->mailbox, mailbox);
+       }
+       if (context) {
+               strcpy(retval->context, context);
+       }
+
+       if (*retval->context) {
+               sprintf(query, "SELECT password,fullname,email,pager FROM users WHERE context='%s' AND mailbox='%s'", context, mailbox);
+       } else {
+               sprintf(query, "SELECT password,fullname,email,pager FROM users WHERE mailbox='%s'", mailbox);
+       }
+       pthread_mutex_lock(&mysqllock);
+       mysql_query(dbhandler, query);
+       if ((result=mysql_store_result(dbhandler))!=NULL) {
+               if ((rowval=mysql_fetch_row(result))!=NULL) {
+                       numFields=mysql_num_fields(result);
+                       fields=mysql_fetch_fields(result);
+                       for (i=0; i<numFields; i++) {
+                               if (rowval[i]) {
+                                       if (!strcmp(fields[i].name, "password")) {
+                                               strcpy(retval->password, rowval[i]);
+                                       } else if (!strcmp(fields[i].name, "fullname")) {
+                                               strcpy(retval->fullname, rowval[i]);
+                                       } else if (!strcmp(fields[i].name, "email")) {
+                                               strcpy(retval->email, rowval[i]);
+                                       } else if (!strcmp(fields[i].name, "pager")) {
+                                               strcpy(retval->pager, rowval[i]);
+                                       }
+                               }
+                       }
+                       mysql_free_result(result);
+                       pthread_mutex_unlock(&mysqllock);
+                       return(retval);
+               } else {
+                       mysql_free_result(result);
+                       pthread_mutex_unlock(&mysqllock);
+                       free(retval);
+                       return(NULL);
+               }
+       }
+       pthread_mutex_unlock(&mysqllock);
+       free(retval);
+       return(NULL);
+}
+
+static void vm_change_password(struct ast_vm_user *vmu, char *password)
+{
+       char query[400];
+
+       if (*vmu->context) {
+               sprintf(query, "UPDATE users SET password='%s' WHERE context='%s' AND mailbox='%s' AND password='%s'", password, vmu->context, vmu->mailbox, vmu->password);
+       } else {
+               sprintf(query, "UPDATE users SET password='%s' WHERE mailbox='%s' AND password='%s'", password, vmu->mailbox, vmu->password);
+       }
+       pthread_mutex_lock(&mysqllock);
+       mysql_query(dbhandler, query);
+       strcpy(vmu->password, password);
+       pthread_mutex_unlock(&mysqllock);
+}
+
+static void reset_user_pw(char *context, char *mailbox, char *password)
+{
+       char query[320];
+
+       if (context) {
+               sprintf(query, "UPDATE users SET password='%s' WHERE context='%s' AND mailbox='%s'", password, context, mailbox);
+       } else {
+               sprintf(query, "UPDATE users SET password='%s' WHERE mailbox='%s'", password, mailbox);
+       }
+       pthread_mutex_lock(&mysqllock);
+       mysql_query(dbhandler, query);
+       pthread_mutex_unlock(&mysqllock);
+}
+#else
+
+static struct ast_vm_user *find_user(struct ast_vm_user *ivm, char *context, char *mailbox)
+{
        /* This function could be made to generate one from a database, too */
        struct ast_vm_user *vmu=NULL, *cur;
        ast_pthread_mutex_lock(&vmlock);
@@ -182,7 +296,28 @@ static struct ast_vm_user *find_user(struct ast_vm_user *ivm, char *context, cha
        return vmu;
 }
 
-static int vm_change_password(struct ast_vm_user *vmu, char *newpassword)
+static int reset_user_pw(char *context, char *mailbox, char *newpass)
+{
+       /* This function could be made to generate one from a database, too */
+       struct ast_vm_user *cur;
+       int res = -1;
+       ast_pthread_mutex_lock(&vmlock);
+       cur = users;
+       while(cur) {
+               if ((!context || !strcasecmp(context, cur->context)) &&
+                       (!strcasecmp(mailbox, cur->mailbox)))
+                               break;
+               cur=cur->next;
+       }
+       if (cur) {
+               strncpy(cur->password, newpass, sizeof(cur->password) - 1);
+               res = 0;
+       }
+       ast_pthread_mutex_unlock(&vmlock);
+       return res;
+}
+
+static void vm_change_password(struct ast_vm_user *vmu, char *newpassword)
 {
         /*  There's probably a better way of doing this. */
         /*  That's why I've put the password change in a separate function. */
@@ -256,7 +391,19 @@ static int vm_change_password(struct ast_vm_user *vmu, char *newpassword)
 
         unlink((char *)tmpin);
         rename((char *)tmpout,(char *)tmpin);
-       return(1);
+       reset_user_pw(vmu->context, vmu->mailbox, newpassword);
+       strncpy(vmu->password, newpassword, sizeof(vmu->password) - 1);
+}
+#endif
+
+static int make_dir(char *dest, int len, char *context, char *ext, char *mailbox)
+{
+       return snprintf(dest, len, "%s/voicemail/%s/%s/%s", (char *)ast_config_AST_SPOOL_DIR,context, ext, mailbox);
+}
+
+static int make_file(char *dest, int len, char *dir, int num)
+{
+       return snprintf(dest, len, "%s/msg%04d", dir, num);
 }
 
 static int
@@ -392,6 +539,8 @@ static int sendmail(char *srcemail, char *email, char *name, int msgnum, char *m
        char dur[256];
        time_t t;
        struct tm tm;
+       if (!strcmp(format, "wav49"))
+               format = "WAV";
        p = popen(SENDMAIL, "w");
        if (p) {
                gethostname(host, sizeof(host));
@@ -418,13 +567,13 @@ static int sendmail(char *srcemail, char *email, char *name, int msgnum, char *m
 
                        fprintf(p, "--%s\n", bound);
                }
-                       fprintf(p, "Content-Type: TEXT/PLAIN; charset=US-ASCII\n\n");
-                       strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
-                       fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message (number %d)\n"
+               fprintf(p, "Content-Type: TEXT/PLAIN; charset=US-ASCII\n\n");
+               strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
+               fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message (number %d)\n"
 
-                          "in mailbox %s from %s, on %s so you might\n"
-                                  "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", name, 
-                               dur, msgnum, mailbox, (callerid ? callerid : "an unknown caller"), date);
+                       "in mailbox %s from %s, on %s so you might\n"
+                       "want to check it when you get a chance.  Thanks!\n\n\t\t\t\t--Asterisk\n\n", name, 
+                       dur, msgnum, mailbox, (callerid ? callerid : "an unknown caller"), date);
                if (attach_voicemail) {
                        fprintf(p, "--%s\n", bound);
                        fprintf(p, "Content-Type: audio/x-wav; name=\"msg%04d.%s\"\n", msgnum, format);
@@ -543,7 +692,11 @@ static int play_and_record(struct ast_channel *chan, char *playfile, char *recor
        char *sfmt[MAX_OTHER_FORMATS];
        char *stringp=NULL;
        time_t start, end;
-       
+       struct ast_dsp *sildet;         /* silence detector dsp */
+       int totalsilence = 0;
+       int dspsilence = 0;
+       int gotsilence = 0;             /* did we timeout for silence? */
+       int rfmt=0;     
        
        ast_log(LOG_DEBUG,"play_and_record: %s, %s, '%s'\n", playfile ? playfile : "<None>", recordfile, fmt);
        snprintf(comment,sizeof(comment),"Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", recordfile, chan->name);
@@ -577,12 +730,29 @@ static int play_and_record(struct ast_channel *chan, char *playfile, char *recor
                time(&start);
        for (x=0;x<fmtcnt;x++) {
                others[x] = ast_writefile(recordfile, sfmt[x], comment, O_TRUNC, 0, 0700);
-               ast_verbose( VERBOSE_PREFIX_3 "x=%i, open writing:  %s format: %s\n", x, recordfile, sfmt[x]);
+               ast_verbose( VERBOSE_PREFIX_3 "x=%i, open writing:  %s format: %s, %p\n", x, recordfile, sfmt[x], others[x]);
                        
                if (!others[x]) {
                        break;
                }
        }
+       
+       sildet = ast_dsp_new(); //Create the silence detector
+       if (!sildet) {
+               ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
+               return -1;
+       }
+       ast_dsp_set_threshold(sildet, 50);
+       
+       if (maxsilence > 0) {
+               rfmt = chan->readformat;
+               res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
+               if (res < 0) {
+                       ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
+                       return -1;
+               }
+       }
+                                               
        if (x == fmtcnt) {
        /* Loop forever, writing the packets we read to the writer(s), until
           we read a # or get a hangup */
@@ -611,6 +781,24 @@ static int play_and_record(struct ast_channel *chan, char *playfile, char *recor
                                for (x=0;x<fmtcnt;x++) {
                                        res = ast_writestream(others[x], f);
                                }
+                               
+                               /* Silence Detection */
+                               if (maxsilence > 0) {
+                                       dspsilence = 0;
+                                       ast_dsp_silence(sildet, f, &dspsilence);
+                                       if (dspsilence)
+                                               totalsilence = dspsilence;
+                                       else
+                                               totalsilence = 0;
+                                       
+                                       if (totalsilence > maxsilence) {
+                                       /* Ended happily with silence */
+                                       ast_frfree(f);
+                                       gotsilence = 1;
+                                       outmsg=2;
+                                       break;
+                                       }
+                               }
                                /* Exit on any error */
                                if (res) {
                                        ast_log(LOG_WARNING, "Error writing frame\n");
@@ -652,8 +840,18 @@ static int play_and_record(struct ast_channel *chan, char *playfile, char *recor
        for (x=0;x<fmtcnt;x++) {
                if (!others[x])
                        break;
+               if (totalsilence)
+                       ast_stream_rewind(others[x], totalsilence-200);
+               else
+                       ast_stream_rewind(others[x], 1000);
+               ast_truncstream(others[x]);
                ast_closestream(others[x]);
        }
+       if (rfmt) {
+               if (ast_set_read_format(chan, rfmt)) {
+                       ast_log(LOG_WARNING, "Unable to restore format %d to channel '%s'\n", rfmt, chan->name);
+               }
+       }
        if (outmsg) {
                if (outmsg > 1) {
                /* Let them know it worked */
@@ -679,7 +877,6 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
        FILE *txt;
        int res = 0;
        int msgnum;
-       int maxmessage=0;
        char date[256];
        char dir[256];
        char fn[256];
@@ -690,14 +887,6 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
        char *stringp;
        time_t start;
        time_t end;
-#if 0
-       /* XXX Need to be moved to play_and_record */
-       struct ast_dsp *sildet;         /* silence detector dsp */
-       int totalsilence = 0;
-       int dspsilence = 0;
-       int gotsilence = 0;             /* did we timeout for silence? */
-#endif 
-
        char tmp[256] = "";
        struct ast_vm_user *vmu;
        struct ast_vm_user svm;
@@ -767,7 +956,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
                        free_user(vmu);
                        return 0;
                }
-               if (!res && (silent < 2)) {
+               if (res >= 0) {
                        /* Unless we're *really* silent, try to send the beep */
                        res = ast_streamfile(chan, "beep", chan->language);
                        if (!res)
@@ -820,7 +1009,9 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
                                        fclose(txt);
                                } else
                                        ast_log(LOG_WARNING, "Error opening text file for output\n");
-                               res = play_and_record(chan, NULL, fn, maxmessage, fmt);
+                               res = play_and_record(chan, NULL, fn, vmmaxmessage, fmt);
+                               if (res > 0)
+                                       res = 0;
                                txt = fopen(txtfile, "a");
                                if (txt) {
                                        time(&end);
@@ -837,24 +1028,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, int silent, int
                        } else
                                ast_log(LOG_WARNING, "No more messages possible\n");
                } else
-                       ast_log(LOG_WARNING, "No format for saving voicemail?\n");
-       
-#if 0
-                                               sildet = ast_dsp_new(); //Create the silence detector
-                                               if (silence > 0) {
-                                                       rfmt = chan->readformat;
-                                                       res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
-                                                       if (res < 0) {
-                                                               ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
-                                                               return -1;
-                                                       }
-                                                       if (!sildet) {
-                                                               ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
-                                                               return -1;
-                                                       }
-                                                       ast_dsp_set_threshold(sildet, 50);
-                                               }
-#endif                                         
+                       ast_log(LOG_WARNING, "No format for saving voicemail?\n");                                      
                free_user(vmu);
        } else
                ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
@@ -1474,7 +1648,7 @@ static int get_folder2(struct ast_channel *chan, char *fn, int start)
        int res = 0;
        res = play_and_wait(chan, fn);
        while (((res < '0') || (res > '9')) &&
-                       (res != '#') && (res > 0)) {
+                       (res != '#') && (res >= 0)) {
                res = get_folder(chan, 0);
        }
        return res;
@@ -1797,11 +1971,8 @@ static int vm_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct
                                cmd = play_and_wait(chan, "vm-mismatch");
                                break;
                        }
-                       if (vm_change_password(vmu,newpassword) < 0)
-                       {
-                               ast_log(LOG_DEBUG,"Failed to set new password of user %s\n",vms->username);
-                       } else
-                ast_log(LOG_DEBUG,"User %s set password to %s of length %i\n",vms->username,newpassword,strlen(newpassword));
+                       vm_change_password(vmu,newpassword);
+                       ast_log(LOG_DEBUG,"User %s set password to %s of length %i\n",vms->username,newpassword,strlen(newpassword));
                        cmd = play_and_wait(chan,"vm-passchanged");
                        break;
                case '*': 
@@ -1841,6 +2012,7 @@ static int vm_execmain(struct ast_channel *chan, void *data)
        char fmtc[256] = "";
        char password[80];
        struct vm_state vms;
+       int logretries = 0;
        struct ast_vm_user *vmu = NULL, vmus;
        char *context=NULL;
 
@@ -1895,7 +2067,7 @@ static int vm_execmain(struct ast_channel *chan, void *data)
        
        /* Authenticate them and get their mailbox/password */
        
-       while (!valid) {
+       while (!valid && (logretries < maxlogins)) {
                /* Prompt for, and read in the username */
                if (!skipuser && ast_readstring(chan, vms.username, sizeof(vms.username) - 1, 2000, 10000, "#") < 0) {
                        ast_log(LOG_WARNING, "Couldn't read username\n");
@@ -1939,6 +2111,13 @@ static int vm_execmain(struct ast_channel *chan, void *data)
                        if (ast_streamfile(chan, "vm-incorrect", chan->language))
                                break;
                }
+               logretries++;
+       }
+       if (!valid && (logretries >= maxlogins)) {
+               ast_stopstream(chan);
+               res = play_and_wait(chan, "vm-goodbye");
+               if (res > 0)
+                       res = 0;
        }
 
        if (valid) {
@@ -1990,6 +2169,7 @@ static int vm_execmain(struct ast_channel *chan, void *data)
                        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) {
@@ -2079,19 +2259,12 @@ static int vm_execmain(struct ast_channel *chan, void *data)
                        case '0':
                                cmd = vm_options(chan, vmu, &vms, vmfmts);
                                break;
-                       case '#':
-                               ast_stopstream(chan);
-                               adsi_goodbye(chan);
-                               cmd = play_and_wait(chan, "vm-goodbye");
-                               if (cmd > 0)
-                                       cmd = '#';
-                               break;
                        default:        /* Nothing */
                                cmd = vm_instructions(chan, &vms);
                                break;
                        }
                }
-               if (cmd == 't') {
+               if ((cmd == 't') || (cmd == '#')) {
                        /* Timeout */
                        res = 0;
                } else {
@@ -2100,9 +2273,12 @@ static int vm_execmain(struct ast_channel *chan, void *data)
                }
        }
 out:
-       if ((res > -1) && (cmd != '#')) {
+       if (res > -1) {
                ast_stopstream(chan);
                adsi_goodbye(chan);
+               res = play_and_wait(chan, "vm-goodbye");
+               if (res > 0)
+                       res = 0;
                if (useadsi)
                        adsi_unload_session(chan);
        }
@@ -2187,7 +2363,7 @@ static int append_mailbox(char *context, char *mbox, char *data)
        return 0;
 }
 
-static int load_users(void)
+static int load_config(void)
 {
        struct ast_vm_user *cur, *l;
        struct ast_config *cfg;
@@ -2200,6 +2376,7 @@ static int load_users(void)
        char *astemail;
        char *s;
        int x;
+
        cfg = ast_load(VOICEMAIL_CONFIG);
        ast_pthread_mutex_lock(&vmlock);
        cur = users;
@@ -2261,6 +2438,32 @@ static int load_users(void)
                        }
                }
 
+               maxlogins = 3;
+               if ((s = ast_variable_retrieve(cfg, "general", "maxlogins"))) {
+                       if (sscanf(s, "%d", &x) == 1) {
+                               maxlogins = x;
+                       } else {
+                               ast_log(LOG_WARNING, "Invalid max failed login attempts\n");
+                       }
+               }
+
+#ifdef USEMYSQLVM
+               if (!(s=ast_variable_retrieve(cfg, "general", "dbuser"))) {
+                       strcpy(dbuser, "test");
+               } else {
+                       strcpy(dbuser, s);
+               }
+               if (!(s=ast_variable_retrieve(cfg, "general", "dbpass"))) {
+                       strcpy(dbpass, "test");
+               } else {
+                       strcpy(dbpass, s);
+               }
+               if (!(s=ast_variable_retrieve(cfg, "general", "dbname"))) {
+                       strcpy(dbname, "vmdb");
+               } else {
+                       strcpy(dbname, s);
+               }
+#else
                cat = ast_category_browse(cfg, NULL);
                while(cat) {
                        if (strcasecmp(cat, "general")) {
@@ -2273,16 +2476,20 @@ static int load_users(void)
                        }
                        cat = ast_category_browse(cfg, cat);
                }
+#endif
                ast_destroy(cfg);
+               ast_pthread_mutex_unlock(&vmlock);
+               return 0;
+       } else {
+               ast_pthread_mutex_unlock(&vmlock);
+               ast_log(LOG_WARNING, "Error reading voicemail config\n");
+               return -1;
        }
-       ast_pthread_mutex_unlock(&vmlock);
-       return 0;
 }
 
 int reload(void)
 {
-       load_users();
-       return 0;
+       return(load_config());
 }
 
 int unload_module(void)
@@ -2291,13 +2498,23 @@ int unload_module(void)
        STANDARD_HANGUP_LOCALUSERS;
        res = ast_unregister_application(app);
        res |= ast_unregister_application(app2);
+#ifdef USEMYSQLVM
+       mysql_logout();
+#endif
        return res;
 }
 
 int load_module(void)
 {
        int res;
-       load_users();
+       if ((res=load_config())) {
+               return(res);
+       }
+#ifdef USEMYSQLVM
+       if ((res=mysql_login())) {
+               return(res);
+       }
+#endif
        res = ast_register_application(app, vm_exec, synopsis_vm, descrip_vm);
        if (!res)
                res = ast_register_application(app2, vm_execmain, synopsis_vmain, descrip_vmain);