Skip to content
Snippets Groups Projects
app_voicemail.c 523 KiB
Newer Older
  • Learn to ignore specific revisions
  • 	ast_odbc_release_obj(obj);
    
    
    /*!
     * \brief Determines the highest message number in use for a given user and mailbox folder.
    
     * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
     *
     * This method is used when mailboxes are stored in an ODBC back end.
     * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
     *
    
     * \return the value of zero or greater to indicate the last message index in use, -1 to indicate none.
    
    
     */
    static int last_message_index(struct ast_vm_user *vmu, char *dir)
    {
    
    	int res;
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	char rowdata[20];
    	char *argv[] = { dir };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .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;
    	}
    
    	snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", 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_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
    		} else {
    			ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    
    		goto bail_with_handle;
    	}
    
    	res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    	if (!SQL_SUCCEEDED(res)) {
    		ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	if (sscanf(rowdata, "%30d", &x) != 1) {
    		ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
    	}
    
    bail_with_handle:
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    bail:
    	ast_odbc_release_obj(obj);
    
    	return x;
    
     * \brief Determines if the specified message exists.
    
     * \param dir the folder the mailbox folder to look for messages.
    
     * \param msgnum the message index to query for.
     *
     * This method is used when mailboxes are stored in an ODBC back end.
     *
     * \return greater than zero if the message exists, zero when the message does not exist or on error.
    
    static int message_exists(char *dir, int msgnum)
    
    	char sql[PATH_MAX];
    
    	char msgnums[20];
    	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);
    
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    
    		return 0;
    	}
    
    	snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
    	snprintf(sql, sizeof(sql), "SELECT COUNT(*) 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)) {
    		ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    	if (!SQL_SUCCEEDED(res)) {
    		ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	if (sscanf(rowdata, "%30d", &x) != 1) {
    		ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
    	}
    
    bail_with_handle:
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    bail:
    	ast_odbc_release_obj(obj);
    
     * \brief returns the number of messages found.
    
     * \param vmu
     * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
     *
     * This method is used when mailboxes are stored in an ODBC back end.
     *
    
     * \return The count of messages being zero or more, less than zero on error.
    
    static int count_messages(struct ast_vm_user *vmu, char *dir)
    
    	int res;
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	char rowdata[20];
    	char *argv[] = { dir };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
    	struct odbc_obj *obj;
    
    	obj = ast_odbc_request_obj(odbc_database, 0);
    
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    
    		return -1;
    	}
    
    	snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", 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)) {
    		ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
    	if (!SQL_SUCCEEDED(res)) {
    		ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
    		goto bail_with_handle;
    	}
    
    	if (sscanf(rowdata, "%30d", &x) != 1) {
    		ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
    	}
    
    bail_with_handle:
    	SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    bail:
    	ast_odbc_release_obj(obj);
    	return x;
    
    /*!
     * \brief Deletes a message from the mailbox folder.
     * \param sdir The mailbox folder to work in.
     * \param smsg The message index to be deleted.
     *
     * This method is used when mailboxes are stored in an ODBC back end.
     * The specified message is directly deleted from the database 'voicemessages' table.
    
     * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
     */
    
    static void delete_file(const char *sdir, int smsg)
    
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	char msgnums[20];
    
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
    	struct odbc_obj *obj;
    
    	obj = ast_odbc_request_obj(odbc_database, 0);
    
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    
    		return;
    	}
    
    	argv[0] = ast_strdupa(sdir);
    
    	snprintf(msgnums, sizeof(msgnums), "%d", smsg);
    	snprintf(sql, sizeof(sql), "DELETE 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);
    	} else {
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    	}
    	ast_odbc_release_obj(obj);
    
    	return;
    
    /*!
     * \brief Copies a voicemail from one mailbox to another.
     * \param sdir the folder for which to look for the message to be copied.
     * \param smsg the index of the message to be copied.
     * \param ddir the destination folder to copy the message into.
     * \param dmsg the index to be used for the copied message.
     * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
     * \param dmailboxcontext The context for the destination user.
     *
     * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
     */
    static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
    {
    	SQLHSTMT stmt;
    	char sql[512];
    	char msgnums[20];
    	char msgnumd[20];
    
    	char msg_id[MSG_ID_LEN];
    
    	struct odbc_obj *obj;
    
    	char *argv[] = { ddir, msgnumd, msg_id, dmailboxuser, dmailboxcontext, sdir, msgnums };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 7, .argv = argv };
    
    	generate_msg_id(msg_id);
    
    	delete_file(ddir, dmsg);
    	obj = ast_odbc_request_obj(odbc_database, 0);
    
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    
    		return;
    	}
    
    	snprintf(msgnums, sizeof(msgnums), "%d", smsg);
    	snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
    	snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
    	stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
    	if (!stmt)
    		ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
    	else
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    	ast_odbc_release_obj(obj);
    
    	return;
    
    struct insert_data {
    	char *sql;
    
    	void *data;
    	SQLLEN datalen;
    	SQLLEN indlen;
    	const char *context;
    	const char *macrocontext;
    	const char *callerid;
    	const char *origtime;
    	const char *duration;
    
    	const char *mailboxuser;
    	const char *mailboxcontext;
    
    	const char *category;
    	const char *flag;
    
    static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
    {
    	struct insert_data *data = vdata;
    	int res;
    	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;
    
    	SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
    	SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
    	SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
    	SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
    	SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
    	SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
    	SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
    	SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
    	SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
    	SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
    	SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
    
    	SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL);
    
    	if (!ast_strlen_zero(data->category)) {
    
    		SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
    
    	res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
    
    	if (!SQL_SUCCEEDED(res)) {
    
    		ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    		return NULL;
    
     * \brief Stores a voicemail into the database.
     * \param dir the folder the mailbox folder to store the message.
     * \param mailboxuser the user owning the mailbox folder.
     * \param mailboxcontext
     * \param msgnum the message index for the message to be stored.
     *
     * This method is used when mailboxes are stored in an ODBC back end.
    
     * The message sound file and information file is looked up on the file system.
    
     * A SQL query is invoked to store the message into the (MySQL) database.
     *
     * \return the zero on success -1 on error.
    
    static int store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum)
    
    	int res = 0;
    	int fd = -1;
    	void *fdm = MAP_FAILED;
    
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	char msgnums[20];
    	char fn[PATH_MAX];
    	char full_fn[PATH_MAX];
    	char fmt[80]="";
    	char *c;
    
    	struct odbc_obj *obj;
    
    	struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
    
    		.context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" };
    
    	struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
    
    	delete_file(dir, msgnum);
    
    
    	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);
    
    	do {
    		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);
    		cfg = ast_config_load(full_fn, config_flags);
    		snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
    		fd = open(full_fn, O_RDWR);
    		if (fd < 0) {
    			ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
    			res = -1;
    			break;
    
    		if (valid_config(cfg)) {
    
    			if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
    				idata.context = "";
    			}
    			if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
    				idata.macrocontext = "";
    			}
    			if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
    				idata.callerid = "";
    			}
    			if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
    				idata.origtime = "";
    			}
    			if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
    				idata.duration = "";
    			}
    			if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
    				idata.category = "";
    			}
    			if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
    				idata.flag = "";
    			}
    
    			if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) {
    				idata.msg_id = "";
    			}
    
    		fdlen = lseek(fd, 0, SEEK_END);
    
    		if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) {
    			ast_log(AST_LOG_WARNING, "Failed to process sound file '%s': %s\n", full_fn, strerror(errno));
    			res = -1;
    			break;
    		}
    
    		fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    
    		if (fdm == MAP_FAILED) {
    
    			ast_log(AST_LOG_WARNING, "Memory map failed for sound file '%s'!\n", full_fn);
    
    		idata.data = fdm;
    		idata.datalen = idata.indlen = fdlen;
    
    		if (!ast_strlen_zero(idata.category))
    			snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
    
    			snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
    
    		if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
    
    			SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    
    		} else {
    			ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
    			res = -1;
    
    	if (valid_config(cfg))
    
    		ast_config_destroy(cfg);
    	if (fdm != MAP_FAILED)
    		munmap(fdm, fdlen);
    	if (fd > -1)
    		close(fd);
    	return res;
    }
    
    /*!
     * \brief Renames a message in a mailbox folder.
     * \param sdir The folder of the message to be renamed.
     * \param smsg The index of the message to be renamed.
     * \param mailboxuser The user to become the owner of the message after it is renamed. Usually this will be the same as the original owner.
     * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
     * \param ddir The destination folder for the message to be renamed into
     * \param dmsg The destination message for the message to be renamed.
     *
     * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
     * The is usually used to resequence the messages in the mailbox, such as to delete messag index 0, it would be called successively to slide all the other messages down one index.
     * But in theory, because the SQL query performs an update on (dir, msgnum, mailboxuser, mailboxcontext) in the database, it should be possible to have the message relocated to another mailbox or context as well.
     */
    static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
    {
    	SQLHSTMT stmt;
    	char sql[PATH_MAX];
    	char msgnums[20];
    	char msgnumd[20];
    	struct odbc_obj *obj;
    	char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
    	struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
    
    	delete_file(ddir, dmsg);
    
    	obj = ast_odbc_request_obj(odbc_database, 0);
    
    		ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
    
    		return;
    	}
    
    	snprintf(msgnums, sizeof(msgnums), "%d", smsg);
    	snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
    	snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? 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);
    	else
    		SQLFreeHandle(SQL_HANDLE_STMT, stmt);
    	ast_odbc_release_obj(obj);
    	return;
    
    /*!
     * \brief Removes a voicemail message file.
     * \param dir the path to the message file.
     * \param msgnum the unique number for the message within the mailbox.
     *
     * Removes the message content file and the information file.
     * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
    
     * Typical use is to clean up after a RETRIEVE operation.
    
     * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
     * \return zero on success, -1 on error.
     */
    static int remove_file(char *dir, int msgnum)
    {
    	char fn[PATH_MAX];
    	char full_fn[PATH_MAX];
    	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;
    }
    
    #else
    #ifndef IMAP_STORAGE
    
     * \brief Find all .txt files - even if they are not in sequence from 0000.
     * \param vmu
     * \param dir
    
     * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
     *
     * \return the count of messages, zero or more.
    
    static int count_messages(struct ast_vm_user *vmu, char *dir)
    
    	int vmcount = 0;
    	DIR *vmdir = NULL;
    	struct dirent *vment = NULL;
    
    	if (vm_lock_path(dir))
    		return ERROR_LOCK_PATH;
    
    	if ((vmdir = opendir(dir))) {
    		while ((vment = readdir(vmdir))) {
    			if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
    				vmcount++;
    
    	ast_unlock_path(dir);
    
    /*!
     * \brief Renames a message in a mailbox folder.
     * \param sfn The path to the mailbox information and data file to be renamed.
     * \param dfn The path for where the message data and information files will be renamed to.
    
     * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
    
    static void rename_file(char *sfn, char *dfn)
    
    	char stxt[PATH_MAX];
    	char dtxt[PATH_MAX];
    
    	ast_filerename(sfn, dfn, NULL);
    
    	snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
    	snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
    	if (ast_check_realtime("voicemail_data")) {
    		ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
    
    	rename(stxt, dtxt);
    
     * \brief Determines the highest message number in use for a given user and mailbox folder.
    
     * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
    
     * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
     * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
    
     * \note Should always be called with a lock already set on dir.
     * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
    
    static int last_message_index(struct ast_vm_user *vmu, char *dir)
    
    	int x;
    	unsigned char map[MAXMSGLIMIT] = "";
    	DIR *msgdir;
    	struct dirent *msgdirent;
    	int msgdirint;
    
    	char extension[4];
    
    	int stopcount = 0;
    
    
    	/* Reading the entire directory into a file map scales better than
    	 * doing a stat repeatedly on a predicted sequence.  I suspect this
    	 * is partially due to stat(2) internally doing a readdir(2) itself to
    	 * find each file. */
    
    	if (!(msgdir = opendir(dir))) {
    		return -1;
    	}
    
    
    	while ((msgdirent = readdir(msgdir))) {
    
    		if (sscanf(msgdirent->d_name, "msg%30d.%3s", &msgdirint, extension) == 2 && !strcmp(extension, "txt") && msgdirint < MAXMSGLIMIT) {
    
    			map[msgdirint] = 1;
    
    			stopcount++;
    			ast_debug(4, "%s map[%d] = %d, count = %d\n", dir, msgdirint, map[msgdirint], stopcount);
    		}
    
    	closedir(msgdir);
    
    	for (x = 0; x < vmu->maxmsg; x++) {
    
    		if (map[x] == 1) {
    			stopcount--;
    		} else if (map[x] == 0 && !stopcount) {
    
    Mark Michelson's avatar
    Mark Michelson committed
    #endif /* #ifndef IMAP_STORAGE */
    #endif /* #else of #ifdef ODBC_STORAGE */
    #ifndef IMAP_STORAGE
    
    /*!
     * \brief Utility function to copy a file.
     * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
     * \param outfile The path for which to copy the file to. The directory permissions must allow the creation (or truncation) of the file, and allow for opening the file in write only mode.
     *
     * When the compiler option HARDLINK_WHEN_POSSIBLE is set, the copy operation will attempt to use the hard link facility instead of copy the file (to save disk space). If the link operation fails, it falls back to the copy operation.
     * The copy operation copies up to 4096 bytes at once.
     *
     * \return zero on success, -1 on error.
     */
    static int copy(char *infile, char *outfile)
    
    	int len;
    	char buf[4096];
    
    #ifdef HARDLINK_WHEN_POSSIBLE
    	/* Hard link if possible; saves disk space & is faster */
    
    	if (!link(infile, outfile)) {
    		return 0;
    	}
    
    
    	if ((ifd = open(infile, O_RDONLY)) < 0) {
    		ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
    		return -1;
    	}
    
    	if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
    		ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
    		close(ifd);
    		return -1;
    	}
    
    	for (;;) {
    		int wrlen;
    
    		len = read(ifd, buf, sizeof(buf));
    		if (!len) {
    			res = 0;
    			break;
    
    
    		if (len < 0) {
    			ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
    			break;
    		}
    
    		wrlen = write(ofd, buf, len);
    		if (errno == ENOMEM || errno == ENOSPC || wrlen != len) {
    			ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, wrlen, len, strerror(errno));
    			break;
    
    
    	close(ifd);
    	close(ofd);
    	if (res) {
    		unlink(outfile);
    	}
    
    	return res;
    
    /*!
     * \brief Copies a voicemail information (envelope) file.
     * \param frompath
    
     *
     * Every voicemail has the data (.wav) file, and the information file.
     * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
     * This is used by the COPY macro when not using IMAP storage.
     */
    static void copy_plain_file(char *frompath, char *topath)
    {
    	char frompath2[PATH_MAX], topath2[PATH_MAX];
    	struct ast_variable *tmp,*var = NULL;
    	const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
    	ast_filecopy(frompath, topath, NULL);
    	snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
    	snprintf(topath2, sizeof(topath2), "%s.txt", topath);
    	if (ast_check_realtime("voicemail_data")) {
    		var = ast_load_realtime("voicemail_data", "filename", frompath, SENTINEL);
    		/* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
    		for (tmp = var; tmp; tmp = tmp->next) {
    			if (!strcasecmp(tmp->name, "origmailbox")) {
    				origmailbox = tmp->value;
    			} else if (!strcasecmp(tmp->name, "context")) {
    				context = tmp->value;
    			} else if (!strcasecmp(tmp->name, "macrocontext")) {
    				macrocontext = tmp->value;
    			} else if (!strcasecmp(tmp->name, "exten")) {
    				exten = tmp->value;
    			} else if (!strcasecmp(tmp->name, "priority")) {
    				priority = tmp->value;
    			} else if (!strcasecmp(tmp->name, "callerchan")) {
    				callerchan = tmp->value;
    			} else if (!strcasecmp(tmp->name, "callerid")) {
    				callerid = tmp->value;
    			} else if (!strcasecmp(tmp->name, "origdate")) {
    				origdate = tmp->value;
    			} else if (!strcasecmp(tmp->name, "origtime")) {
    				origtime = tmp->value;
    			} else if (!strcasecmp(tmp->name, "category")) {
    				category = tmp->value;
    			} else if (!strcasecmp(tmp->name, "duration")) {
    				duration = tmp->value;
    
    		ast_store_realtime("voicemail_data", "filename", topath, "origmailbox", origmailbox, "context", context, "macrocontext", macrocontext, "exten", exten, "priority", priority, "callerchan", callerchan, "callerid", callerid, "origdate", origdate, "origtime", origtime, "category", category, "duration", duration, SENTINEL);
    
    	copy(frompath2, topath2);
    	ast_variables_destroy(var);
    
     * \brief Removes the voicemail sound and information file.
    
    Corey Farrell's avatar
    Corey Farrell committed
     * \param file The path to the sound file. This will be the folder and message index, without the extension.
    
     * This is used by the DELETE macro when voicemails are stored on the file system.
     *
     * \return zero on success, -1 on error.
    
    static int vm_delete(char *file)
    
    	char *txt;
    	int txtsize = 0;
    
    	txtsize = (strlen(file) + 5)*sizeof(char);
    
    	txt = ast_alloca(txtsize);
    
    	/* Sprintf here would safe because we alloca'd exactly the right length,
    	 * but trying to eliminate all sprintf's anyhow
    	 */
    	if (ast_check_realtime("voicemail_data")) {
    		ast_destroy_realtime("voicemail_data", "filename", file, SENTINEL);
    
    	snprintf(txt, txtsize, "%s.txt", file);
    	unlink(txt);
    	return ast_filedelete(file, NULL);
    
    /*!
     * \brief utility used by inchar(), for base_encode()
     */
    static int inbuf(struct baseio *bio, FILE *fi)
    
    	if ((l = fread(bio->iobuf, 1, BASEMAXINLINE, fi)) != BASEMAXINLINE) {
    
    		if (l == 0) {
    			/* Assume EOF */
    			return 0;
    		}
    
    /*!
     * \brief utility used by base_encode()
     */
    static int inchar(struct baseio *bio, FILE *fi)
    
    	if (bio->iocp>=bio->iolen) {
    		if (!inbuf(bio, fi))
    			return EOF;
    	}
    
    	return bio->iobuf[bio->iocp++];
    }
    
    /*!
     * \brief utility used by base_encode()
     */
    static int ochar(struct baseio *bio, int c, FILE *so)
    
    	if (bio->linelength >= BASELINELEN) {
    
    		if (fputs(ENDL, so) == EOF) {
    
    	if (putc(((unsigned char) c), so) == EOF) {
    
    	bio->linelength++;
    
     * \brief Performs a base 64 encode algorithm on the contents of a File
     * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
     * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
     *
     * TODO: shouldn't this (and the above 3 support functions) be put into some kind of external utility location, such as funcs/func_base64.c ?
    
     *
     * \return zero on success, -1 on error.
     */
    
    static int base_encode(char *filename, FILE *so)
    
    	static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
    		'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
    		'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
    		'1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
    
    	FILE *fi;
    	struct baseio bio;
    
    	memset(&bio, 0, sizeof(bio));
    	bio.iocp = BASEMAXINLINE;
    
    	if (!(fi = fopen(filename, "rb"))) {
    		ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
    
    	while (!hiteof){
    		unsigned char igroup[3], ogroup[4];
    
    		memset(igroup, 0, sizeof(igroup));
    
    			if ((c = inchar(&bio, fi)) == EOF) {
    
    		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];
    
    				ochar(&bio, ogroup[i], so);
    
    	if (fputs(ENDL, so) == EOF) {
    
    static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *dur, char *date, const char *category, const char *flag)
    
    	char num[12];
    
    	char fromdir[256], fromfile[256];
    	struct ast_config *msg_cfg;
    	const char *origcallerid, *origtime;
    	char origcidname[80], origcidnum[80], origdate[80];
    	int inttime;
    	struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
    
    
    	/* Prepare variables for substitution in email body and subject */
    	pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
    	pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
    
    	snprintf(num, sizeof(num), "%d", msgnum);
    	pbx_builtin_setvar_helper(ast, "VM_MSGNUM", num);
    
    	pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
    	pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
    
    	pbx_builtin_setvar_helper(ast, "VM_CALLERID", (!ast_strlen_zero(cidname) || !ast_strlen_zero(cidnum)) ?
    		ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, NULL) : "an unknown caller");
    
    	pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (!ast_strlen_zero(cidname) ? cidname : "an unknown caller"));
    	pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (!ast_strlen_zero(cidnum) ? cidnum : "an unknown caller"));
    
    	pbx_builtin_setvar_helper(ast, "VM_DATE", date);
    	pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
    	pbx_builtin_setvar_helper(ast, "VM_FLAG", flag);
    
    
    	/* Retrieve info from VM attribute file */
    	make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, fromfolder);
    	make_file(fromfile, sizeof(fromfile), fromdir, msgnum - 1);
    	if (strlen(fromfile) < sizeof(fromfile) - 5) {
    		strcat(fromfile, ".txt");
    	}
    
    	if (!(msg_cfg = ast_config_load(fromfile, config_flags)) || !(valid_config(msg_cfg))) {
    
    		ast_debug(1, "Config load for message text file '%s' failed\n", fromfile);
    
    		return;
    	}
    
    	if ((origcallerid = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
    		pbx_builtin_setvar_helper(ast, "ORIG_VM_CALLERID", origcallerid);
    		ast_callerid_split(origcallerid, origcidname, sizeof(origcidname), origcidnum, sizeof(origcidnum));
    		pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNAME", origcidname);
    		pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNUM", origcidnum);
    	}
    
    
    Tilghman Lesher's avatar
    Tilghman Lesher committed
    	if ((origtime = ast_variable_retrieve(msg_cfg, "message", "origtime")) && sscanf(origtime, "%30d", &inttime) == 1) {
    
    		struct timeval tv = { inttime, };
    		struct ast_tm tm;
    		ast_localtime(&tv, &tm, NULL);
    
    		ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
    
    		pbx_builtin_setvar_helper(ast, "ORIG_VM_DATE", origdate);
    	}
    	ast_config_destroy(msg_cfg);
    
    /*!
     * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
     * \param from The string to work with.
    
     * \param buf The buffer into which to write the modified quoted string.
     * \param maxlen Always zero, but see \see ast_str
    
     * \return The destination string with quotes wrapped on it (the to field).
     */
    
    static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
    
    	const char *ptr;
    
    	/* We're only ever passing 0 to maxlen, so short output isn't possible */
    	ast_str_set(buf, maxlen, "\"");
    	for (ptr = from; *ptr; ptr++) {
    		if (*ptr == '"' || *ptr == '\\') {
    			ast_str_append(buf, maxlen, "\\%c", *ptr);
    		} else {
    			ast_str_append(buf, maxlen, "%c", *ptr);
    		}
    
    	ast_str_append(buf, maxlen, "\"");
    
    	return ast_str_buffer(*buf);
    
    /*! \brief
     * fill in *tm for current time according to the proper timezone, if any.
    
     * \return tm so it can be used as a function argument.
    
     */
    static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
    {
    	const struct vm_zone *z = NULL;
    	struct timeval t = ast_tvnow();
    
    	/* Does this user have a timezone specified? */
    	if (!ast_strlen_zero(vmu->zonetag)) {
    		/* Find the zone in the list */
    		AST_LIST_LOCK(&zones);
    		AST_LIST_TRAVERSE(&zones, z, list) {
    			if (!strcmp(z->name, vmu->zonetag))
    				break;
    
    		AST_LIST_UNLOCK(&zones);
    	}
    	ast_localtime(&t, tm, z ? z->timezone : NULL);
    	return tm;