Skip to content
Snippets Groups Projects
app_voicemail.c 79.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
     * Asterisk -- A telephony toolkit for Linux.
     *
     * Voicemail System (did you ever think it could be so easy?)
     * 
    
     * Copyright (C) 2003, Digium Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License
     */
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/lock.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/file.h>
    #include <asterisk/logger.h>
    #include <asterisk/channel.h>
    
    #include <asterisk/channel_pvt.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/pbx.h>
    #include <asterisk/options.h>
    #include <asterisk/config.h>
    #include <asterisk/say.h>
    #include <asterisk/module.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/adsi.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <asterisk/app.h>
    
    #include <asterisk/manager.h>
    
    #include <asterisk/dsp.h>
    
    #include <asterisk/localtime.h>
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/time.h>
    #include <sys/stat.h>
    #include <time.h>
    
    
    /* we define USESQLVM when we have MySQL or POSTGRES */
    #ifdef USEMYSQLVM
    #include <mysql/mysql.h>
    #define USESQLVM 1
    #endif
    
    #ifdef USEPOSTGRESVM
    /*
     * PostgreSQL routines written by Otmar Lendl <lendl@nic.at>
     */
    #include <postgresql/libpq-fe.h>
    #define USESQLVM 1
    #endif
    
    #ifndef USESQLVM
    static inline int sql_init(void) { return 0; }
    static inline void sql_close(void) { }
    #endif
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include <pthread.h>
    #include "../asterisk.h"
    
    Mark Spencer's avatar
    Mark Spencer committed
    #include "../astconf.h"
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    #define COMMAND_TIMEOUT 5000
    
    #define VOICEMAIL_CONFIG "voicemail.conf"
    #define ASTERISK_USERNAME "asterisk"
    
    #define SENDMAIL "/usr/sbin/sendmail -t"
    
    #define INTRO "vm-intro"
    
    #define MAXMSG 100
    #define MAX_OTHER_FORMATS 10
    
    #define VM_SPOOL_DIR AST_SPOOL_DIR "/vm"
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define BASEMAXINLINE 256
    #define BASELINELEN 72
    #define BASEMAXINLINE 256
    #define eol "\r\n"
    
    
    #define MAX_DATETIME_FORMAT	512
    #define DIGITS_DIR	AST_SOUNDS "/digits/"
    struct baseio {
    	int iocp;
    	int iolen;
    	int linelength;
    	int ateof;
    	unsigned char iobuf[BASEMAXINLINE];
    };
    
    struct ast_vm_user {
    	char context[80];
    	char mailbox[80];
    	char password[80];
    	char fullname[80];
    	char email[80];
    	char pager[80];
    	char serveremail[80];
    	char zonetag[80];
    	int attach;
    	int alloced;
    	struct ast_vm_user *next;
    };
    
    struct vm_zone {
    	char name[80];
    	char timezone[80];
    	char msg_format[512];
    	struct vm_zone *next;
    };
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    static char *tdesc = "Comedian Mail (Voicemail System)";
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *adapp = "CoMa";
    
    static char *adsec = "_AST";
    
    static char *addesc = "Comedian Mail";
    
    static int adver = 1;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *synopsis_vm =
    "Leave a voicemail message";
    
    static char *descrip_vm =
    
    "  VoiceMail([s|u|b]extension[@context]):  Leaves voicemail for a given\n"
    "extension (must be configured in voicemail.conf).  If the extension is\n"
    "preceded by an 's' then instructions for leaving the message will be\n"
    "skipped.  If the extension is preceeded by 'u' then the \"unavailable\"\n"
    "message will be played (/var/lib/asterisk/sounds/vm/<exten>/unavail) if it\n"
    "exists.  If the extension is preceeded by a 'b' then the the busy message\n"
    "will be played (that is, busy instead of unavail).\n"
    
    "If the requested mailbox does not exist, and there exists a priority\n"
    "n + 101, then that priority will be taken next.\n"
    
    "Returns -1 on error or mailbox not found, or if the user hangs up.\n"
    "Otherwise, it returns 0.\n";
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    static char *synopsis_vmain =
    "Enter voicemail system";
    
    static char *descrip_vmain =
    
    "  VoiceMailMain([[s]mailbox][@context]): Enters the main voicemail system\n"
    "for the checking of voicemail.  The mailbox can be passed as the option,\n"
    "which will stop the voicemail system from prompting the user for the mailbox.\n"
    "If the mailbox is preceded by 's' then the password check will be skipped.  If\n"
    "a context is specified, logins are considered in that context only.\n"
    "Returns -1 if the user hangs up or 0 otherwise.\n";
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* Leave a message */
    
    static char *capp = "VoiceMail2";
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *app = "VoiceMail";
    
    /* Check mail, control, etc */
    
    static char *capp2 = "VoiceMailMain2";
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *app2 = "VoiceMailMain";
    
    
    static ast_mutex_t vmlock = AST_MUTEX_INITIALIZER;
    struct ast_vm_user *users;
    struct ast_vm_user *usersl;
    struct vm_zone *zones = NULL;
    struct vm_zone *zonesl = NULL;
    static int attach_voicemail;
    static int maxsilence;
    static int silencethreshold = 128;
    static char serveremail[80];
    static char vmfmts[80];
    static int vmmaxmessage;
    static int maxgreet;
    static int skipms;
    static int maxlogins;
    
    static char *emailbody = NULL;
    static int pbxskip = 0;
    static char fromstring[100];
    static char emailtitle[100];
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    STANDARD_LOCAL_USER;
    
    LOCAL_USER_DECL;
    
    
    static void apply_options(struct ast_vm_user *vmu, char *options)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	/* Destructively Parse options and apply */
    	char *stringp = ast_strdupa(options);
    	char *s;
    	char *var, *value;
    	while((s = strsep(&stringp, "|"))) {
    		value = s;
    		if ((var = strsep(&value, "=")) && value) {
    			if (!strcasecmp(var, "attach")) {
    				if (ast_true(value))
    					vmu->attach = 1;
    				else
    					vmu->attach = 0;
    			} else if (!strcasecmp(var, "serveremail")) {
    				strncpy(vmu->serveremail, value, sizeof(vmu->serveremail) - 1);
    			} else if (!strcasecmp(var, "tz")) {
    				strncpy(vmu->zonetag, value, sizeof(vmu->zonetag) - 1);
    			}
    		}
    	}
    	
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    #ifdef USEMYSQLVM
    #include "mysql-vm-routines.h"
    #endif
    
    #ifdef USEPOSTGRESVM
    
    PGconn *dbhandler;
    char	dboption[256];
    ast_mutex_t postgreslock;
    
    static int sql_init(void)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	ast_verbose( VERBOSE_PREFIX_3 "Logging into postgres database: %s\n", dboption);
    /*	fprintf(stderr,"Logging into postgres database: %s\n", dboption); */
    
    	dbhandler=PQconnectdb(dboption);
    	if (PQstatus(dbhandler) == CONNECTION_BAD) {
    		ast_log(LOG_WARNING, "Error Logging into database %s: %s\n",dboption,PQerrorMessage(dbhandler));
    		return(-1);
    	}
    	ast_mutex_init(&postgreslock);
    
    /*	fprintf(stderr,"postgres login OK\n"); */
    	return(0);
    }
    
    static void sql_close(void)
    {
    	PQfinish(dbhandler);
    }
    
    
    static struct ast_vm_user *find_user(struct ast_vm_user *ivm, char *context, char *mailbox)
    {
    	PGresult *PGSQLres;
    
    
    	int numFields, i;
    	char *fname;
    	char query[240];
    	char options[160] = "";
    	struct ast_vm_user *retval;
    
    	retval=malloc(sizeof(struct ast_vm_user));
    
    /*	fprintf(stderr,"postgres find_user:\n"); */
    
    	if (retval) {
    		*retval->mailbox='\0';
    		*retval->context='\0';
    		*retval->password='\0';
    		*retval->fullname='\0';
    		*retval->email='\0';
    		*retval->pager='\0';
    		*retval->serveremail='\0';
    		retval->attach=-1;
    		retval->alloced=1;
    		retval->next=NULL;
    		if (mailbox) {
    			strcpy(retval->mailbox, mailbox);
    		}
    		if (context) {
    			strcpy(retval->context, context);
    		}
    
    		else
    		{
    			strcpy(retval->context, "default");
    		}
    
    
    		if (*retval->context) {
    			sprintf(query, "SELECT password,fullname,email,pager,options FROM voicemail WHERE context='%s' AND mailbox='%s'", context, mailbox);
    		} else {
    
    			sprintf(query, "SELECT password,fullname,email,pager,options FROM voicemail WHERE context='default' AND mailbox='%s'", mailbox);
    
    		}
    /*	fprintf(stderr,"postgres find_user: query = %s\n",query); */
    		ast_mutex_lock(&postgreslock);
    		PGSQLres=PQexec(dbhandler,query);
    		if (PGSQLres!=NULL) {
    			if (PQresultStatus(PGSQLres) == PGRES_BAD_RESPONSE ||
    				PQresultStatus(PGSQLres) == PGRES_NONFATAL_ERROR ||
    				PQresultStatus(PGSQLres) == PGRES_FATAL_ERROR) {
    
    				ast_log(LOG_WARNING,"PGSQL_query: Query Error (%s) Calling PQreset\n",PQcmdStatus(PGSQLres));
    				PQclear(PGSQLres);
    				PQreset(dbhandler);
    				ast_mutex_unlock(&postgreslock);
    				free(retval);
    				return(NULL);
    			} else {
    			numFields = PQnfields(PGSQLres);
    /*	fprintf(stderr,"postgres find_user: query found %d rows with %d fields\n",PQntuples(PGSQLres), numFields); */
    			if (PQntuples(PGSQLres) != 1) {
    				ast_log(LOG_WARNING,"PGSQL_query: Did not find a unique mailbox for %s\n",mailbox);
    				PQclear(PGSQLres);
    				ast_mutex_unlock(&postgreslock);
    				free(retval);
    				return(NULL);
    			}
    			for (i=0; i<numFields; i++) {
    				fname = PQfname(PGSQLres,i);
    				if (!strcmp(fname, "password")) {
    					strncpy(retval->password, PQgetvalue(PGSQLres,0,i),sizeof(retval->password) - 1);
    				} else if (!strcmp(fname, "fullname")) {
    					strncpy(retval->fullname, PQgetvalue(PGSQLres,0,i),sizeof(retval->fullname) - 1);
    				} else if (!strcmp(fname, "email")) {
    					strncpy(retval->email, PQgetvalue(PGSQLres,0,i),sizeof(retval->email) - 1);
    				} else if (!strcmp(fname, "pager")) {
    					strncpy(retval->pager, PQgetvalue(PGSQLres,0,i),sizeof(retval->pager) - 1);
    				} else if (!strcmp(fname, "options")) {
    					strncpy(options, PQgetvalue(PGSQLres,0,i), sizeof(options) - 1);
    					apply_options(retval, options);
    				}
    			}
    			}
    			PQclear(PGSQLres);
    			ast_mutex_unlock(&postgreslock);
    			return(retval);
    		}
    		else {
    			ast_log(LOG_WARNING,"PGSQL_query: Connection Error (%s)\n",PQerrorMessage(dbhandler));
    			ast_mutex_unlock(&postgreslock);
    			free(retval);
    			return(NULL);
    		}
    		/* not reached */
    	} /* malloc() retval */
    	return(NULL);
    }
    
    
    static void vm_change_password(struct ast_vm_user *vmu, char *password)
    {
    	char query[400];
    
    	if (*vmu->context) {
    		sprintf(query, "UPDATE voicemail SET password='%s' WHERE context='%s' AND mailbox='%s' AND (password='%s' OR password IS NULL)", password, vmu->context, vmu->mailbox, vmu->password);
    	} else {
    		sprintf(query, "UPDATE voicemail SET password='%s' WHERE mailbox='%s' AND (password='%s' OR password IS NULL)", password, vmu->mailbox, vmu->password);
    	}
    /*	fprintf(stderr,"postgres change_password: query = %s\n",query); */
    	ast_mutex_lock(&postgreslock);
    	PQexec(dbhandler, query);
    	strcpy(vmu->password, password);
    	ast_mutex_unlock(&postgreslock);
    }
    
    static void reset_user_pw(char *context, char *mailbox, char *password)
    {
    	char query[320];
    
    	if (context) {
    		sprintf(query, "UPDATE voicemail SET password='%s' WHERE context='%s' AND mailbox='%s'", password, context, mailbox);
    	} else {
    		sprintf(query, "UPDATE voicemail SET password='%s' WHERE mailbox='%s'", password, mailbox);
    	}
    	ast_mutex_lock(&postgreslock);
    /*	fprintf(stderr,"postgres reset_user_pw: query = %s\n",query); */
    	PQexec(dbhandler, query);
    	ast_mutex_unlock(&postgreslock);
    }
    
    #endif	/* Postgres */
    
    #ifndef USESQLVM
    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_mutex_lock(&vmlock);
    	cur = users;
    	while(cur) {
    		if ((!context || !strcasecmp(context, cur->context)) &&
    			(!strcasecmp(mailbox, cur->mailbox)))
    				break;
    		cur=cur->next;
    	}
    	if (cur) {
    		if (ivm)
    			vmu = ivm;
    		else
    			/* Make a copy, so that on a reload, we have no race */
    			vmu = malloc(sizeof(struct ast_vm_user));
    		if (vmu) {
    			memcpy(vmu, cur, sizeof(struct ast_vm_user));
    			if (ivm)
    				vmu->alloced = 0;
    			else
    				vmu->alloced = 1;
    			vmu->next = NULL;
    		}
    	}
    	ast_mutex_unlock(&vmlock);
    	return vmu;
    }
    
    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_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_mutex_unlock(&vmlock);
    	return res;
    
    static void vm_change_password(struct ast_vm_user *vmu, char *newpassword)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
            /*  There's probably a better way of doing this. */
            /*  That's why I've put the password change in a separate function. */
    
    		/*  This could also be done with a database function */
    	
    
    Mark Spencer's avatar
    Mark Spencer committed
            FILE *configin;
            FILE *configout;
    		char inbuf[256];
    		char orig[256];
    
    Mark Spencer's avatar
    Mark Spencer committed
    		char tmpin[AST_CONFIG_MAX_PATH];
    		char tmpout[AST_CONFIG_MAX_PATH];
    
    Mark Spencer's avatar
    Mark Spencer committed
    		char *user, *pass, *rest, *trim;
    
    		snprintf((char *)tmpin, sizeof(tmpin)-1, "%s/voicemail.conf",(char *)ast_config_AST_CONFIG_DIR);
    		snprintf((char *)tmpout, sizeof(tmpout)-1, "%s/voicemail.conf.new",(char *)ast_config_AST_CONFIG_DIR);
    
    Mark Spencer's avatar
    Mark Spencer committed
            configin = fopen((char *)tmpin,"r");
    
    		if (configin)
    	        configout = fopen((char *)tmpout,"w+");
    		else
    			configout = NULL;
    		if(!configin || !configout) {
    			if (configin)
    				fclose(configin);
    			else
    				ast_log(LOG_WARNING, "Warning: Unable to open '%s' for reading: %s\n", tmpin, strerror(errno));
    			if (configout)
    				fclose(configout);
    			else
    				ast_log(LOG_WARNING, "Warning: Unable to open '%s' for writing: %s\n", tmpout, strerror(errno));
    			return;
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    
            while (!feof(configin)) {
    			/* Read in the line */
    			fgets(inbuf, sizeof(inbuf), configin);
    			if (!feof(configin)) {
    				/* Make a backup of it */
    				memcpy(orig, inbuf, sizeof(orig));
    				/* Strip trailing \n and comment */
    				inbuf[strlen(inbuf) - 1] = '\0';
    				user = strchr(inbuf, ';');
    				if (user)
    					*user = '\0';
    				user=inbuf;
    				while(*user < 33)
    					user++;
    				pass = strchr(user, '=');
    				if (pass > user) {
    					trim = pass - 1;
    					while(*trim && *trim < 33) {
    						*trim = '\0';
    						trim--;
    					}
    				}
    				if (pass) {
    					*pass = '\0';
    					pass++;
    					if (*pass == '>')
    						pass++;
    					while(*pass && *pass < 33)
    						pass++;
    				}
    				if (pass) {
    					rest = strchr(pass,',');
    					if (rest) {
    						*rest = '\0';
    						rest++;
    					}
    				} else
    					rest = NULL;
    
    				if (user && pass && *user && *pass && !strcmp(user, vmu->mailbox) && !strcmp(pass, vmu->password)) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    					/* This is the line */
    					if (rest) {
    
    						fprintf(configout, "%s => %s,%s\n", vmu->mailbox,newpassword,rest);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					} else {
    
    						fprintf(configout, "%s => %s\n", vmu->mailbox,newpassword);
    
    Mark Spencer's avatar
    Mark Spencer committed
    					}
    				} else {
    					/* Put it back like it was */
    					fprintf(configout, orig);
    				}
    			}
            }
            fclose(configin);
            fclose(configout);
    
    
    Mark Spencer's avatar
    Mark Spencer committed
            unlink((char *)tmpin);
            rename((char *)tmpout,(char *)tmpin);
    
    	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);
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int
    
    inbuf(struct baseio *bio, FILE *fi)
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return 0;
    
    
    	if ( (l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if(ferror(fi))
    			return -1;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return 0;
    	}
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    	return 1;
    }
    
    static int 
    
    inchar(struct baseio *bio, FILE *fi)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if(bio->iocp>=bio->iolen)
    		if(!inbuf(bio, fi))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			return EOF;
    
    
    	return bio->iobuf[bio->iocp++];
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    static int
    
    ochar(struct baseio *bio, int c, FILE *so)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	if(bio->linelength>=BASELINELEN) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if(fputs(eol,so)==EOF)
    			return -1;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    	if(putc(((unsigned char)c),so)==EOF)
    		return -1;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    	return 1;
    }
    
    static int base_encode(char *filename, FILE *so)
    {
    	unsigned char dtable[BASEMAXINLINE];
    	int i,hiteof= 0;
    	FILE *fi;
    
    	memset(&bio, 0, sizeof(bio));
    	bio.iocp = BASEMAXINLINE;
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    	if ( !(fi = fopen(filename, "rb"))) {
    		ast_log(LOG_WARNING, "Failed to open log file: %s: %s\n", filename, strerror(errno));
    		return -1;
    	}
    
    	for(i= 0;i<9;i++){
    		dtable[i]= 'A'+i;
    		dtable[i+9]= 'J'+i;
    		dtable[26+i]= 'a'+i;
    		dtable[26+i+9]= 'j'+i;
    	}
    	for(i= 0;i<8;i++){
    		dtable[i+18]= 'S'+i;
    		dtable[26+i+18]= 's'+i;
    	}
    	for(i= 0;i<10;i++){
    		dtable[52+i]= '0'+i;
    	}
    	dtable[62]= '+';
    	dtable[63]= '/';
    
    	while(!hiteof){
    		unsigned char igroup[3],ogroup[4];
    		int c,n;
    
    		igroup[0]= igroup[1]= igroup[2]= 0;
    
    		for(n= 0;n<3;n++){
    
    			if ( (c = inchar(&bio, fi)) == EOF) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				hiteof= 1;
    				break;
    			}
    
    			igroup[n]= (unsigned char)c;
    		}
    
    		if(n> 0){
    			ogroup[0]= dtable[igroup[0]>>2];
    			ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
    			ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
    			ogroup[3]= dtable[igroup[2]&0x3F];
    
    			if(n<3) {
    				ogroup[3]= '=';
    
    				if(n<2)
    					ogroup[2]= '=';
    			}
    
    			for(i= 0;i<4;i++)
    
    Mark Spencer's avatar
    Mark Spencer committed
    		}
    	}
    
    	if(fputs(eol,so)==EOF)
    		return 0;
    
    	fclose(fi);
    
    	return 1;
    }
    
    
    static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *mailbox, char *callerid, char *attach, char *format, long duration, int attach_user_voicemail)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	FILE *p;
    	char date[256];
    	char host[256];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char who[256];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char bound[256];
    	char fname[256];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char dur[256];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	time_t t;
    
    	struct vm_zone *the_zone = NULL;
    
    	if (!strcmp(format, "wav49"))
    		format = "WAV";
    	ast_log(LOG_DEBUG, "Attaching file '%s', format '%s', uservm is '%d', global is %d\n", attach, format, attach_user_voicemail, attach_voicemail);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p = popen(SENDMAIL, "w");
    	if (p) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    		gethostname(host, sizeof(host));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (strchr(srcemail, '@'))
    
    Mark Spencer's avatar
    Mark Spencer committed
    			strncpy(who, srcemail, sizeof(who)-1);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		else {
    			snprintf(who, sizeof(who), "%s@%s", srcemail, host);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		snprintf(dur, sizeof(dur), "%ld:%02ld", duration / 60, duration % 60);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		time(&t);
    
    
    		/* Does this user have a timezone specified? */
    		if (strlen(vmu->zonetag)) {
    			/* Find the zone in the list */
    			struct vm_zone *z;
    			z = zones;
    			while (z) {
    				if (!strcmp(z->name, vmu->zonetag)) {
    					the_zone = z;
    					break;
    				}
    				z = z->next;
    			}
    		}
    
    		if (the_zone)
    			ast_localtime(&t,&tm,the_zone->timezone);
    		else
    			ast_localtime(&t,&tm,NULL);
    
    		strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		fprintf(p, "Date: %s\n", date);
    
    		
    		if (*fromstring)
    			fprintf(p, "From: %s <%s>\n", fromstring, who);
    		else
    			fprintf(p, "From: Asterisk PBX <%s>\n", who);
    		fprintf(p, "To: %s <%s>\n", vmu->fullname, vmu->email);
    
    		if( *emailtitle)
    		{
    			fprintf(p, emailtitle, msgnum, mailbox) ;
    			fprintf(p,"\n") ;
    		}
    		else
    		if (pbxskip)
    			fprintf(p, "Subject: New message %d in mailbox %s\n", msgnum + 1, mailbox);
    		else
    			fprintf(p, "Subject: [PBX]: New message %d in mailbox %s\n", msgnum + 1, mailbox);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		fprintf(p, "Message-ID: <Asterisk-%d-%s-%d@%s>\n", msgnum, mailbox, getpid(), host);
    		fprintf(p, "MIME-Version: 1.0\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    			// Something unique.
    			snprintf(bound, sizeof(bound), "Boundary=%d%s%d", msgnum, mailbox, getpid());
    
    Mark Spencer's avatar
    Mark Spencer committed
    			fprintf(p, "Content-Type: MULTIPART/MIXED; BOUNDARY=\"%s\"\n\n\n", bound);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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);
    		if (emailbody) {
    			struct ast_channel *ast = ast_channel_alloc(0);
    			if (ast) {
    				char *passdata;
    				int vmlen = strlen(emailbody)*3 + 200;
    				if ((passdata = alloca(vmlen))) {
    					memset(passdata, 0, vmlen);
    					pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
    					pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
    					sprintf(passdata,"%d",msgnum);
    					pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
    					pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
    					pbx_builtin_setvar_helper(ast, "VM_CALLERID", (callerid ? callerid : "an unknown caller"));
    					pbx_builtin_setvar_helper(ast, "VM_DATE", date);
    					pbx_substitute_variables_helper(ast,emailbody,passdata,vmlen);
    					fprintf(p, "%s\n",passdata);
    				} else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
    				ast_channel_free(ast);
    			} else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
    		} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			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", vmu->fullname, 
    			dur, msgnum + 1, mailbox, (callerid ? callerid : "an unknown caller"), date);
    		}
    		if (attach_user_voicemail) {
    
    Mark Spencer's avatar
    Mark Spencer committed
    			fprintf(p, "--%s\n", bound);
    
    			fprintf(p, "Content-Type: audio/x-%s; name=\"msg%04d.%s\"\n", format, msgnum, format);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			fprintf(p, "Content-Transfer-Encoding: BASE64\n");
    			fprintf(p, "Content-Description: Voicemail sound attachment.\n");
    			fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"\n\n", msgnum, format);
    
    			snprintf(fname, sizeof(fname), "%s.%s", attach, format);
    			base_encode(fname, p);
    			fprintf(p, "\n\n--%s--\n.\n", bound);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    		pclose(p);
    	} else {
    		ast_log(LOG_WARNING, "Unable to launch '%s'\n", SENDMAIL);
    		return -1;
    	}
    	return 0;
    }
    
    
    static int sendpage(char *srcemail, char *pager, int msgnum, char *mailbox, char *callerid, long duration, struct ast_vm_user *vmu)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	FILE *p;
    	char date[256];
    	char host[256];
    	char who[256];
    	char dur[256];
    	time_t t;
    	struct tm tm;
    
    	struct vm_zone *the_zone = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	p = popen(SENDMAIL, "w");
    
    	if (p) {
    		gethostname(host, sizeof(host));
    		if (strchr(srcemail, '@'))
    			strncpy(who, srcemail, sizeof(who)-1);
    		else {
    			snprintf(who, sizeof(who), "%s@%s", srcemail, host);
    		}
    		snprintf(dur, sizeof(dur), "%ld:%02ld", duration / 60, duration % 60);
    		time(&t);
    
    
    		/* Does this user have a timezone specified? */
    		if (strlen(vmu->zonetag)) {
    			/* Find the zone in the list */
    			struct vm_zone *z;
    			z = zones;
    			while (z) {
    				if (!strcmp(z->name, vmu->zonetag)) {
    					the_zone = z;
    					break;
    				}
    				z = z->next;
    			}
    		}
    
    		if (the_zone)
    			ast_localtime(&t,&tm,the_zone->timezone);
    		else
    			ast_localtime(&t,&tm,NULL);
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    		strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
    		fprintf(p, "Date: %s\n", date);
    		fprintf(p, "From: Asterisk PBX <%s>\n", who);
    		fprintf(p, "To: %s\n", pager);
    
    		fprintf(p, "Subject: New VM\n\n");
    
    Mark Spencer's avatar
    Mark Spencer committed
    		strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
    
    		fprintf(p, "New %s long msg in box %s\n"
    
    Mark Spencer's avatar
    Mark Spencer committed
    		           "from %s, on %s", dur, mailbox, (callerid ? callerid : "unknown"), date);
    		pclose(p);
    	} else {
    		ast_log(LOG_WARNING, "Unable to launch '%s'\n", SENDMAIL);
    		return -1;
    	}
    	return 0;
    }
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int get_date(char *s, int len)
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	time_t t;
    	t = time(0);
    
    	localtime_r(&t,&tm);
    	return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
    
    static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    	int res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char fn[256];
    
    	snprintf(fn, sizeof(fn), "voicemail/%s/%s/greet", context, ext);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (ast_fileexists(fn, NULL, NULL) > 0) {
    		res = ast_streamfile(chan, fn, chan->language);
    		if (res)
    			return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		res = ast_waitstream(chan, ecodes);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res)
    			return res;
    	} else {
    		res = ast_streamfile(chan, "vm-theperson", chan->language);
    		if (res)
    			return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		res = ast_waitstream(chan, ecodes);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res)
    			return res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    		res = ast_say_digit_str(chan, ext, ecodes, chan->language);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (res)
    			return res;
    	}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (busy)
    		res = ast_streamfile(chan, "vm-isonphone", chan->language);
    	else
    		res = ast_streamfile(chan, "vm-isunavail", chan->language);
    	if (res)
    		return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	res = ast_waitstream(chan, ecodes);
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return res;
    }
    
    
    static int play_and_wait(struct ast_channel *chan, char *fn)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	int d;
    	d = ast_streamfile(chan, fn, chan->language);
    	if (d)
    		return d;
    	d = ast_waitstream(chan, AST_DIGIT_ANY);
    
    	ast_stopstream(chan);
    
    	return d;
    }
    
    static int play_and_record(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt)
    {
    	char d, *fmts;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char comment[256];
    
    	int x, fmtcnt=1, res=-1,outmsg=0;
    	struct ast_frame *f;
    	struct ast_filestream *others[MAX_OTHER_FORMATS];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	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);
    
    	if (playfile) {	
    		d = play_and_wait(chan, playfile);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (d > -1)
    
    			d = ast_streamfile(chan, "beep",chan->language);
    		if (!d)
    			d = ast_waitstream(chan,"");
    		if (d < 0)
    			return -1;
    	}
    	
    	fmts = ast_strdupa(fmt);
    	
    	stringp=fmts;
    	strsep(&stringp, "|");
    	ast_log(LOG_DEBUG,"Recording Formats: sfmts=%s\n", fmts);	
    	sfmt[0] = ast_strdupa(fmts);
    	
    	while((fmt = strsep(&stringp, "|"))) {
    		if (fmtcnt > MAX_OTHER_FORMATS - 1) {
    			ast_log(LOG_WARNING, "Please increase MAX_OTHER_FORMATS in app_voicemail.c\n");
    			break;
    		}
    		sfmt[fmtcnt++] = ast_strdupa(fmt);
    	}
    
    	if (maxtime)
    		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, %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, silencethreshold);
    	
    	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 */
    		f = NULL;
    		for(;;) {
    		 	res = ast_waitfor(chan, 2000);
    			if (!res) {
    				ast_log(LOG_DEBUG, "One waitfor failed, trying another\n");
    				/* Try one more time in case of masq */
    			 	res = ast_waitfor(chan, 2000);
    				if (!res) {
    					ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name);
    					res = -1;
    				}
    			}
    			
    			if (res < 0) {
    				f = NULL;
    				break;
    			}
    			f = ast_read(chan);
    			if (!f)
    				break;
    			if (f->frametype == AST_FRAME_VOICE) {
    				/* write each format */
    				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");
    					ast_frfree(f);
    					break;
    				}
    			} else if (f->frametype == AST_FRAME_VIDEO) {