Skip to content
Snippets Groups Projects
app_voicemail.c 523 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	long debug;
    	char tmp[256];
    
    		ast_log(LOG_ERROR, "vm_state is NULL!\n");
    
    	ast_debug(3, "vm_state user is:%s\n", vms->imapuser);
    
    	if (vms->mailstream == NIL || !vms->mailstream) {
    
    		ast_debug(1, "mailstream not set.\n");
    
    	} else {
    		stream = vms->mailstream;
    	}
    	/* debug = T;  user wants protocol telemetry? */
    	debug = NIL;  /* NO protocol telemetry? */
    
    	if (delimiter == '\0') {		/* did not probe the server yet */
    		char *cp;
    #ifdef USE_SYSTEM_IMAP
    #include <imap/linkage.c>
    #elif defined(USE_SYSTEM_CCLIENT)
    #include <c-client/linkage.c>
    #else
    #include "linkage.c"
    
    		/* Connect to INBOX first to get folders delimiter */
    		imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
    		ast_mutex_lock(&vms->lock);
    
    		stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
    
    		ast_mutex_unlock(&vms->lock);
    		if (stream == NIL) {
    
    			ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
    
    		get_mailbox_delimiter(vms, stream);
    
    		/* update delimiter in imapfolder */
    
    		for (cp = vms->imapfolder; *cp; cp++)
    
    			if (*cp == '/')
    				*cp = delimiter;
    	}
    	/* Now connect to the target folder */
    	imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
    
    	ast_debug(3, "Before mail_open, server: %s, box:%d\n", tmp, box);
    
    	ast_mutex_lock(&vms->lock);
    
    	vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
    
    	/* Create the folder if it dosn't exist */
    	if (vms->mailstream && !mail_status(vms->mailstream, tmp, SA_UIDNEXT)) {
    		mail_create(vms->mailstream, tmp);
    	}
    
    	ast_mutex_unlock(&vms->lock);
    	if (vms->mailstream == NIL) {
    		return -1;
    
    static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
    
    	SEARCHPGM *pgm;
    	SEARCHHEADER *hdr;
    
    
    	/* If Urgent, then look at INBOX */
    	if (box == 11) {
    		box = NEW_FOLDER;
    		urgent = 1;
    
    	ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
    
    	ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
    
    	ast_copy_string(vms->imapserver, vmu->imapserver, sizeof(vms->imapserver));
    	ast_copy_string(vms->imapport, vmu->imapport, sizeof(vms->imapport));
    	ast_copy_string(vms->imapflags, vmu->imapflags, sizeof(vms->imapflags));
    
    	vms->imapversion = vmu->imapversion;
    
    	ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
    
    	if (init_mailstream(vms, box) || !vms->mailstream) {
    
    		ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
    		return -1;
    	}
    
    	create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
    
    	/* Check Quota */
    	if  (box == 0)  {
    
    		ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
    		check_quota(vms, (char *) mbox(vmu, box));
    
    	ast_mutex_lock(&vms->lock);
    
    	pgm = mail_newsearchpgm();
    
    	/* Check IMAP folder for Asterisk messages only... */
    
    	hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
    
    	hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
    
    	pgm->header = hdr;
    	pgm->deleted = 0;
    	pgm->undeleted = 1;
    
    	/* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
    	if (box == NEW_FOLDER && urgent == 1) {
    		pgm->unseen = 1;
    		pgm->seen = 0;
    		pgm->flagged = 1;
    		pgm->unflagged = 0;
    	} else if (box == NEW_FOLDER && urgent == 0) {
    		pgm->unseen = 1;
    		pgm->seen = 0;
    		pgm->flagged = 0;
    		pgm->unflagged = 1;
    	} else if (box == OLD_FOLDER) {
    		pgm->seen = 1;
    		pgm->unseen = 0;
    
    	ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
    
    	vms->vmArrayIndex = 0;
    	mail_search_full (vms->mailstream, NULL, pgm, NIL);
    	vms->lastmsg = vms->vmArrayIndex - 1;
    	mail_free_searchpgm(&pgm);
    
    	/* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
    	 * ensure to allocate enough space to account for all of them. Warn if old messages
    	 * have not been checked first as that is required.
    	 */
    	if (box == 0 && !vms->dh_arraysize) {
    		ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
    	}
    	if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
    
    		ast_mutex_unlock(&vms->lock);
    
    	ast_mutex_unlock(&vms->lock);
    
    static void write_file(char *filename, char *buffer, unsigned long len)
    
    	if (!filename || !buffer) {
    		return;
    	}
    
    	if (!(output = fopen(filename, "w"))) {
    		ast_log(LOG_ERROR, "Unable to open/create file %s: %s\n", filename, strerror(errno));
    		return;
    	}
    
    
    	if (fwrite(buffer, len, 1, output) != 1) {
    
    			ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
    
    static void update_messages_by_imapuser(const char *user, unsigned long number)
    
    	struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
    
    	if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
    		return;
    
    
    	ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
    
    
    	/* Ensure we have room for the next message. */
    	if (vms->vmArrayIndex >= vms->msg_array_max) {
    		long *new_mem = ast_realloc(vms->msgArray, 2 * vms->msg_array_max * sizeof(long));
    		if (!new_mem) {
    			return;
    		}
    		vms->msgArray = new_mem;
    		vms->msg_array_max *= 2;
    	}
    
    
    	vms->msgArray[vms->vmArrayIndex++] = number;
    
    void mm_searched(MAILSTREAM *stream, unsigned long number)
    {
    	char *mailbox = stream->mailbox, buf[1024] = "", *user;
    
    	if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
    		return;
    
    	update_messages_by_imapuser(user, number);
    
    static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
    
    	struct ast_variable *var;
    	struct ast_vm_user *vmu;
    
    	vmu = ast_calloc(1, sizeof *vmu);
    	if (!vmu)
    		return NULL;
    
    	populate_defaults(vmu);
    
    
    	var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
    	if (var) {
    		apply_options_full(vmu, var);
    		ast_variables_destroy(var);
    		return vmu;
    	} else {
    
    		ast_free(vmu);
    
    /* Interfaces to C-client */
    
    void mm_exists(MAILSTREAM * stream, unsigned long number)
    {
    	/* mail_ping will callback here if new mail! */
    	ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
    	if (number == 0) return;
    	set_update(stream);
    }
    
    void mm_expunged(MAILSTREAM * stream, unsigned long number)
    {
    	/* mail_ping will callback here if expunged mail! */
    	ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
    	if (number == 0) return;
    	set_update(stream);
    }
    
    void mm_flags(MAILSTREAM * stream, unsigned long number)
    {
    	/* mail_ping will callback here if read mail! */
    	ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
    	if (number == 0) return;
    	set_update(stream);
    }
    
    void mm_notify(MAILSTREAM * stream, char *string, long errflg)
    {
    	ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
    	mm_log (string, errflg);
    }
    
    
    void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
    {
    	if (delimiter == '\0') {
    		delimiter = delim;
    
    	ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
    
    	if (attributes & LATT_NOINFERIORS)
    		ast_debug(5, "no inferiors\n");
    	if (attributes & LATT_NOSELECT)
    		ast_debug(5, "no select\n");
    	if (attributes & LATT_MARKED)
    		ast_debug(5, "marked\n");
    	if (attributes & LATT_UNMARKED)
    		ast_debug(5, "unmarked\n");
    }
    
    void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
    
    	ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
    
    	if (attributes & LATT_NOINFERIORS)
    		ast_debug(5, "no inferiors\n");
    	if (attributes & LATT_NOSELECT)
    		ast_debug(5, "no select\n");
    	if (attributes & LATT_MARKED)
    		ast_debug(5, "marked\n");
    	if (attributes & LATT_UNMARKED)
    		ast_debug(5, "unmarked\n");
    
    
    void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
    
    
    	if (!DEBUG_ATLEAST(5) || !(str = ast_str_create(256))) {
    
    	    return;
    	}
    
    	ast_str_append(&str, 0, " Mailbox %s", mailbox);
    	if (status->flags & SA_MESSAGES) {
    		ast_str_append(&str, 0, ", %lu messages", status->messages);
    	}
    	if (status->flags & SA_RECENT) {
    		ast_str_append(&str, 0, ", %lu recent", status->recent);
    	}
    	if (status->flags & SA_UNSEEN) {
    		ast_str_append(&str, 0, ", %lu unseen", status->unseen);
    	}
    	if (status->flags & SA_UIDVALIDITY) {
    		ast_str_append(&str, 0, ", %lu UID validity", status->uidvalidity);
    	}
    	if (status->flags & SA_UIDNEXT) {
    		ast_str_append(&str, 0, ", %lu next UID", status->uidnext);
    	}
    	ast_log(LOG_DEBUG, "%s\n", ast_str_buffer(str));
    
    	ast_free(str);
    
    
    void mm_log(char *string, long errflg)
    
    	switch ((short) errflg) {
    		case NIL:
    
    			ast_debug(1, "IMAP Info: %s\n", string);
    
    			break;
    		case PARSE:
    		case WARN:
    			ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
    			break;
    		case ERROR:
    			ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
    
    
    void mm_dlog(char *string)
    
    	ast_log(AST_LOG_NOTICE, "%s\n", string);
    }
    
    
    void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
    {
    	struct ast_vm_user *vmu;
    
    	ast_debug(4, "Entering callback mm_login\n");
    
    	ast_copy_string(user, mb->user, MAILTMPLEN);
    
    	/* We should only do this when necessary */
    	if (!ast_strlen_zero(authpassword)) {
    		ast_copy_string(pwd, authpassword, MAILTMPLEN);
    	} else {
    		AST_LIST_TRAVERSE(&users, vmu, list) {
    			if (!strcasecmp(mb->user, vmu->imapuser)) {
    				ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
    
    			}
    		}
    		if (!vmu) {
    			if ((vmu = find_user_realtime_imapuser(mb->user))) {
    				ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
    				free_user(vmu);
    			}
    
    
    void mm_critical(MAILSTREAM * stream)
    
    
    void mm_nocritical(MAILSTREAM * stream)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
    {
    	kill (getpid (), SIGSTOP);
    	return NIL;
    }
    
    
    void mm_fatal(char *string)
    {
    	ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
    }
    
    /* C-client callback to handle quota */
    static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
    {
    	struct vm_state *vms;
    	char *mailbox = stream->mailbox, *user;
    	char buf[1024] = "";
    	unsigned long usage = 0, limit = 0;
    
    	while (pquota) {
    		usage = pquota->usage;
    		limit = pquota->limit;
    		pquota = pquota->next;
    
    	if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || (!(vms = get_vm_state_by_imapuser(user, 2)) && !(vms = get_vm_state_by_imapuser(user, 0)))) {
    
    		ast_log(AST_LOG_ERROR, "No state found.\n");
    		return;
    
    	ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
    
    	vms->quota_usage = usage;
    	vms->quota_limit = limit;
    }
    
    static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
    {
    	char *start, *eol_pnt;
    	int taglen;
    
    	if (ast_strlen_zero(header) || ast_strlen_zero(tag))
    		return NULL;
    
    	taglen = strlen(tag) + 1;
    	if (taglen < 1)
    		return NULL;
    
    
    		return NULL;
    
    	/* Since we can be called multiple times we should clear our buffer */
    	memset(buf, 0, len);
    
    	ast_copy_string(buf, start+taglen, len);
    	if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
    		*eol_pnt = '\0';
    	return buf;
    }
    
    static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
    {
    
    	char *start, *eol_pnt, *quote;
    
    
    	if (ast_strlen_zero(mailbox))
    		return NULL;
    
    	if (!(start = strstr(mailbox, "/user=")))
    		return NULL;
    
    	ast_copy_string(buf, start+6, len);
    
    
    	if (!(quote = strchr(buf, '"'))) {
    		if ((eol_pnt = strchr(buf, '/')) || (eol_pnt = strchr(buf, '}'))) {
    			*eol_pnt = '\0';
    		}
    
    		if ((eol_pnt = strchr(quote + 1, '"'))) {
    			*eol_pnt = '\0';
    		}
    		return quote + 1;
    
    }
    
    static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
    {
    	struct vm_state *vms_p;
    
    
    	pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
    
    	if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
    		return vms_p;
    	}
    
    	ast_debug(5, "Adding new vmstate for %s\n", vmu->imapuser);
    
    	/* XXX: Is this correctly freed always? */
    
    	if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
    		return NULL;
    	ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
    
    	ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
    
    	ast_copy_string(vms_p->imapserver, vmu->imapserver, sizeof(vms_p->imapserver));
    	ast_copy_string(vms_p->imapport, vmu->imapport, sizeof(vms_p->imapport));
    	ast_copy_string(vms_p->imapflags, vmu->imapflags, sizeof(vms_p->imapflags));
    
    	ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
    
    	ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
    
    	vms_p->mailstream = NIL; /* save for access from interactive entry point */
    
    	vms_p->imapversion = vmu->imapversion;
    
    	ast_debug(5, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
    
    	vms_p->updated = 1;
    	/* set mailbox to INBOX! */
    
    	ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
    
    	init_vm_state(vms_p);
    	vmstate_insert(vms_p);
    	return vms_p;
    }
    
    
    static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
    
    {
    	struct vmstate *vlist = NULL;
    
    
    	if (interactive) {
    		struct vm_state *vms;
    
    		pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
    
    		if ((vms = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms->imapuser, user)) {
    
    	AST_LIST_LOCK(&vmstates);
    	AST_LIST_TRAVERSE(&vmstates, vlist, list) {
    		if (!vlist->vms) {
    			ast_debug(3, "error: vms is NULL for %s\n", user);
    			continue;
    
    		if (vlist->vms->imapversion != imapversion) {
    			continue;
    		}
    
    		if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
    			AST_LIST_UNLOCK(&vmstates);
    			return vlist->vms;
    
    	AST_LIST_UNLOCK(&vmstates);
    
    	ast_debug(3, "%s not found in vmstates\n", user);
    
    static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
    
    	struct vmstate *vlist = NULL;
    
    	const char *local_context = S_OR(context, "default");
    
    	if (interactive) {
    		struct vm_state *vms;
    
    		pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
    
    		if ((vms = pthread_getspecific(ts_vmstate.key)) &&
    
    		    !strcmp(vms->username,mailbox) && !strcmp(vms->context, local_context)) {
    			return vms;
    		}
    
    	AST_LIST_LOCK(&vmstates);
    	AST_LIST_TRAVERSE(&vmstates, vlist, list) {
    		if (!vlist->vms) {
    			ast_debug(3, "error: vms is NULL for %s\n", mailbox);
    			continue;
    		}
    
    		if (vlist->vms->imapversion != imapversion) {
    			continue;
    		}
    
    		ast_debug(3, "comparing mailbox %s@%s (i=%d) to vmstate mailbox %s@%s (i=%d)\n", mailbox, local_context, interactive, vlist->vms->username, vlist->vms->context, vlist->vms->interactive);
    
    		if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
    
    			ast_debug(3, "Found it!\n");
    			AST_LIST_UNLOCK(&vmstates);
    			return vlist->vms;
    
    	AST_LIST_UNLOCK(&vmstates);
    
    	ast_debug(3, "%s not found in vmstates\n", mailbox);
    
    	return NULL;
    
    static void vmstate_insert(struct vm_state *vms)
    
    	struct vmstate *v;
    	struct vm_state *altvms;
    
    	/* If interactive, it probably already exists, and we should
    	   use the one we already have since it is more up to date.
    	   We can compare the username to find the duplicate */
    	if (vms->interactive == 1) {
    
    		altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
    
    			ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
    
    			vms->newmessages = altvms->newmessages;
    			vms->oldmessages = altvms->oldmessages;
    			vms->vmArrayIndex = altvms->vmArrayIndex;
    
    			/* XXX: no msgArray copying? */
    
    			vms->lastmsg = altvms->lastmsg;
    			vms->curmsg = altvms->curmsg;
    			/* get a pointer to the persistent store */
    			vms->persist_vms = altvms;
    			/* Reuse the mailstream? */
    
    #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
    
    			vms->mailstream = altvms->mailstream;
    
    #else
    			vms->mailstream = NIL;
    #endif
    
    Mark Spencer's avatar
    Mark Spencer committed
    	}
    
    
    	if (!(v = ast_calloc(1, sizeof(*v))))
    		return;
    
    	ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
    
    
    	AST_LIST_LOCK(&vmstates);
    	AST_LIST_INSERT_TAIL(&vmstates, v, list);
    	AST_LIST_UNLOCK(&vmstates);
    
    static void vmstate_delete(struct vm_state *vms)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct vmstate *vc = NULL;
    	struct vm_state *altvms = NULL;
    
    Mark Spencer's avatar
    Mark Spencer committed
    
    
    	/* If interactive, we should copy pertinent info
    	   back to the persistent state (to make update immediate) */
    	if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
    		ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
    		altvms->newmessages = vms->newmessages;
    		altvms->oldmessages = vms->oldmessages;
    		altvms->updated = 1;
    
    		vms->mailstream = mail_close(vms->mailstream);
    
    		/* Interactive states are not stored within the persistent list */
    		return;
    
    	ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
    
    	AST_LIST_LOCK(&vmstates);
    	AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
    		if (vc->vms == vms) {
    			AST_LIST_REMOVE_CURRENT(list);
    			break;
    
    	AST_LIST_TRAVERSE_SAFE_END
    	AST_LIST_UNLOCK(&vmstates);
    
    	if (vc) {
    		ast_mutex_destroy(&vc->vms->lock);
    
    		ast_free(vc->vms->msgArray);
    		vc->vms->msgArray = NULL;
    		vc->vms->msg_array_max = 0;
    		/* XXX: is no one supposed to free vms itself? */
    
    		ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
    
    static void set_update(MAILSTREAM * stream)
    
    Mark Spencer's avatar
    Mark Spencer committed
    {
    
    	struct vm_state *vms;
    	char *mailbox = stream->mailbox, *user;
    	char buf[1024] = "";
    
    	if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
    
    			ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
    		return;
    	}
    
    	ast_debug(3, "User %s mailbox set for update.\n", user);
    
    	vms->updated = 1; /* Set updated flag since mailbox changed */
    }
    
    static void init_vm_state(struct vm_state *vms)
    
    	vms->msg_array_max = VMSTATE_MAX_MSG_ARRAY;
    	vms->msgArray = ast_calloc(vms->msg_array_max, sizeof(long));
    	if (!vms->msgArray) {
    		/* Out of mem? This can't be good. */
    		vms->msg_array_max = 0;
    
    	ast_mutex_init(&vms->lock);
    }
    
    static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
    
    {
    	char *body_content;
    	char *body_decoded;
    	char *fn = is_intro ? vms->introfn : vms->fn;
    
    	unsigned long len = 0;
    	unsigned long newlen = 0;
    
    	char filename[256];
    
    	if (!body || body == NIL)
    		return -1;
    
    
    	ast_mutex_lock(&vms->lock);
    
    	body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
    
    	ast_mutex_unlock(&vms->lock);
    
    	if (len > MAX_MAIL_BODY_CONTENT_SIZE) {
    		ast_log(AST_LOG_ERROR,
    			"Msgno %ld, section %s. The body's content size %ld is huge (max %ld). User:%s, mailbox %s\n",
    			vms->msgArray[vms->curmsg], section, len, MAX_MAIL_BODY_CONTENT_SIZE, vms->imapuser, vms->username);
    		return -1;
    	}
    	if (body_content != NIL && len) {
    
    		snprintf(filename, sizeof(filename), "%s.%s", fn, format);
    
    		/* ast_debug(1, body_content); */
    
    		body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
    
    		/* If the body of the file is empty, return an error */
    
    		if (!newlen || !body_decoded) {
    
    		write_file(filename, (char *) body_decoded, newlen);
    
    		ast_debug(5, "Body of message is NULL.\n");
    		return -1;
    
    /*!
     * \brief Get delimiter via mm_list callback
    
     * \param vms		The voicemail state object
    
     * \param stream
     *
     * Determines the delimiter character that is used by the underlying IMAP based mail store.
     */
    
    /* MUTEX should already be held */
    
    static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream) {
    
    	snprintf(tmp, sizeof(tmp), "{%s}", S_OR(vms->imapserver, imapserver));
    
    	mail_list(stream, tmp, "*");
    }
    
    /*!
     * \brief Check Quota for user
    
     * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
     * \param mailbox the mailbox to check the quota for.
     *
     * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
     */
    static void check_quota(struct vm_state *vms, char *mailbox) {
    
    	ast_mutex_lock(&vms->lock);
    
    	mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
    	ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
    	if (vms && vms->mailstream != NULL) {
    		imap_getquotaroot(vms->mailstream, mailbox);
    	} else {
    		ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
    	}
    
    	ast_mutex_unlock(&vms->lock);
    
    #endif /* IMAP_STORAGE */
    
    /*! \brief Lock file path
    
    Tilghman Lesher's avatar
     
    Tilghman Lesher committed
     * 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;
    	}
    
    #define MSG_ID_LEN 256
    
    /* Used to attach a unique identifier to an msg_id */
    static int msg_id_incrementor;
    
    /*!
     * \brief Sets the destination string to a uniquely identifying msg_id string
     * \param dst pointer to a character buffer that should contain MSG_ID_LEN characters.
     */
    static void generate_msg_id(char *dst);
    
    
    #ifdef ODBC_STORAGE
    struct generic_prepare_struct {
    	char *sql;
    	int argc;
    	char **argv;
    };
    
    static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
    
    	struct generic_prepare_struct *gps = data;
    	int res, i;
    	SQLHSTMT stmt;
    
    	res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
    
    	if (!SQL_SUCCEEDED(res)) {
    
    		ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
    		return NULL;
    	}
    
    	res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
    
    	if (!SQL_SUCCEEDED(res)) {
    
    		ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    		return NULL;
    	}
    	for (i = 0; i < gps->argc; i++)
    		SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
    
    	return stmt;
    
    static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
    {
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	struct odbc_obj *obj;
    	char msg_num_str[20];
    	char *argv[] = { msg_id, dir, msg_num_str };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv };
    
    	obj = ast_odbc_request_obj(odbc_database, 0);
    	if (!obj) {
    		ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir);
    		return;
    	}
    
    	snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num);
    	snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table);
    	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
    	if (!stmt) {
    		ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
    	} else {
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    	}
    	ast_odbc_release_obj(obj);
    	return;
    }
    
    
     * \brief Retrieves a file from an ODBC data store.
    
     * \param dir the path to the file to be retrieved.
    
     * \param msgnum the message number, such as within a mailbox folder.
    
     * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
     * The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
    
     * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
     * The output is the message information file with the name msgnum and the extension .txt
     * and the message file with the extension of its format, in the directory with base file name of the msgnum.
    
     * \return 0 on success, -1 on error.
    
    static int retrieve_file(char *dir, int msgnum)
    
    	size_t fdlen = 0;
    	void *fdm = MAP_FAILED;
    
    	char sql[PATH_MAX];
    
    	char fmt[80] = "";
    
    	char *c;
    	char coltitle[256];
    	SQLSMALLINT collen;
    	SQLSMALLINT datatype;
    	SQLSMALLINT decimaldigits;
    	SQLSMALLINT nullable;
    	SQLULEN colsize;
    	SQLLEN colsize2;
    
    	char rowdata[80];
    	char fn[PATH_MAX];
    	char full_fn[PATH_MAX];
    	char msgnums[80];
    	char *argv[] = { dir, msgnums };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
    	struct odbc_obj *obj;
    
    	obj = ast_odbc_request_obj(odbc_database, 0);
    
    	if (!obj) {
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    		return -1;
    	}
    
    	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));
    
    	/* Create the information file */
    	snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
    
    	if (!(f = fopen(full_fn, "w+"))) {
    		ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
    		goto bail;
    	}
    
    	snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
    	snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
    
    	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
    	if (!stmt) {
    		ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
    		goto bail;
    	}
    
    	res = SQLFetch(stmt);
    	if (!SQL_SUCCEEDED(res)) {
    		if (res != SQL_NO_DATA) {
    
    			ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    
    		goto bail_with_handle;
    	}
    
    	fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
    	if (fd < 0) {
    		ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
    		goto bail_with_handle;
    	}
    
    	res = SQLNumResultCols(stmt, &colcount);
    	if (!SQL_SUCCEEDED(res)) {
    		ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	fprintf(f, "[message]\n");
    	for (x = 0; x < colcount; x++) {
    		rowdata[0] = '\0';
    		colsize = 0;
    		collen = sizeof(coltitle);
    		res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
    							&datatype, &colsize, &decimaldigits, &nullable);
    		if (!SQL_SUCCEEDED(res)) {
    			ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
    			goto bail_with_handle;
    		}
    		if (!strcasecmp(coltitle, "recording")) {
    			off_t offset;
    			res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
    			fdlen = colsize2;
    			if (fd > -1) {
    				char tmp[1] = "";
    				lseek(fd, fdlen - 1, SEEK_SET);
    				if (write(fd, tmp, 1) != 1) {
    					close(fd);
    					fd = -1;
    					continue;
    				}
    				/* Read out in small chunks */
    				for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
    					if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
    						ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
    						goto bail_with_handle;
    
    					res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
    					munmap(fdm, CHUNKSIZE);
    					if (!SQL_SUCCEEDED(res)) {
    						ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    						unlink(full_fn);
    						goto bail_with_handle;
    
    				if (truncate(full_fn, fdlen) < 0) {
    					ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
    
    			}
    		} else {
    			res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    			if (res == SQL_NULL_DATA && !strcasecmp(coltitle, "msg_id")) {
    				char msg_id[MSG_ID_LEN];
    				generate_msg_id(msg_id);
    				snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
    				odbc_update_msg_id(dir, msgnum, msg_id);
    			} else if (!SQL_SUCCEEDED(res)) {
    				ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
    				goto bail_with_handle;
    			}
    			if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir")) {
    				fprintf(f, "%s=%s\n", coltitle, rowdata);
    
    	}
    
    bail_with_handle:
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    bail:
    
    	if (f)
    		fclose(f);
    	if (fd > -1)
    		close(fd);