Skip to content
Snippets Groups Projects
app_voicemail.c 201 KiB
Newer Older
  • Learn to ignore specific revisions
  • Mark Spencer's avatar
    Mark Spencer committed
    /*
    
     * Asterisk -- An open source telephony toolkit.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Copyright (C) 1999 - 2005, Digium, Inc.
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * Mark Spencer <markster@digium.com>
    
    Mark Spencer's avatar
    Mark Spencer committed
     *
    
     * 12-16-2004 : Support for Greek added by InAccess Networks (work funded by HOL, www.hol.gr)
    
     *				 George Konstantoulakis <gkon@inaccessnetworks.com>
    
     * 05-10-2005 : Support for Swedish and Norwegian added by Daniel Nylander, http://www.danielnylander.se/
     *
     * 05-11-2005 : An option for maximum number of messsages per mailbox added by GDS Partners (www.gdspartners.com)
     * 07-11-2005 : An issue with voicemail synchronization has been fixed by GDS Partners (www.gdspartners.com)
    
     *				 Stojan Sljivic <stojan.sljivic@gdspartners.com>
    
     *
     * See http://www.asterisk.org for more information about
     * the Asterisk project. Please do not directly contact
     * any of the maintainers of this project for assistance;
     * the project provides a web site, mailing lists and IRC
     * channels for your use.
     *
     * This program is free software, distributed under the terms of
     * the GNU General Public License Version 2. See the LICENSE file
     * at the top of the source tree.
     */
    
    
     * \brief Comedian Mail - Voicemail System
    
    #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 <sys/types.h>
    #include <sys/mman.h>
    #include <time.h>
    #include <dirent.h>
    
    #include "asterisk.h"
    
    ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
    
    
    #include "asterisk/lock.h"
    #include "asterisk/file.h"
    #include "asterisk/logger.h"
    #include "asterisk/channel.h"
    #include "asterisk/pbx.h"
    #include "asterisk/options.h"
    #include "asterisk/config.h"
    #include "asterisk/say.h"
    #include "asterisk/module.h"
    #include "asterisk/adsi.h"
    #include "asterisk/app.h"
    #include "asterisk/manager.h"
    #include "asterisk/dsp.h"
    #include "asterisk/localtime.h"
    #include "asterisk/cli.h"
    #include "asterisk/utils.h"
    
    #ifdef USE_ODBC_STORAGE
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    #define COMMAND_TIMEOUT 5000
    
    #define VOICEMAIL_CONFIG "voicemail.conf"
    #define ASTERISK_USERNAME "asterisk"
    
    
    /* Default mail command to mail voicemail. Change it with the
        mailcmd= command in voicemail.conf */
    
    Mark Spencer's avatar
    Mark Spencer committed
    #define SENDMAIL "/usr/sbin/sendmail -t"
    
    #define INTRO "vm-intro"
    
    #define MAXMSG 100
    
    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 MAX_NUM_CID_CONTEXTS 10
    
    
    #define VM_REVIEW		(1 << 0)
    #define VM_OPERATOR		(1 << 1)
    #define VM_SAYCID		(1 << 2)
    #define VM_SVMAIL		(1 << 3)
    #define VM_ENVELOPE		(1 << 4)
    #define VM_SAYDURATION		(1 << 5)
    #define VM_SKIPAFTERCMD 	(1 << 6)
    #define VM_FORCENAME		(1 << 7)	/* Have new users record their name */
    #define VM_FORCEGREET		(1 << 8)	/* Have new users record their greetings */
    #define VM_PBXSKIP		(1 << 9)
    #define VM_DIRECFORWARD 	(1 << 10)	/* directory_forward */
    #define VM_ATTACH		(1 << 11)
    #define VM_DELETE		(1 << 12)
    #define VM_ALLOCED		(1 << 13)
    
    
    #define ERROR_LOCK_PATH		-100
    
    
    enum {
    	OPT_SILENT =(1 << 0),
    	OPT_BUSY_GREETING = (1 << 1),
    	OPT_UNAVAIL_GREETING = (1 << 2),
    	OPT_RECORDGAIN = (1 << 3),
    	OPT_PREPEND_MAILBOX = (1 << 4),
    } vm_option_flags;
    
    enum {
    	OPT_ARG_RECORDGAIN = 0,
    	OPT_ARG_ARRAY_SIZE = 1,
    } vm_option_args;
    
    AST_APP_OPTIONS(vm_app_options, {
    	AST_APP_OPTION('s', OPT_SILENT),
    	AST_APP_OPTION('b', OPT_BUSY_GREETING),
    	AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
    	AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
    	AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
    
    /* Syntaxes supported, not really language codes.
    	en - English
    	de - German
    	es - Spanish
    	fr - French
    
    	nl - Dutch
    	pt - Portuguese
    
    
    German requires the following additional soundfile:
    1F	einE (feminine)
    
    Spanish requires the following additional soundfile:
    1M      un (masculine)
    
    Dutch, Portuguese & Spanish require the following additional soundfiles:
    vm-INBOXs	singular of 'new'
    vm-Olds		singular of 'old/heard/read'
    
    NB these are plural:
    vm-INBOX	nieuwe (nl)
    vm-Old		oude (nl)
    
    
    Swedish uses:
    vm-nytt		singular of 'new'
    vm-nya		plural of 'new'
    vm-gammalt	singular of 'old'
    vm-gamla	plural of 'old'
    digits/ett	'one', not always same as 'digits/1'
    
    Norwegian uses:
    vm-ny		singular of 'new'
    vm-nye		plural of 'new'
    vm-gammel	singular of 'old'
    vm-gamle	plural of 'old'
    
    
    Dutch also uses:
    nl-om		'at'?
    
    Spanish also uses:
    vm-youhaveno
    
    
    Italian requires the following additional soundfile:
    
    For vm_intro_it:
    vm-nuovo	new
    vm-nuovi	new plural
    vm-vecchio	old
    vm-vecchi	old plural
    
    Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
    spelled among others when you have to change folder. For the above reasons, vm-INBOX
    and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
    
    struct baseio {
    	int iocp;
    	int iolen;
    	int linelength;
    	int ateof;
    	unsigned char iobuf[BASEMAXINLINE];
    };
    
    
    /* Structure for linked list of users */
    
    	char context[AST_MAX_CONTEXT];	/* Voicemail context */
    	char mailbox[AST_MAX_EXTENSION];/* Mailbox id, unique within vm context */
    
    	char password[80];		/* Secret pin code, numbers only */
    	char fullname[80];		/* Full name, for directory app */
    	char email[80];			/* E-mail address */
    	char pager[80];			/* E-mail address to pager (no attachment) */
    	char serveremail[80];		/* From: Mail address */
    	char mailcmd[160];		/* Configurable mail command */
    	char language[MAX_LANGUAGE];    /* Config: Language setting */
    	char zonetag[80];		/* Time zone */
    
    	char callback[80];
    	char dialout[80];
    
    Mark Spencer's avatar
    Mark Spencer committed
    	char uniqueid[20];		/* Unique integer identifier */
    
    	char exit[80];
    
    	unsigned int flags;		/* VM_ flags */	
    
    	int maxmsg;			/* Maximum number of msgs per folder for this mailbox */
    
    	struct ast_vm_user *next;
    };
    
    struct vm_zone {
    	char name[80];
    	char timezone[80];
    	char msg_format[512];
    	struct vm_zone *next;
    };
    
    struct vm_state {
    	char curbox[80];
    	char username[80];
    	char curdir[256];
    	char vmbox[256];
    	char fn[256];
    	char fn2[256];
    
    	int curmsg;
    	int lastmsg;
    	int newmessages;
    	int oldmessages;
    	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, 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,
    			      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);
    
    
    #ifdef USE_ODBC_STORAGE
    static char odbc_database[80];
    
    static char odbc_table[80];
    
    #define RETRIEVE(a,b) retrieve_file(a,b)
    #define DISPOSE(a,b) remove_file(a,b)
    
    #define STORE(a,b,c,d) store_file(a,b,c,d)
    
    #define EXISTS(a,b,c,d) (message_exists(a,b))
    
    #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
    #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
    
    #define DELETE(a,b,c) (delete_file(a,b))
    #else
    #define RETRIEVE(a,b)
    #define DISPOSE(a,b)
    
    #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
    
    #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
    #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
    
    #define DELETE(a,b,c) (vm_delete(c))
    #endif
    
    
    static char VM_SPOOL_DIR[AST_CONFIG_MAX_PATH];
    
    
    static char ext_pass_cmd[128];
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *tdesc = "Comedian Mail (Voicemail System)";
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *addesc = "Comedian Mail";
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    static char *synopsis_vm =
    "Leave a voicemail message";
    
    static char *descrip_vm =
    
    "  VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]):  Leaves"
    "voicemail for a given mailbox (must be configured in voicemail.conf).\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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "* 's'    instructions for leaving the message will be skipped.\n"
    "* 'u'    the \"unavailable\" greeting will be played.\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"
    
    "extension 'a' in the current context.\n"
    
    "If the requested mailbox does not exist, and there exists a priority\n"
    "n + 101, then that priority will be taken next.\n"
    
    "If an error occur in the voicemail application resulting in that the message cannot be left,\n" 
    "and there exists a priority n + 101, then that priority will be taken next.\n"
    
    "When multiple mailboxes are specified, the unavailable or busy message\n"
    "will be taken from the first mailbox specified.\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([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: \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"
    
    Russell Bryant's avatar
    Russell Bryant committed
    "* 's'    the password check will be skipped.\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 =
    "Check if vmbox exists";
    
    static char *descrip_vm_box_exists =
    "  MailboxExists(mailbox[@context]): Conditionally branches to priority n+101\n"
    "if the specified voice mailbox exists.\n";
    
    
    static char *synopsis_vmauthenticate =
    "Authenticate off voicemail passwords";
    
    static char *descrip_vmauthenticate =
    
    "  VMAuthenticate([mailbox][@context][|options]): Behaves identically to\n"
    "the Authenticate application, with the exception that the passwords are\n"
    "taken from voicemail.conf.\n"
    
    "  If the mailbox is specified, only that mailbox's password will be considered\n"
    "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
    
    "be set with the authenticated mailbox.\n"
    "If the options contain 's' then no initial prompts will be played.\n";
    
    Mark Spencer's avatar
    Mark Spencer committed
    /* Leave a message */
    static char *app = "VoiceMail";
    
    /* Check mail, control, etc */
    static char *app2 = "VoiceMailMain";
    
    
    static char *app4 = "VMAuthenticate";
    
    AST_MUTEX_DEFINE_STATIC(vmlock);
    
    struct ast_vm_user *users;
    struct ast_vm_user *usersl;
    struct vm_zone *zones = NULL;
    struct vm_zone *zonesl = NULL;
    static int maxsilence;
    
    static int silencethreshold = 128;
    static char serveremail[80];
    
    static char mailcmd[160];	/* Configurable mail cmd */
    
    static int vmmaxmessage;
    static int maxgreet;
    static int skipms;
    static int maxlogins;
    
    
    static struct ast_flags globalflags = {0};
    
    
    static int saydurationminfo;
    
    static char dialcontext[AST_MAX_CONTEXT];
    static char callcontext[AST_MAX_CONTEXT];
    static char exitcontext[AST_MAX_CONTEXT];
    
    
    static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
    
    
    
    static char *emailsubject = NULL;
    
    static char *pagerbody = NULL;
    static char *pagersubject = NULL;
    
    static char charset[32] = "ISO-8859-1";
    
    static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
    static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
    
    static int adsiver = 1;
    
    static char emaildateformat[32] = "%A, %B %d, %Y at %r";
    
    Mark Spencer's avatar
    Mark Spencer committed
    STANDARD_LOCAL_USER;
    
    LOCAL_USER_DECL;
    
    
    static void populate_defaults(struct ast_vm_user *vmu)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);	
    
    		vmu->saydurationm = saydurationminfo;
    
    	if (callcontext)
    
    		ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
    
    	if (dialcontext)
    
    		ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
    
    	if (exitcontext)
    
    		ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!strcasecmp(var, "attach")) {
    
    		ast_set2_flag(vmu, ast_true(value), VM_ATTACH);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "serveremail")) {
    
    		ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "language")) {
    
    		ast_copy_string(vmu->language, value, sizeof(vmu->language));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "tz")) {
    
    		ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "delete")) {
    
    		ast_set2_flag(vmu, ast_true(value), VM_DELETE);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "saycid")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_SAYCID);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var,"sendvoicemail")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "review")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_REVIEW);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "operator")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "envelope")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);	
    
    	} else if (!strcasecmp(var, "sayduration")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);	
    
    	} else if (!strcasecmp(var, "saydurationm")){
    		if (sscanf(value, "%d", &x) == 1) {
    			vmu->saydurationm = x;
    		} else {
    			ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "forcename")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "forcegreetings")){
    
    		ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "callback")) {
    
    		ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "dialout")) {
    
    		ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else if (!strcasecmp(var, "exitcontext")) {
    
    		ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
    
    	} else if (!strcasecmp(var, "maxmsg")) {
    		vmu->maxmsg = atoi(value);
     		if (vmu->maxmsg <= 0) {
    			ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
    			vmu->maxmsg = MAXMSG;
    		} else if (vmu->maxmsg > MAXMSGLIMIT) {
    			ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
    			vmu->maxmsg = MAXMSGLIMIT;
    		}
    
    	} else if (!strcasecmp(var, "options")) {
    		apply_options(vmu, value);
    
    Mark Spencer's avatar
    Mark Spencer committed
    }
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    Mark Spencer's avatar
    Mark Spencer committed
    	int res;
    	if (!ast_strlen_zero(vmu->uniqueid)) {
    
    		res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
    
    			ast_copy_string(vmu->password, password, sizeof(vmu->password));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		return res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	return -1;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void apply_options(struct ast_vm_user *vmu, const char *options)
    {	/* Destructively Parse options and apply */
    	char *stringp;
    	char *s;
    	char *var, *value;
    	stringp = ast_strdupa(options);
    	while ((s = strsep(&stringp, "|"))) {
    		value = s;
    		if ((var = strsep(&value, "=")) && value) {
    			apply_option(vmu, var, value);
    		}
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
    
    Mark Spencer's avatar
    Mark Spencer committed
    	struct ast_variable *var, *tmp;
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (ivm)
    		retval=ivm;
    	else
    		retval=malloc(sizeof(struct ast_vm_user));
    
    		memset(retval, 0, sizeof(struct ast_vm_user));
    
    Mark Spencer's avatar
    Mark Spencer committed
    		if (!ivm)
    
    			ast_set_flag(retval, VM_ALLOCED);	
    
    			ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
    
    			ast_copy_string(retval->context, context, sizeof(retval->context));
    
    			strcpy(retval->context, "default");
    
    		populate_defaults(retval);
    
    Mark Spencer's avatar
    Mark Spencer committed
    		var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", retval->context, NULL);
    		if (var) {
    			tmp = var;
    			while(tmp) {
    				printf("%s => %s\n", tmp->name, tmp->value);
    				if (!strcasecmp(tmp->name, "password")) {
    
    					ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
    
    Mark Spencer's avatar
    Mark Spencer committed
    				} else if (!strcasecmp(tmp->name, "uniqueid")) {
    
    					ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
    
    				} else if (!strcasecmp(tmp->name, "pager")) {
    
    					ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
    
    				} else if (!strcasecmp(tmp->name, "email")) {
    
    					ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
    
    				} else if (!strcasecmp(tmp->name, "fullname")) {
    
    					ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
    
    Mark Spencer's avatar
    Mark Spencer committed
    				} else
    					apply_option(retval, tmp->name, tmp->value);
    				tmp = tmp->next;
    			} 
    		} else { 
    			if (!ivm) 
    
    Mark Spencer's avatar
    Mark Spencer committed
    			retval = NULL;
    		}	
    	} 
    	return retval;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
    
    {
    	/* This function could be made to generate one from a database, too */
    	struct ast_vm_user *vmu=NULL, *cur;
    	ast_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));
    
    			ast_set2_flag(vmu, !ivm, VM_ALLOCED);	
    
    Mark Spencer's avatar
    Mark Spencer committed
    	} else
    		vmu = find_user_realtime(ivm, context, mailbox);
    
    Mark Spencer's avatar
    Mark Spencer committed
    static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
    
    {
    	/* This function could be made to generate one from a database, too */
    	struct ast_vm_user *cur;
    	int res = -1;
    	ast_mutex_lock(&vmlock);
    	cur = users;
    
    	while (cur) {
    
    		if ((!context || !strcasecmp(context, cur->context)) &&
    			(!strcasecmp(mailbox, cur->mailbox)))
    				break;
    		cur=cur->next;
    	}
    	if (cur) {
    
    		ast_copy_string(cur->password, newpass, sizeof(cur->password));
    
    		res = 0;
    	}
    	ast_mutex_unlock(&vmlock);
    	return res;
    
    Mark Spencer's avatar
    Mark Spencer committed
    static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	/*  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 */
    
    	FILE *configin;
    	FILE *configout;
    	int linenum=0;
    	char inbuf[256];
    	char orig[256];
    	char currcontext[256] ="";
    	char tmpin[AST_CONFIG_MAX_PATH];
    	char tmpout[AST_CONFIG_MAX_PATH];
    	struct stat statbuf;
    
    
    Mark Spencer's avatar
    Mark Spencer committed
    	if (!change_password_realtime(vmu, newpassword))
    		return;
    
    
    	snprintf(tmpin, sizeof(tmpin), "%s/voicemail.conf", ast_config_AST_CONFIG_DIR);
    	snprintf(tmpout, sizeof(tmpout), "%s/voicemail.conf.new", ast_config_AST_CONFIG_DIR);
    	configin = fopen(tmpin,"r");
    	if (configin)
    		configout = fopen(tmpout,"w+");
    	else
    		configout = NULL;
    	if (!configin || !configout) {
    
    			fclose(configin);
    
    			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));
    
    	while (!feof(configin)) {
    		/* Read in the line */
    		fgets(inbuf, sizeof(inbuf), configin);
    		linenum++;
    		if (!feof(configin)) {
    
    			char *user = NULL, *pass = NULL, *rest = NULL, *trim = NULL,
    				*comment = NULL, *tmpctx = NULL, *tmpctxend = NULL;
    			
    
    			/* Make a backup of it */
    
    			ast_copy_string(orig, inbuf, sizeof(orig));
    			
    			/*
    				Read the file line by line, split each line into a comment and command section
    				only parse the command portion of the line
    			*/
    			if (inbuf[strlen(inbuf) - 1] == '\n')
    				inbuf[strlen(inbuf) - 1] = '\0';
    			comment = strchr(inbuf, ';');
    			if (comment) {
    				*comment = '\0'; /* Now inbuf is terminated just before the comment */
    				comment++;
    
    			
    			if (inbuf[0] != '\0') { /* skip over parsing for lines starting with a comment character and empty lines */
    				/* Check for a context, first '[' to first ']' */
    				tmpctx = strchr(inbuf, '[');
    				if (tmpctx) {
    					tmpctxend = strchr(inbuf, ']');
    					if (tmpctxend && (tmpctxend > tmpctx)) {
    						/* Valid context */
    						ast_copy_string(currcontext, tmpctx + 1, tmpctxend - tmpctx - 1);
    						currcontext[tmpctxend - tmpctx - 1] = '\0';
    					} else {
    						tmpctx = NULL;
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    				
    				if (!tmpctx) {
    					/* This isn't a context line, check for MBX => PSWD... */
    					user = inbuf;
    					pass = strchr(user, '=');
    					if(pass > user) {
    						/* We have a line in the form of aaaaa=aaaaaa */
    						*pass = '\0';
    						pass++;
    						
    						/* Trim whitespace from user */
    						trim = pass - 2;
    						while (*trim && *trim < 33) {
    							*trim = '\0';
    							trim--;
    						}
    						
    						/* Trim whitespace and '>' from pass */
    						if (*pass == '>') {
    							*pass = '\0';
    							pass++;
    						}
    						while (*pass && *pass < 33) {
    							*pass = '\0';
    							pass++;
    						}
    						
    						/* 
    						   Since no whitespace allowed in fields, or more correctly white space
    						   inside the fields is there for a purpose, we can just terminate pass
    						   at the comma or EOL whichever comes first.
    						*/
    						trim = strchr(pass, ',');
    						if (trim) {
    							*trim = '\0';
    							rest = trim + 1;
    						} else {
    							rest = NULL;
    						}
    					} else {
    						user = NULL;
    						pass = NULL;
    						rest = NULL;
    					}
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    			/* Compare user, pass AND context */
    			if (user && *user && !strcmp(user, vmu->mailbox) &&
    				 pass && !strcmp(pass, vmu->password) &&
    				 currcontext && *currcontext && !strcmp(currcontext, vmu->context)) {
    
    				/* This is the line */
    				if (rest) {
    
    					fprintf(configout, "%s => %s,%s", vmu->mailbox,newpassword,rest);
    				} else {
    					fprintf(configout, "%s => %s", vmu->mailbox,newpassword);
    				}
    				/* If there was a comment on the line print it out */
    				if (comment) {
    					fprintf(configout, ";%s\n", comment);
    
    Mark Spencer's avatar
    Mark Spencer committed
    				} else {
    
    Mark Spencer's avatar
    Mark Spencer committed
    				}
    
    			} else {
    				/* Put it back like it was */
    
    				fprintf(configout, "%s", orig);
    
    Mark Spencer's avatar
    Mark Spencer committed
    			}
    
    		}
    	}
    	fclose(configin);
    	fclose(configout);
    
    	stat((char *)tmpin, &statbuf);
    	chmod((char *)tmpout, statbuf.st_mode);
    	chown((char *)tmpout, statbuf.st_uid, statbuf.st_gid);
    	unlink((char *)tmpin);
    	rename((char *)tmpout,(char *)tmpin);
    
    	reset_user_pw(vmu->context, vmu->mailbox, newpassword);
    
    	ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
    
    static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
    {
    	char buf[255];
    	snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
    
    	if (!ast_safe_system(buf))
    		ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
    
    static int make_dir(char *dest, int len, char *context, char *ext, char *mailbox)
    {
    
    	return snprintf(dest, len, "%s%s/%s/%s", VM_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);
    
    /* only return failure if ast_lock_path returns 'timeout',
       not if the path does not exist or any other reason
    */
    static int vm_lock_path(const char *path)
    {
    	switch (ast_lock_path(path)) {
    	case AST_LOCK_TIMEOUT:
    		return -1;
    	default:
    		return 0;
    	}
    }
    
    
    
    #ifdef USE_ODBC_STORAGE
    static int retrieve_file(char *dir, int msgnum)
    {
    	int x = 0;
    	int res;
    	int fd=-1;
    	size_t fdlen = 0;
    	void *fdm=NULL;
    	SQLSMALLINT colcount=0;
    	SQLHSTMT stmt;
    	char sql[256];
    	char fmt[80]="";
    	char *c;
    	char coltitle[256];
    	SQLSMALLINT collen;
    	SQLSMALLINT datatype;
    	SQLSMALLINT decimaldigits;
    	SQLSMALLINT nullable;
    	SQLULEN colsize;
    	FILE *f=NULL;
    	char rowdata[80];
    	char fn[256];
    	char full_fn[256];
    	char msgnums[80];
    	
    	odbc_obj *obj;
    
    	obj = fetch_odbc_obj(odbc_database, 0);
    
    		ast_copy_string(fmt, vmfmts, sizeof(fmt));
    
    		c = strchr(fmt, '|');
    		if (c)
    			*c = '\0';
    		if (!strcasecmp(fmt, "wav49"))
    
    			strcpy(fmt, "WAV");
    
    		snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
    		if (msgnum > -1)
    			make_file(fn, sizeof(fn), dir, msgnum);
    		else
    
    			ast_copy_string(fn, dir, sizeof(fn));
    
    		snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
    		f = fopen(full_fn, "w+");
    		snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
    		res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
    			goto yuck;
    		}
    
    		snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
    
    		res = SQLPrepare(stmt, sql, SQL_NTS);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
    		SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
    
    		res = odbc_smart_execute(obj, stmt);
    
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    
    		res = SQLFetch(stmt);
    		if (res == SQL_NO_DATA) {
    
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    
    		else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC);
    		if (fd < 0) {
    			ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		res = SQLNumResultCols(stmt, &colcount);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {	
    			ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		if (f) 
    			fprintf(f, "[message]\n");
    		for (x=0;x<colcount;x++) {
    			rowdata[0] = '\0';
    			collen = sizeof(coltitle);
    			res = SQLDescribeCol(stmt, x + 1, coltitle, sizeof(coltitle), &collen, 
    						&datatype, &colsize, &decimaldigits, &nullable);
    
    			if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    
    				ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
    
    				SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    				goto yuck;
    			}
    
    			if (!strcasecmp(coltitle, "recording")) {
    				res = SQLGetData(stmt, x + 1, SQL_BINARY, NULL, 0, &colsize);
    				fdlen = colsize;
    				fd = open(full_fn, O_RDWR | O_TRUNC | O_CREAT, 0770);
    				if (fd > -1) {
    					char tmp[1]="";
    					lseek(fd, fdlen - 1, SEEK_SET);
    					if (write(fd, tmp, 1) != 1) {
    						close(fd);
    						fd = -1;
    
    					if (fd > -1)
    						fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    				}
    				if (fdm) {
    					memset(fdm, 0, fdlen);
    					res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
    
    					if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    						ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    						SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    						goto yuck;
    					}
    				}
    
    			} else {
    				res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    				if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    					ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    					SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    					goto yuck;
    				}
    				if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
    					fprintf(f, "%s=%s\n", coltitle, rowdata);
    
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    	} else
    		ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    yuck:	
    	if (f)
    		fclose(f);
    	if (fdm)
    		munmap(fdm, fdlen);
    	if (fd > -1)
    		close(fd);
    	return x - 1;
    }
    
    static int remove_file(char *dir, int msgnum)
    {
    	char fn[256];
    	char full_fn[256];
    	char msgnums[80];
    	
    	if (msgnum > -1) {
    		snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
    		make_file(fn, sizeof(fn), dir, msgnum);
    	} else
    
    		ast_copy_string(fn, dir, sizeof(fn));
    
    	ast_filedelete(fn, NULL);	
    	snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
    	unlink(full_fn);
    	return 0;
    }
    
    
    static int last_message_index(struct ast_vm_user *vmu, char *dir)
    
    {
    	int x = 0;
    	int res;
    	SQLHSTMT stmt;
    	char sql[256];
    	char rowdata[20];
    	
    	odbc_obj *obj;
    
    	obj = fetch_odbc_obj(odbc_database, 0);
    
    	if (obj) {
    		res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
    			goto yuck;
    		}
    
    		snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
    
    		res = SQLPrepare(stmt, sql, SQL_NTS);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
    
    		res = odbc_smart_execute(obj, stmt);
    
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		res = SQLFetch(stmt);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    		res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    		if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
    			ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    			SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    			goto yuck;
    		}
    
    			ast_log(LOG_WARNING, "Failed to read message count!\n");
    		SQLFreeHandle (SQL_HANDLE_STMT, stmt);
    	} else
    		ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    yuck:	
    	return x - 1;
    }
    
    static int message_exists(char *dir, int msgnum)