Newer
Older
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)
Tilghman Lesher
committed
int res;
Tilghman Lesher
committed
char rowdata[20];
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);
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
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);
Tilghman Lesher
committed
}
* \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)
Tilghman Lesher
committed
{
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;
}
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
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.
*/
Tilghman Lesher
committed
static void delete_file(const char *sdir, int smsg)
Matt O'Gorman
committed
{
SQLHSTMT stmt;
char sql[PATH_MAX];
char msgnums[20];
Tilghman Lesher
committed
char *argv[] = { NULL, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
struct odbc_obj *obj;
Tilghman Lesher
committed
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;
Matt O'Gorman
committed
/*!
* \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 *argv[] = { ddir, msgnumd, msg_id, dmailboxuser, dmailboxcontext, sdir, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 7, .argv = argv };
Matt O'Gorman
committed
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;
Matt O'Gorman
committed
struct insert_data {
char *sql;
Tilghman Lesher
committed
const char *dir;
const char *msgnums;
void *data;
SQLLEN datalen;
SQLLEN indlen;
const char *context;
const char *macrocontext;
const char *callerid;
const char *origtime;
const char *duration;
Tilghman Lesher
committed
const char *mailboxuser;
const char *mailboxcontext;
const char *category;
const char *flag;
const char *msg_id;
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;
Mark Michelson
committed
}
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;
}
Matt O'Gorman
committed
Matt O'Gorman
committed
}
* \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.
Tilghman Lesher
committed
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;
Matthew Jordan
committed
off_t fdlen = -1;
SQLHSTMT stmt;
char sql[PATH_MAX];
char msgnums[20];
char fn[PATH_MAX];
char full_fn[PATH_MAX];
char fmt[80]="";
char *c;
struct ast_config *cfg = NULL;
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';
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)) {
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
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);
Matthew Jordan
committed
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);
Matthew Jordan
committed
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);
Mark Michelson
committed
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;
ast_odbc_release_obj(obj);
if (valid_config(cfg))
ast_config_destroy(cfg);
if (fdm != MAP_FAILED)
munmap(fdm, fdlen);
if (fd > -1)
close(fd);
return res;
}
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
/*!
* \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++;
/*!
* \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)
Matt O'Gorman
committed
{
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);
Matt O'Gorman
committed
}
* \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)
Matt O'Gorman
committed
{
int x;
unsigned char map[MAXMSGLIMIT] = "";
DIR *msgdir;
struct dirent *msgdirent;
int msgdirint;
/* 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) {
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) {
Matt O'Gorman
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
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
*
* 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;
Tilghman Lesher
committed
}
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);
Tilghman Lesher
committed
}
Tilghman Lesher
committed
* \brief Removes the voicemail sound and information file.
* \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)
Tilghman Lesher
committed
{
char *txt;
int txtsize = 0;
txtsize = (strlen(file) + 5)*sizeof(char);
/* 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);
Tilghman Lesher
committed
}
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;
}
bio->iolen = l;
bio->iocp = 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) {
bio->linelength = 0;
if (putc(((unsigned char) c), so) == EOF) {
Matthew Fredrickson
committed
Joshua Colp
committed
* \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', '+', '/'};
int i, hiteof = 0;
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];
int c, n;
memset(igroup, 0, sizeof(igroup));
Kevin P. Fleming
committed
for (n = 0; n < 3; n++) {
if ((c = inchar(&bio, fi)) == EOF) {
hiteof = 1;
igroup[n] = (unsigned char) c;
if (n > 0) {
ogroup[0]= dtable[igroup[0] >> 2];
ogroup[1]= dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)];
ogroup[2]= dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)];
ogroup[3]= dtable[igroup[2] & 0x3F];
if (n < 3) {
ogroup[3] = '=';
Tilghman Lesher
committed
if (n < 2)
ogroup[2] = '=';
for (i = 0; i < 4; i++)
ochar(&bio, ogroup[i], so);
Kevin P. Fleming
committed
}
Tilghman Lesher
committed
if (fputs(ENDL, so) == EOF) {
Tilghman Lesher
committed
Kevin P. Fleming
committed
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 callerid[256];
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);
}
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);
Tilghman Lesher
committed
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;