diff --git a/Makefile.am b/Makefile.am index 519fe02e4b..63b0ec1b42 100644 --- a/Makefile.am +++ b/Makefile.am @@ -135,6 +135,7 @@ freeswitch_LDADD += -lcurses endif if ADD_ODBC +CORE_CFLAGS += -DSWITCH_HAVE_ODBC libfreeswitch_la_SOURCES += src/switch_odbc.c libfreeswitch_la_LDFLAGS += -lodbc endif diff --git a/build/modules.conf.in b/build/modules.conf.in index a7abc70001..ee132a531f 100644 --- a/build/modules.conf.in +++ b/build/modules.conf.in @@ -5,6 +5,7 @@ applications/mod_conference applications/mod_dptools applications/mod_enum applications/mod_fifo +#applications/mod_voicemail #applications/mod_ivrtest #applications/mod_soundtouch #applications/mod_rss diff --git a/conf/default_context.xml b/conf/default_context.xml index 990a721e7f..2776a48997 100644 --- a/conf/default_context.xml +++ b/conf/default_context.xml @@ -19,6 +19,21 @@ + + + + + + + + + + + + + + + diff --git a/conf/directory.xml b/conf/directory.xml index 101286fbc8..32fef0faa9 100644 --- a/conf/directory.xml +++ b/conf/directory.xml @@ -1,7 +1,7 @@ - + @@ -34,6 +34,8 @@ + + diff --git a/conf/freeswitch.xml b/conf/freeswitch.xml index 6a5d67759d..953ad1957d 100644 --- a/conf/freeswitch.xml +++ b/conf/freeswitch.xml @@ -102,6 +102,7 @@ + @@ -145,6 +146,8 @@ + + diff --git a/conf/modules.conf.xml b/conf/modules.conf.xml index 3d5cd7d43a..30fce65b6f 100644 --- a/conf/modules.conf.xml +++ b/conf/modules.conf.xml @@ -37,6 +37,7 @@ + diff --git a/conf/voicemail.conf.xml b/conf/voicemail.conf.xml new file mode 100644 index 0000000000..0a7e16c21f --- /dev/null +++ b/conf/voicemail.conf.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/voicemail_en_tts.xml b/conf/voicemail_en_tts.xml new file mode 100644 index 0000000000..969a0d2c49 --- /dev/null +++ b/conf/voicemail_en_tts.xml @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/include/switch_apr.h b/src/include/switch_apr.h index 1442131a20..ea5a0f597f 100644 --- a/src/include/switch_apr.h +++ b/src/include/switch_apr.h @@ -748,6 +748,11 @@ SWITCH_DECLARE(switch_size_t) switch_file_get_size(switch_file_t *thefile); SWITCH_DECLARE(switch_status_t) switch_file_exists(const char *filename, switch_memory_pool_t *pool); +SWITCH_DECLARE(switch_status_t) switch_dir_make(const char *path, switch_fileperms_t perm, + switch_memory_pool_t *pool); +SWITCH_DECLARE(switch_status_t) switch_dir_make_recursive(const char *path, switch_fileperms_t perm, + switch_memory_pool_t *pool); + typedef struct switch_dir switch_dir_t; struct switch_array_header_t { diff --git a/src/include/switch_types.h b/src/include/switch_types.h index 3adcb4886e..0e8ac81bd7 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -253,6 +253,7 @@ struct switch_directories { char *temp_dir; char *htdocs_dir; char *grammar_dir; + char *storage_dir; }; typedef struct switch_directories switch_directories; diff --git a/src/mod/applications/mod_dptools/mod_dptools.c b/src/mod/applications/mod_dptools/mod_dptools.c index fe7e426927..50d6e021ff 100644 --- a/src/mod/applications/mod_dptools/mod_dptools.c +++ b/src/mod/applications/mod_dptools/mod_dptools.c @@ -675,9 +675,16 @@ SWITCH_STANDARD_API(strftime_api_function) switch_size_t retsize; switch_time_exp_t tm; char date[80] = ""; - - switch_time_exp_lt(&tm, switch_time_now()); - switch_strftime(date, &retsize, sizeof(date), cmd ? cmd : "%Y-%m-%d %T", &tm); + switch_time_t thetime; + char *p; + if (!switch_strlen_zero(cmd) && (p = strchr(cmd, '|'))) { + thetime = switch_time_make(atoi(cmd),0); + cmd = p+1; + } else { + thetime = switch_time_now(); + } + switch_time_exp_lt(&tm, thetime); + switch_strftime(date, &retsize, sizeof(date), switch_strlen_zero(cmd) ? "%Y-%m-%d %T" : cmd, &tm); stream->write_function(stream, "%s", date); return SWITCH_STATUS_SUCCESS; diff --git a/src/mod/applications/mod_voicemail/Makefile b/src/mod/applications/mod_voicemail/Makefile new file mode 100644 index 0000000000..c6d645e6aa --- /dev/null +++ b/src/mod/applications/mod_voicemail/Makefile @@ -0,0 +1,2 @@ +BASE=../../../.. +include /usr/src/freeswitch.trunk/build/modmake.rules diff --git a/src/mod/applications/mod_voicemail/mod_voicemail.c b/src/mod/applications/mod_voicemail/mod_voicemail.c new file mode 100644 index 0000000000..aac7f32176 --- /dev/null +++ b/src/mod/applications/mod_voicemail/mod_voicemail.c @@ -0,0 +1,1633 @@ +/* + * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * Copyright (C) 2005/2006, Anthony Minessale II + * + * Version: MPL 1.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application + * + * The Initial Developer of the Original Code is + * Anthony Minessale II + * Portions created by the Initial Developer are Copyright (C) + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Anthony Minessale II + * + * + * mod_voicemail.c -- Voicemail Module + * + */ +#include + +#ifdef SWITCH_HAVE_ODBC +#include +#endif + + +#define TRY_CODE(code) do {status = code; if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { goto end; } break;} while(status) + +SWITCH_MODULE_LOAD_FUNCTION(mod_voicemail_load); +SWITCH_MODULE_DEFINITION(mod_voicemail, mod_voicemail_load, NULL, NULL); + +static struct { + switch_hash_t *profile_hash; + int debug; + switch_memory_pool_t *pool; +} globals; + +struct vm_profile { + char *name; + char *dbname; + char *odbc_dsn; + char *odbc_user; + char *odbc_pass; + char terminator_key[2]; + char play_new_messages_key[2]; + char play_saved_messages_key[2]; + char save_message_key[2]; + char delete_message_key[2]; + char replay_message_key[2]; + + char main_menu_key[2]; + char config_menu_key[2]; + char record_greeting_key[2]; + char choose_greeting_key[2]; + char record_name_key[2]; + + char record_file_key[2]; + char listen_file_key[2]; + char save_file_key[2]; + char delete_file_key[2]; + char undelete_file_key[2]; + char *tone_spec; + uint32_t digit_timeout; + uint32_t max_login_attempts; + uint32_t max_record_len; + switch_mutex_t *mutex; +#ifdef SWITCH_HAVE_ODBC + switch_odbc_handle_t *master_odbc; +#else + void *filler1; +#endif +}; +typedef struct vm_profile vm_profile_t; + + +#define B64BUFFLEN 1024 +static const char c64[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int write_buf(int fd, char *buf) +{ + + int len = (int) strlen(buf); + if (fd && write(fd, buf, len) != len) { + close(fd); + return 0; + } + + return 1; +} +static switch_bool_t vm_email(char *to, char *from, char *headers, char *body, char *file) +{ + char *bound = "XXXX_boundary_XXXX"; + char filename[80], buf[B64BUFFLEN]; + int fd = 0, ifd = 0; + int x = 0, y = 0, bytes = 0, ilen = 0; + unsigned int b = 0, l = 0; + unsigned char in[B64BUFFLEN]; + unsigned char out[B64BUFFLEN + 512]; + char *path = NULL; + + snprintf(filename, 80, "%smail.%ld%04x", SWITCH_GLOBAL_dirs.temp_dir, time(NULL), rand() & 0xffff); + + if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644))) { + if (file) { + path = file; + if ((ifd = open(path, O_RDONLY)) < 1) { + return SWITCH_FALSE; + } + + snprintf(buf, B64BUFFLEN, "MIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=\"%s\"\n", bound); + if (!write_buf(fd, buf)) { + return SWITCH_FALSE; + } + } + + if (headers && !write_buf(fd, headers)) + return SWITCH_FALSE; + + if (!write_buf(fd, "\n\n")) + return SWITCH_FALSE; + + if (file) { + snprintf(buf, B64BUFFLEN, "--%s\nContent-Type: text/plain\n\n", bound); + if (!write_buf(fd, buf)) + return SWITCH_FALSE; + } + + if (body) { + if (!write_buf(fd, body)) { + return SWITCH_FALSE; + } + } + + if (file) { + snprintf(buf, B64BUFFLEN, "\n\n--%s\nContent-Type: application/octet-stream\n" + "Content-Transfer-Encoding: base64\n" + "Content-Description: Sound attachment.\n" "Content-Disposition: attachment; filename=\"%s\"\n\n", bound, switch_cut_path(file)); + if (!write_buf(fd, buf)) + return SWITCH_FALSE; + + while ((ilen = read(ifd, in, B64BUFFLEN))) { + for (x = 0; x < ilen; x++) { + b = (b << 8) + in[x]; + l += 8; + while (l >= 6) { + out[bytes++] = c64[(b >> (l -= 6)) % 64]; + if (++y != 72) + continue; + out[bytes++] = '\n'; + y = 0; + } + } + if (write(fd, &out, bytes) != bytes) { + return -1; + } else + bytes = 0; + + } + + if (l > 0) { + out[bytes++] = c64[((b % 16) << (6 - l)) % 64]; + } + if (l != 0) + while (l < 6) { + out[bytes++] = '=', l += 2; + } + if (write(fd, &out, bytes) != bytes) { + return -1; + } + + } + + + + if (file) { + snprintf(buf, B64BUFFLEN, "\n\n--%s--\n.\n", bound); + if (!write_buf(fd, buf)) + return SWITCH_FALSE; + } + } + + if (fd) { + close(fd); + } + if (ifd) { + close(ifd); + } + snprintf(buf, B64BUFFLEN, "/bin/cat %s | /usr/sbin/sendmail -tvf \"%s\" %s", filename, from, to); + if(system(buf)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Unable to execute command: %s\n", buf); + } + + unlink(filename); + + + if (file) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Emailed file [%s] to [%s]\n", filename, to); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Emailed data to [%s]\n", to); + } + + return SWITCH_TRUE; +} + +static switch_status_t vm_execute_sql(vm_profile_t *profile, char *sql, switch_mutex_t *mutex) +{ + switch_core_db_t *db; + switch_status_t status = SWITCH_STATUS_SUCCESS; + + if (mutex) { + switch_mutex_lock(mutex); + } + +#ifdef SWITCH_HAVE_ODBC + if (profile->odbc_dsn) { + SQLHSTMT stmt; + if (switch_odbc_handle_exec(profile->master_odbc, sql, &stmt) != SWITCH_ODBC_SUCCESS) { + char *err_str; + err_str = switch_odbc_handle_get_error(profile->master_odbc, stmt); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "ERR: [%s]\n[%s]\n", sql, switch_str_nil(err_str)); + switch_safe_free(err_str); + status = SWITCH_STATUS_FALSE; + } + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + } else { +#endif + if (!(db = switch_core_db_open_file(profile->dbname))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", profile->dbname); + status = SWITCH_STATUS_FALSE; + goto end; + } + + status = switch_core_db_persistant_execute(db, sql, 25); + switch_core_db_close(db); + +#ifdef SWITCH_HAVE_ODBC + } +#endif + + + end: + if (mutex) { + switch_mutex_unlock(mutex); + } + + return status; +} + + +static switch_bool_t vm_execute_sql_callback(vm_profile_t *profile, + switch_mutex_t *mutex, + char *sql, + switch_core_db_callback_func_t callback, + void *pdata) +{ + switch_bool_t ret = SWITCH_FALSE; + switch_core_db_t *db; + char *errmsg = NULL; + + if (mutex) { + switch_mutex_lock(mutex); + } + + +#ifdef SWITCH_HAVE_ODBC + if (profile->odbc_dsn) { + switch_odbc_handle_callback_exec(profile->master_odbc, sql, callback, pdata); + } else { +#endif + + + + if (!(db = switch_core_db_open_file(profile->dbname))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Opening DB %s\n", profile->dbname); + goto end; + } + + + switch_core_db_exec(db, sql, callback, pdata, &errmsg); + + if (errmsg) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "SQL ERR: [%s] %s\n", sql, errmsg); + free(errmsg); + } + + if (db) { + switch_core_db_close(db); + } + +#ifdef SWITCH_HAVE_ODBC + } +#endif + + + end: + + if (mutex) { + switch_mutex_unlock(mutex); + } + + + + return ret; + +} + + +static char vm_sql[] = + "CREATE TABLE voicemail_data (\n" + " created_epoch INTEGER(8),\n" + " read_epoch INTEGER(8),\n" + " user VARCHAR(255),\n" + " domain VARCHAR(255),\n" + " uuid VARCHAR(255),\n" + " cid_name VARCHAR(255),\n" + " cid_number VARCHAR(255),\n" + " in_folder VARCHAR(255),\n" + " file_path VARCHAR(255),\n" + " flags VARCHAR(255)\n" + ");\n"; + + +static char vm_pref_sql[] = + "CREATE TABLE voicemail_prefs (\n" + " user VARCHAR(255),\n" + " domain VARCHAR(255),\n" + " name_path VARCHAR(255),\n" + " greeting_path VARCHAR(255)\n" + ");\n"; + + + + +static switch_status_t load_config(void) +{ + char *cf = "voicemail.conf"; + vm_profile_t *profile = NULL; + switch_xml_t cfg, xml, settings, param, x_profile, x_profiles; + + memset(&globals, 0, sizeof(globals)); + switch_core_new_memory_pool(&globals.pool); + switch_core_hash_init(&globals.profile_hash, globals.pool); + + if (!(xml = switch_xml_open_cfg(cf, &cfg, NULL))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", cf); + return SWITCH_STATUS_TERM; + } + + if ((settings = switch_xml_child(cfg, "settings"))) { + for (param = switch_xml_child(settings, "param"); param; param = param->next) { + char *var = (char *) switch_xml_attr_soft(param, "name"); + char *val = (char *) switch_xml_attr_soft(param, "value"); + + if (!strcasecmp(var, "debug")) { + globals.debug = atoi(val); + } + } + } + + if (!(x_profiles = switch_xml_child(cfg, "profiles"))) { + goto end; + } + + for (x_profile = switch_xml_child(x_profiles, "profile"); x_profile; x_profile = x_profile->next) { + char *name = (char *) switch_xml_attr_soft(x_profile, "name"); + char *odbc_dsn = NULL, *odbc_dsn_tmp = NULL, *odbc_user = NULL, *odbc_pass = NULL, *dbname = NULL; + char *terminator_key = "#"; + char *play_new_messages_key = "1"; + char *play_saved_messages_key = "2"; + char *save_message_key = "8"; + char *delete_message_key = "7"; + char *replay_message_key = "1"; + + char *main_menu_key = "0"; + char *config_menu_key = "5"; + char *record_greeting_key = "1"; + char *choose_greeting_key = "2"; + char *record_name_key = "3"; + + char *record_file_key = "3"; + char *listen_file_key = "1"; + char *save_file_key = "2"; + char *delete_file_key = "7"; + char *undelete_file_key = "8"; + char *tone_spec = "%(1000, 0, 640)"; + + switch_core_db_t *db; + uint32_t timeout = 10000, max_login_attempts = 3, max_record_len = 300; + + db = NULL; + for (param = switch_xml_child(x_profile, "param"); param; param = param->next) { + char *var, *val; + + var = (char *) switch_xml_attr_soft(param, "name"); + val = (char *) switch_xml_attr_soft(param, "value"); + + if (!strcasecmp(var, "terminator-key") && !switch_strlen_zero(val)) { + terminator_key = val; + } else if (!strcasecmp(var, "play-new-messages-key") && !switch_strlen_zero(val)) { + play_new_messages_key = val; + } else if (!strcasecmp(var, "play-saved-messages-key") && !switch_strlen_zero(val)) { + play_saved_messages_key = val; + } else if (!strcasecmp(var, "save-message-key") && !switch_strlen_zero(val)) { + save_message_key = val; + } else if (!strcasecmp(var, "delete-message-key") && !switch_strlen_zero(val)) { + delete_message_key = val; + } else if (!strcasecmp(var, "replay-message-key") && !switch_strlen_zero(val)) { + replay_message_key = val; + } else if (!strcasecmp(var, "main-menu-key") && !switch_strlen_zero(val)) { + main_menu_key = val; + } else if (!strcasecmp(var, "config-menu-key") && !switch_strlen_zero(val)) { + config_menu_key = val; + } else if (!strcasecmp(var, "record-greeting-key") && !switch_strlen_zero(val)) { + record_greeting_key = val; + } else if (!strcasecmp(var, "choose-greeting-key") && !switch_strlen_zero(val)) { + choose_greeting_key = val; + } else if (!strcasecmp(var, "record-name-key") && !switch_strlen_zero(val)) { + record_name_key = val; + } else if (!strcasecmp(var, "listen-file-key") && !switch_strlen_zero(val)) { + listen_file_key = val; + } else if (!strcasecmp(var, "save-file-key") && !switch_strlen_zero(val)) { + save_file_key = val; + } else if (!strcasecmp(var, "delete-file-key") && !switch_strlen_zero(val)) { + delete_file_key = val; + } else if (!strcasecmp(var, "undelete-file-key") && !switch_strlen_zero(val)) { + undelete_file_key = val; + } else if (!strcasecmp(var, "tone-spec")) { + tone_spec = val; + } else if (!strcasecmp(var, "digit-timeout")) { + int tmp = 0; + if (!switch_strlen_zero(val)) { + tmp = atoi(val); + } + if (tmp >= 1000 && tmp <= 30000) { + timeout = tmp; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "invalid timeout value [%s] must be between 1000 and 30000 ms\n", val); + } + } else if (!strcasecmp(var, "max-login-attempts")) { + int tmp = 0; + if (!switch_strlen_zero(val)) { + tmp = atoi(val); + } + if (tmp > 0 && tmp < 11) { + max_login_attempts = tmp; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "invalid attempts [%s] must be between 1 and 10 ms\n", val); + } + } else if (!strcasecmp(var, "max-record-len")) { + int tmp = 0; + if (!switch_strlen_zero(val)) { + tmp = atoi(val); + } + if (tmp > 0 && tmp < 10000) { + max_record_len = tmp; + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "invalid attempts [%s] must be between 1 and 10000s\n", val); + } + } else if (!strcasecmp(var, "odbc-dsn")) { +#ifdef SWITCH_HAVE_ODBC + odbc_dsn_tmp = val; + odbc_dsn = strdup(val); + + if ((odbc_user = strchr(odbc_dsn, ':'))) { + *odbc_user++ = '\0'; + } + + if ((odbc_pass = strchr(odbc_user, ':'))) { + *odbc_pass++ = '\0'; + } +#else + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "ODBC IS NOT AVAILABLE!\n"); +#endif + } + } + + if (switch_strlen_zero(name)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No name specified.\n"); + } else { + profile = switch_core_alloc(globals.pool, sizeof(*profile)); + profile->name = switch_core_strdup(globals.pool, name); + + if (!switch_strlen_zero(odbc_dsn) && !switch_strlen_zero(odbc_user) && !switch_strlen_zero(odbc_pass)) { + profile->odbc_dsn = switch_core_strdup(globals.pool, odbc_dsn_tmp); + profile->odbc_user = switch_core_strdup(globals.pool, odbc_user); + profile->odbc_pass = switch_core_strdup(globals.pool, odbc_pass); + } else { + profile->dbname = switch_core_sprintf(globals.pool, "voicemail_%s", name); + } +#ifdef SWITCH_HAVE_ODBC + if (profile->odbc_dsn) { + if (!(profile->master_odbc = switch_odbc_handle_new(profile->odbc_dsn, profile->odbc_user, profile->odbc_pass))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open ODBC Database!\n"); + continue; + + } + if (switch_odbc_handle_connect(profile->master_odbc) != SWITCH_ODBC_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open ODBC Database!\n"); + continue; + } + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Connected ODBC DSN: %s\n", profile->odbc_dsn); + switch_odbc_handle_exec(profile->master_odbc, vm_sql, NULL); + switch_odbc_handle_exec(profile->master_odbc, vm_pref_sql, NULL); + } else { +#endif + if ((db = switch_core_db_open_file(profile->dbname))) { + switch_core_db_test_reactive(db, "select * from voicemail_data", vm_sql); + switch_core_db_test_reactive(db, "select * from voicemail_prefs", vm_pref_sql); + } else { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Cannot Open SQL Database!\n"); + continue; + } + switch_core_db_close(db); +#ifdef SWITCH_HAVE_ODBC + } +#endif + profile->digit_timeout = timeout; + profile->max_login_attempts = max_login_attempts; + profile->max_record_len = max_record_len; + *profile->terminator_key = *terminator_key; + *profile->play_new_messages_key = *play_new_messages_key; + *profile->play_saved_messages_key = *play_saved_messages_key; + *profile->save_message_key = *save_message_key; + *profile->delete_message_key = *delete_message_key; + *profile->replay_message_key = *replay_message_key; + *profile->main_menu_key = *main_menu_key; + *profile->config_menu_key = *config_menu_key; + *profile->record_greeting_key = *record_greeting_key; + *profile->choose_greeting_key = *choose_greeting_key; + *profile->record_name_key = *record_name_key; + *profile->record_file_key = *record_file_key; + *profile->listen_file_key = *listen_file_key; + *profile->save_file_key = *save_file_key; + *profile->delete_file_key = *delete_file_key; + *profile->undelete_file_key = *undelete_file_key; + profile->tone_spec = switch_core_strdup(globals.pool, tone_spec); + + switch_mutex_init(&profile->mutex, SWITCH_MUTEX_NESTED, globals.pool); + + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Added Profile %s\n", profile->name); + switch_core_hash_insert(globals.profile_hash, profile->name, profile); + + } + + + + switch_safe_free(odbc_dsn); + switch_safe_free(dbname); + + } + + end: + + switch_xml_free(xml); + + return SWITCH_STATUS_SUCCESS; +} + + +static switch_status_t cancel_on_dtmf(switch_core_session_t *session, void *input, switch_input_type_t itype, void *buf, unsigned int buflen) +{ + + + switch (itype) { + case SWITCH_INPUT_TYPE_DTMF: + { + char *dtmf = (char *) input; + if (buf && buflen) { + switch_copy_string(buf, dtmf, buflen); + } + return SWITCH_STATUS_BREAK; + } + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + + +static switch_status_t control_playback(switch_core_session_t *session, void *input, switch_input_type_t itype, void *buf, unsigned int buflen) +{ + + + switch (itype) { + case SWITCH_INPUT_TYPE_DTMF: + { + char *dtmf = (char *) input; + switch_file_handle_t *fh = (switch_file_handle_t *) buf; + uint32_t pos = 0; + + switch(*dtmf) { + case '0': + { + if (switch_test_flag(fh, SWITCH_FILE_PAUSE)) { + switch_clear_flag(fh, SWITCH_FILE_PAUSE); + } else { + switch_set_flag(fh, SWITCH_FILE_PAUSE); + } + return SWITCH_STATUS_SUCCESS; + } + break; + case '1': + { + unsigned int pos = 0; + fh->speed = 0; + switch_core_file_seek(fh, &pos, 0, SEEK_SET); + return SWITCH_STATUS_SUCCESS; + } + break; + case '6': + { + int samps = 24000; + switch_core_file_seek(fh, &pos, samps, SEEK_CUR); + return SWITCH_STATUS_SUCCESS; + } + break; + case '4': + { + int samps = 24000; + switch_core_file_seek(fh, &pos, fh->pos - samps, SEEK_SET); + return SWITCH_STATUS_SUCCESS; + } + break; + case '#': + return SWITCH_STATUS_BREAK; + + default: + return SWITCH_STATUS_SUCCESS; + } + } + break; + default: + break; + } + + return SWITCH_STATUS_SUCCESS; +} + +struct prefs_callback { + char name_path[255]; + char greeting_path[255]; +}; +typedef struct prefs_callback prefs_callback_t; + +static int prefs_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + prefs_callback_t *cbt = (prefs_callback_t *) pArg; + + switch_copy_string(cbt->name_path, argv[2], sizeof(cbt->name_path)); + switch_copy_string(cbt->greeting_path, argv[3], sizeof(cbt->greeting_path)); + + return 0; +} + + +typedef enum { + VM_CHECK_START, + VM_CHECK_AUTH, + VM_CHECK_MENU, + VM_CHECK_CONFIG, + VM_CHECK_PLAY_MESSAGES, + VM_CHECK_FOLDER_SUMMARY, + VM_CHECK_LISTEN +} vm_check_state_t; + + +#define VM_ACK_MACRO "voicemail_ack" +#define VM_SAY_DATE_MACRO "voicemail_say_date" +#define VM_PLAY_GREETING_MACRO "voicemail_play_greeting" +#define VM_SAY_MESSAGE_NUMBER_MACRO "voicemail_say_message_number" +#define VM_SAY_NUMBER_MACRO "voicemail_say_number" +#define VM_SAY_PHONE_NUMBER_MACRO "voicemail_say_phone_number" +#define VM_SAY_NAME_MACRO "voicemail_say_name" + +#define VM_RECORD_MESSAGE_MACRO "voicemail_record_message" +#define VM_CHOOSE_GREETING_MACRO "voicemail_choose_greeting" +#define VM_CHOOSE_GREETING_FAIL_MACRO "voicemail_choose_greeting_fail" +#define VM_CHOOSE_GREETING_SELECTED_MACRO "voicemail_greeting_selected" +#define VM_RECORD_GREETING_MACRO "voicemail_record_greeting" +#define VM_RECORD_NAME_MACRO "voicemail_record_name" +#define VM_LISTEN_FILE_CHECK_MACRO "voicemail_listen_file_check" +#define VM_RECORD_FILE_CHECK_MACRO "voicemail_record_file_check" +#define VM_MENU_MACRO "voicemail_menu" +#define VM_CONFIG_MENU_MACRO "voicemail_config_menu" +#define VM_ENTER_ID_MACRO "voicemail_enter_id" +#define VM_ENTER_PASS_MACRO "voicemail_enter_pass" +#define VM_FAIL_AUTH_MACRO "voicemail_fail_auth" +#define VM_ABORT_MACRO "voicemail_abort" +#define VM_HELLO_MACRO "voicemail_hello" +#define VM_GOODBYE_MACRO "voicemail_goodbye" +#define VM_NEW_MESSAGE_COUNT_MACRO "voicemail_new_message_count" +#define VM_SAVED_MESSAGE_COUNT_MACRO "voicemail_saved_message_count" + + +static switch_status_t vm_macro_get(switch_core_session_t *session, + char *macro, + char *macro_arg, + char *buf, + switch_size_t buflen, + switch_size_t maxlen, + char *term_chars, + char *terminator_key, + uint32_t timeout) +{ + switch_input_args_t args = { 0 }, *ap = NULL; + switch_channel_t *channel; + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_size_t bslen; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + if (buf && buflen) { + *buf = '\0'; + args.input_callback = cancel_on_dtmf; + args.buf = buf; + args.buflen = sizeof(buf); + ap = &args; + } + + status = switch_ivr_phrase_macro(session, macro, macro_arg, NULL, ap); + + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + if (buf) { + memset(buf, 0, buflen); + } + return status; + } + + if (!buf) { + return status; + } + + bslen = strlen(buf); + + if (maxlen == 0 || maxlen > buflen - 1) { + maxlen = buflen -1; + } + if (bslen < maxlen) { + status = switch_ivr_collect_digits_count(session, buf + bslen, buflen, maxlen - bslen, term_chars, terminator_key, timeout); + } + + return status; +} + +struct callback { + char *buf; + size_t len; + int matches; +}; +typedef struct callback callback_t; + +static int sql2str_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + callback_t *cbt = (callback_t *) pArg; + + switch_copy_string(cbt->buf, argv[0], cbt->len); + cbt->matches++; + return 0; +} + + +static int unlink_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + if (argv[0]) { + unlink(argv[0]); + } + return 0; +} + + +typedef enum { + MSG_NONE, + MSG_NEW, + MSG_SAVED +} msg_type_t; + +static uint32_t DEFAULT_DIR_PERMS = SWITCH_FPROT_UREAD | SWITCH_FPROT_UWRITE | SWITCH_FPROT_UEXECUTE | SWITCH_FPROT_GREAD | SWITCH_FPROT_GEXECUTE; + + +static switch_status_t create_file(switch_core_session_t *session, vm_profile_t *profile, char *macro_name, char *file_path) +{ + switch_channel_t *channel; + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_file_handle_t fh = { 0 }; + switch_input_args_t args = { 0 }; + char term; + char input[10] = "" , key_buf[80] = ""; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + + while(switch_channel_ready(channel)) { + + + switch_channel_set_variable(channel, "RECORD_COMMENT", macro_name); + + + + + + snprintf(key_buf, sizeof(key_buf), "%s:%s:%s", + profile->listen_file_key, + profile->save_file_key, + profile->record_file_key); + + + record_file: + args.input_callback = cancel_on_dtmf; + TRY_CODE(switch_ivr_phrase_macro(session, macro_name, NULL, NULL, NULL)); + TRY_CODE(switch_ivr_gentones(session, profile->tone_spec, 0, NULL)); + + memset(&fh, 0, sizeof(fh)); + fh.thresh = 200; + fh.silence_hits = 2; + + + switch_ivr_record_file(session, &fh, file_path, &args, profile->max_record_len); + status = SWITCH_STATUS_SUCCESS; + + play_file: + memset(&fh, 0, sizeof(fh)); + args.input_callback = control_playback; + args.buf = &fh; + switch_ivr_play_file(session, &fh, file_path, &args); + + + while(switch_channel_ready(channel)) { + status = vm_macro_get(session, VM_RECORD_FILE_CHECK_MACRO, + key_buf, input, sizeof(input), 1, profile->terminator_key, &term, profile->digit_timeout); + + if (!strcmp(input, profile->listen_file_key)) { + goto play_file; + } else if (!strcmp(input, profile->record_file_key)) { + goto record_file; + } else { + TRY_CODE(switch_ivr_phrase_macro(session, VM_ACK_MACRO, "saved", NULL, NULL)); + goto end; + } + } + } + + end: + + return status; +} + + +struct listen_callback { + char created_epoch[255]; + char read_epoch[255]; + char user[255]; + char domain[255]; + char uuid[255]; + char cid_name[255]; + char cid_number[255]; + char in_folder[255]; + char file_path[255]; + char flags[255]; + int index; + int want; + msg_type_t type; +}; +typedef struct listen_callback listen_callback_t; + +static int listen_callback(void *pArg, int argc, char **argv, char **columnNames) +{ + listen_callback_t *cbt = (listen_callback_t *) pArg; + + if (cbt->index++ != cbt->want) { + return 0; + } + + switch_copy_string(cbt->created_epoch, argv[0], 255); + switch_copy_string(cbt->read_epoch, argv[1], 255); + switch_copy_string(cbt->user, argv[2], 255); + switch_copy_string(cbt->domain, argv[3], 255); + switch_copy_string(cbt->uuid, argv[4], 255); + switch_copy_string(cbt->cid_name, argv[5], 255); + switch_copy_string(cbt->cid_number, argv[6], 255); + switch_copy_string(cbt->in_folder, argv[7], 255); + switch_copy_string(cbt->file_path, argv[8], 255); + switch_copy_string(cbt->flags, argv[9], 255); + + + return -1; +} + + +static switch_status_t listen_file(switch_core_session_t *session, vm_profile_t *profile, listen_callback_t *cbt) +{ + switch_channel_t *channel; + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_input_args_t args = { 0 }; + char term; + char input[10] = "" , key_buf[80] = ""; + switch_file_handle_t fh = { 0 }; + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + + if(switch_channel_ready(channel)) { + + args.input_callback = cancel_on_dtmf; + + + snprintf(key_buf, sizeof(key_buf), "%s:%s:%s:%s", + profile->listen_file_key, + profile->save_file_key, + profile->delete_file_key, + profile->undelete_file_key); + + + snprintf(input, sizeof(input), "%s:%d", cbt->type == MSG_NEW ? "new" : "saved", cbt->index); + TRY_CODE(switch_ivr_phrase_macro(session, VM_SAY_MESSAGE_NUMBER_MACRO, input, NULL, NULL)); + TRY_CODE(switch_ivr_phrase_macro(session, VM_SAY_DATE_MACRO, cbt->created_epoch, NULL, NULL)); + play_file: + + memset(&fh, 0, sizeof(fh)); + args.input_callback = control_playback; + args.buf = &fh; + TRY_CODE(switch_ivr_play_file(session, NULL, cbt->file_path, &args)); + + if (switch_channel_ready(channel)) { + status = vm_macro_get(session, VM_LISTEN_FILE_CHECK_MACRO, + key_buf, input, sizeof(input), 1, profile->terminator_key, &term, profile->digit_timeout); + + if (!strcmp(input, profile->listen_file_key)) { + goto play_file; + } else if (!strcmp(input, profile->delete_file_key)) { + char *sql = switch_mprintf("update voicemail_data set flags='delete' where uuid='%s'", cbt->uuid); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(sql); + TRY_CODE(switch_ivr_phrase_macro(session, VM_ACK_MACRO, "deleted", NULL, NULL)); + } else { + char *sql = switch_mprintf("update voicemail_data set flags='save' where uuid='%s'", cbt->uuid); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(sql); + TRY_CODE(switch_ivr_phrase_macro(session, VM_ACK_MACRO, "saved", NULL, NULL)); + } + } + } + + end: + + return status; +} + +static void message_count(vm_profile_t *profile, char *myid, char *domain_name, char *myfolder, int *total_new_messages, int *total_saved_messages) +{ + char msg_count[80] = ""; + callback_t cbt = { 0 }; + char sql[128]; + + cbt.buf = msg_count; + cbt.len = sizeof(msg_count); + + + snprintf(sql, sizeof(sql), + "select count(*) from voicemail_data where user='%s' and domain='%s' and in_folder='%s' and read_epoch=0", + myid, + domain_name, + myfolder); + vm_execute_sql_callback(profile, profile->mutex, sql, sql2str_callback, &cbt); + *total_new_messages = atoi(msg_count); + + snprintf(sql, sizeof(sql), + "select count(*) from voicemail_data where user='%s' and domain='%s' and in_folder='%s' and read_epoch!=0", + myid, + domain_name, + myfolder); + vm_execute_sql_callback(profile, profile->mutex, sql, sql2str_callback, &cbt); + *total_saved_messages = atoi(msg_count); +} + + +static void voicemail_check_main(switch_core_session_t *session, char *profile_name, char *domain_name, char *id, int auth) +{ + vm_check_state_t vm_check_state = VM_CHECK_START; + switch_channel_t *channel; + vm_profile_t *profile; + switch_xml_t x_domain, x_domain_root, x_user, x_params, x_param; + switch_status_t status; + char pass_buf[80] = "", *mypass = NULL, id_buf[80] = "", *myid = id, *myfolder = NULL; + const char *thepass = NULL; + char term = 0; + uint32_t timeout, attempts = 0; + int failed = 0; + msg_type_t play_msg_type = MSG_NONE; + char *dir_path = NULL, *file_path = NULL; + int total_new_messages = 0; + int total_saved_messages = 0; + int heard_auto_saved = 0, heard_auto_new = 0; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + + if (!(profile = switch_core_hash_find(globals.profile_hash, profile_name))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error invalid profile %s", profile_name); + return; + } + + x_user = x_domain = x_domain_root = NULL; + + timeout = profile->digit_timeout; + attempts = profile->max_login_attempts; + + status = switch_ivr_phrase_macro(session, VM_HELLO_MACRO, NULL, NULL, NULL); + + while(switch_channel_ready(channel)) { + + switch_ivr_sleep(session, 100); + + switch(vm_check_state) { + case VM_CHECK_START: + { + total_new_messages = 0; + total_saved_messages = 0; + heard_auto_saved = 0; + heard_auto_new = 0; + play_msg_type = MSG_NONE; + attempts = profile->max_login_attempts; + myid = id; + mypass = NULL; + myfolder = "inbox"; + vm_check_state = VM_CHECK_AUTH; + if (x_domain_root) { + switch_xml_free(x_domain_root); + } + x_user = x_domain = x_domain_root = NULL; + } + break; + case VM_CHECK_FOLDER_SUMMARY: + { + char msg_count[80] = ""; + switch_channel_set_variable(channel, "voicemail_current_folder", myfolder); + message_count(profile, myid, domain_name, myfolder, &total_new_messages, &total_saved_messages); + snprintf(msg_count, sizeof(msg_count), "%d", total_new_messages); + if ((status = switch_ivr_phrase_macro(session, VM_NEW_MESSAGE_COUNT_MACRO, msg_count, NULL, NULL)) != SWITCH_STATUS_SUCCESS) { + goto end; + } + + if (!heard_auto_new && total_new_messages > 0) { + heard_auto_new = 1; + play_msg_type = MSG_NEW; + vm_check_state = VM_CHECK_PLAY_MESSAGES; + continue; + } + + snprintf(msg_count, sizeof(msg_count), "%d", total_saved_messages); + if ((status = switch_ivr_phrase_macro(session, VM_SAVED_MESSAGE_COUNT_MACRO, msg_count, NULL, NULL)) != SWITCH_STATUS_SUCCESS) { + goto end; + } + + if (!heard_auto_saved && total_saved_messages > 0) { + heard_auto_saved = 1; + play_msg_type = MSG_SAVED; + vm_check_state = VM_CHECK_PLAY_MESSAGES; + continue; + } + + vm_check_state = VM_CHECK_MENU; + } + break; + case VM_CHECK_PLAY_MESSAGES: + { + listen_callback_t cbt; + char sql[128]; + int cur_message, total_messages; + message_count(profile, myid, domain_name, myfolder, &total_new_messages, &total_saved_messages); + memset(&cbt, 0, sizeof(cbt)); + switch(play_msg_type) { + case MSG_NEW: + { + snprintf(sql, sizeof(sql), + "select * from voicemail_data where user='%s' and domain='%s' and read_epoch=0", myid, domain_name); + total_messages = total_new_messages; + heard_auto_new = heard_auto_saved = 1; + } + break; + case MSG_SAVED: + default: + { + snprintf(sql, sizeof(sql), + "select * from voicemail_data where user='%s' and domain='%s' and read_epoch!=0", myid, domain_name); + total_messages = total_saved_messages; + heard_auto_new = heard_auto_saved = 1; + } + break; + } + for (cur_message = 0; cur_message < total_messages; cur_message++) { + cbt.index = 0; + cbt.want = cur_message; + cbt.type = play_msg_type; + vm_execute_sql_callback(profile, profile->mutex, sql, listen_callback, &cbt); + status = listen_file(session, profile, &cbt); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + break; + } + } + snprintf(sql, sizeof(sql), "update voicemail_data set read_epoch=%ld where user='%s' and domain='%s' and flags='save'", + (long)time(NULL), myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + snprintf(sql, sizeof(sql), "select file_path from voicemail_data where user='%s' and domain='%s' and flags='delete'", myid, domain_name); + vm_execute_sql_callback(profile, profile->mutex, sql, unlink_callback, NULL); + snprintf(sql, sizeof(sql), "delete from voicemail_data where user='%s' and domain='%s' and flags='delete'", myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + vm_check_state = VM_CHECK_FOLDER_SUMMARY; + } + break; + case VM_CHECK_CONFIG: + { + char *sql = NULL; + char input[10] = ""; + char key_buf[80] = ""; + callback_t cbt = { 0 }; + char msg_count[80] = ""; + cbt.buf = msg_count; + cbt.len = sizeof(msg_count); + sql = switch_mprintf("select count(*) from voicemail_prefs where user='%q' and domain = '%q'", myid, domain_name); + vm_execute_sql_callback(profile, profile->mutex, sql, sql2str_callback, &cbt); + switch_safe_free(sql); + if (switch_strlen_zero(msg_count) || !atoi(msg_count)) { + sql = switch_mprintf("insert into voicemail_prefs values('%q','%q','','')", myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(sql); + } + + snprintf(key_buf, sizeof(key_buf), "%s:%s:%s:%s", + profile->record_greeting_key, + profile->choose_greeting_key, + profile->record_name_key, + profile->main_menu_key); + + + TRY_CODE(vm_macro_get(session, VM_CONFIG_MENU_MACRO, key_buf, input, sizeof(input), 1, + profile->terminator_key, &term, timeout)); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto end; + } + + if (!strcmp(input, profile->main_menu_key)) { + vm_check_state = VM_CHECK_MENU; + } else if (!strcmp(input, profile->choose_greeting_key)) { + int num; + switch_input_args_t args = { 0 }; + args.input_callback = cancel_on_dtmf; + + TRY_CODE(vm_macro_get(session, VM_CHOOSE_GREETING_MACRO, key_buf, input, sizeof(input), 1, + profile->terminator_key, &term, timeout)); + + num = atoi(input); + file_path = switch_mprintf("%s%sgreeting_%d.wav", dir_path, SWITCH_PATH_SEPARATOR, num); + if (num < 1 || num > 3) { + status = SWITCH_STATUS_FALSE; + } else { + switch_file_handle_t fh = { 0 }; + memset(&fh, 0, sizeof(fh)); + args.input_callback = control_playback; + args.buf = &fh; + status = switch_ivr_play_file(session, NULL, file_path, &args); + } + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + TRY_CODE(switch_ivr_phrase_macro(session, VM_CHOOSE_GREETING_FAIL_MACRO, NULL, NULL, NULL)); + } else { + TRY_CODE(switch_ivr_phrase_macro(session, VM_CHOOSE_GREETING_SELECTED_MACRO, input, NULL, NULL)); + sql = switch_mprintf("update voicemail_prefs set greeting_path='%s' where user='%s' and domain='%s'", file_path, myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(sql); + } + switch_safe_free(file_path); + } else if (!strcmp(input, profile->record_greeting_key)) { + int num; + TRY_CODE(vm_macro_get(session, VM_CHOOSE_GREETING_MACRO, key_buf, input, sizeof(input), 1, + profile->terminator_key, &term, timeout)); + + num = atoi(input); + if (num < 1 || num > 3) { + TRY_CODE(switch_ivr_phrase_macro(session, VM_CHOOSE_GREETING_FAIL_MACRO, NULL, NULL, NULL)); + } else { + file_path = switch_mprintf("%s%sgreeting_%d.wav", dir_path, SWITCH_PATH_SEPARATOR, num); + TRY_CODE(create_file(session, profile, VM_RECORD_GREETING_MACRO, file_path)); + sql = switch_mprintf("update voicemail_prefs set greeting_path='%s' where user='%s' and domain='%s'", file_path, myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(sql); + switch_safe_free(file_path); + } + + } else if (!strcmp(input, profile->record_name_key)) { + file_path = switch_mprintf("%s%srecorded_name.wav", dir_path, SWITCH_PATH_SEPARATOR); + TRY_CODE(create_file(session, profile, VM_RECORD_NAME_MACRO, file_path)); + sql = switch_mprintf("update voicemail_prefs set name_path='%s' where user='%s' and domain='%s'", file_path, myid, domain_name); + vm_execute_sql(profile, sql, profile->mutex); + switch_safe_free(file_path); + switch_safe_free(sql); + } + + continue; + } + break; + case VM_CHECK_MENU: + { + char input[10] = ""; + char key_buf[80] = ""; + play_msg_type = MSG_NONE; + + snprintf(key_buf, sizeof(key_buf), "%s:%s:%s", + profile->play_new_messages_key, + profile->play_saved_messages_key, + profile->config_menu_key); + + status = vm_macro_get(session, VM_MENU_MACRO, key_buf, input, sizeof(input), 1, + profile->terminator_key, &term, timeout); + if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) { + goto end; + } + + if (!strcmp(input, profile->play_new_messages_key)) { + play_msg_type = MSG_NEW; + } else if (!strcmp(input, profile->play_saved_messages_key)) { + play_msg_type = MSG_SAVED; + } else if (!strcmp(input, profile->config_menu_key)) { + vm_check_state = VM_CHECK_CONFIG; + } + + if (play_msg_type) { + vm_check_state = VM_CHECK_PLAY_MESSAGES; + } + + + continue; + } + break; + case VM_CHECK_AUTH: + { + if (!attempts) { + failed = 1; + goto end; + } + + attempts--; + + if (!myid) { + status = vm_macro_get(session, VM_ENTER_ID_MACRO, profile->terminator_key, id_buf, sizeof(id_buf), 0, + profile->terminator_key, &term, timeout); + if (status != SWITCH_STATUS_SUCCESS) { + goto end; + } + + if (switch_strlen_zero(id_buf)) { + continue; + } else { + myid = id_buf; + } + } + + if (!x_user) { + int ok = 1; + char *xtra = switch_mprintf("mailbox=%s", myid); + + assert(xtra); + + if (switch_xml_locate_user(myid, domain_name, switch_channel_get_variable(channel, "network_addr"), + &x_domain_root, &x_domain, &x_user, xtra) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "can't find user [%s@%s]\n", myid, domain_name); + ok = 0; + } + + switch_safe_free(xtra); + + if (!ok) { + goto failed; + } + } + + if (!mypass) { + if (auth) { + mypass = "OK"; + } else { + status = vm_macro_get(session, VM_ENTER_PASS_MACRO, profile->terminator_key, + pass_buf, sizeof(pass_buf), 0, profile->terminator_key, &term, timeout); + if (status != SWITCH_STATUS_SUCCESS) { + goto end; + } + if (switch_strlen_zero(pass_buf)) { + continue; + } else { + mypass = pass_buf; + } + } + } + + if (!(x_params = switch_xml_child(x_user, "params"))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "can't find params for user [%s@%s]\n", myid, domain_name); + goto failed; + } + + thepass = NULL; + for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { + const char *var = switch_xml_attr_soft(x_param, "name"); + const char *val = switch_xml_attr_soft(x_param, "value"); + + if (!strcasecmp(var, "password")) { + thepass = val; + } else if (!strcasecmp(var, "vm-password")) { + thepass = val; + } + } + switch_xml_free(x_domain_root); + x_domain_root = NULL; + + if (auth || !thepass || (thepass && mypass && !strcmp(thepass, mypass))) { + if (!dir_path) { + dir_path = switch_core_session_sprintf(session, "%s%svoicemail%s%s%s%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, + SWITCH_PATH_SEPARATOR, + SWITCH_PATH_SEPARATOR, + profile->name, + SWITCH_PATH_SEPARATOR, + domain_name, + SWITCH_PATH_SEPARATOR, + myid); + + + if (switch_dir_make_recursive(dir_path, DEFAULT_DIR_PERMS, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s", dir_path); + return; + } + } + + + vm_check_state = VM_CHECK_FOLDER_SUMMARY; + //vm_check_state = VM_CHECK_MENU; + } else { + goto failed; + } + + continue; + failed: + status = switch_ivr_phrase_macro(session, VM_FAIL_AUTH_MACRO, NULL, NULL, NULL); + myid = id; + mypass = NULL; + continue; + } + break; + default: + break; + } + } + + end: + + if (switch_channel_ready(channel)) { + if (failed) { + status = switch_ivr_phrase_macro(session, VM_ABORT_MACRO, NULL, NULL, NULL); + } + status = switch_ivr_phrase_macro(session, VM_GOODBYE_MACRO, NULL, NULL, NULL); + } + + if (x_domain_root) { + switch_xml_free(x_domain_root); + } + +} + + +static switch_status_t voicemail_leave_main(switch_core_session_t *session, char *profile_name, char *domain_name, char *id) +{ + switch_channel_t *channel; + char *myfolder = "inbox"; + char sql[128]; + prefs_callback_t cbt; + vm_profile_t *profile; + char *uuid = switch_core_session_get_uuid(session); + char *file_path = NULL; + char *dir_path = NULL; + switch_status_t status = SWITCH_STATUS_SUCCESS; + switch_caller_profile_t *caller_profile; + switch_file_handle_t fh = { 0 }; + switch_input_args_t args = { 0 }; + char *email_vm = NULL; + + memset(&cbt, 0, sizeof(cbt)); + if (!(profile = switch_core_hash_find(globals.profile_hash, profile_name))) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error invalid profile %s", profile_name); + return SWITCH_STATUS_FALSE; + } + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + caller_profile = switch_channel_get_caller_profile(channel); + dir_path = switch_core_session_sprintf(session, "%s%svoicemail%s%s%s%s%s%s", SWITCH_GLOBAL_dirs.storage_dir, + SWITCH_PATH_SEPARATOR, + SWITCH_PATH_SEPARATOR, + profile->name, + SWITCH_PATH_SEPARATOR, + domain_name, + SWITCH_PATH_SEPARATOR, + id); + if (switch_dir_make_recursive(dir_path, DEFAULT_DIR_PERMS, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s", dir_path); + goto end; + } + + + if (id) { + int ok = 1; + char *xtra = switch_mprintf("mailbox=%s", id); + switch_xml_t x_domain, x_domain_root, x_user, x_params, x_param; + + assert(xtra); + x_user = x_domain = x_domain_root = NULL; + if (switch_xml_locate_user(id, domain_name, switch_channel_get_variable(channel, "network_addr"), + &x_domain_root, &x_domain, &x_user, xtra) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING, "can't find user [%s@%s]\n", id, domain_name); + ok = 0; + } + + if ((x_params = switch_xml_child(x_user, "params"))) { + for (x_param = switch_xml_child(x_params, "param"); x_param; x_param = x_param->next) { + const char *var = switch_xml_attr_soft(x_param, "name"); + const char *val = switch_xml_attr_soft(x_param, "value"); + + if (!strcasecmp(var, "vm-mailto")) { + email_vm = switch_core_session_strdup(session, val); + } + } + } + + switch_safe_free(xtra); + switch_xml_free(x_domain_root); + if (!ok) { + goto end; + } + } + + snprintf(sql, sizeof(sql), + "select * from voicemail_prefs where user='%s' and domain='%s'", + id, + domain_name); + vm_execute_sql_callback(profile, profile->mutex, sql, prefs_callback, &cbt); + + file_path = switch_mprintf("%s%smsg_%s.wav", dir_path, SWITCH_PATH_SEPARATOR, uuid); + + memset(&fh, 0, sizeof(fh)); + args.input_callback = control_playback; + args.buf = &fh; + + if (!switch_strlen_zero(cbt.greeting_path)) { + TRY_CODE(switch_ivr_play_file(session, NULL, cbt.greeting_path, NULL)); + } else { + if (!switch_strlen_zero(cbt.name_path)) { + TRY_CODE(switch_ivr_play_file(session, NULL, cbt.name_path, NULL)); + } + TRY_CODE(switch_ivr_phrase_macro(session, VM_PLAY_GREETING_MACRO, id, NULL, NULL)); + } + + if(switch_channel_ready(channel)) { + char *usql; + status = create_file(session, profile, VM_RECORD_MESSAGE_MACRO, file_path); + if (status == SWITCH_STATUS_SUCCESS || status == SWITCH_STATUS_BREAK) { + usql = switch_mprintf("insert into voicemail_data values(%ld,0,'%q','%q','%q','%q','%q','%q','%q','')", (long)time(NULL), + id, domain_name, uuid, caller_profile->caller_id_name, caller_profile->caller_id_number, myfolder, file_path); + vm_execute_sql(profile, usql, profile->mutex); + switch_safe_free(usql); + } else { + unlink(file_path); + goto end; + } + + } + + end: + + if (!switch_strlen_zero(email_vm)) { + + switch_event_t *event; + char *from = switch_core_session_sprintf(session, "FreeSWITCH mod_voicemail <%s@%s>", id, domain_name); + char *headers = switch_core_session_sprintf(session, "Subject: Voicemail from %s %s", + caller_profile->caller_id_name, caller_profile->caller_id_number); + char *body; + + + if (switch_event_create(&event, SWITCH_EVENT_MESSAGE) == SWITCH_STATUS_SUCCESS) { + switch_channel_event_set_data(channel, event); + switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Message-Type", "voicemail"); + switch_event_serialize(event, &body); + switch_event_fire(&event); + } else { + body = switch_mprintf("Voicemail from %s %s", + caller_profile->caller_id_name, caller_profile->caller_id_number); + } + + //TBD add better formatting to the body + vm_email(email_vm, from, headers, body, file_path); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Sending message to %s", email_vm); + switch_safe_free(body); + } + + switch_safe_free(file_path); + + if (switch_channel_ready(channel)) { + status = switch_ivr_phrase_macro(session, VM_GOODBYE_MACRO, NULL, NULL, NULL); + } + + return status; + + +} + + +#define VM_DESC "voicemail" +#define VM_USAGE "[check|auth] []" + +SWITCH_STANDARD_APP(voicemail_function) +{ + int argc = 0; + char *argv[4] = { 0 }; + char *mydata = NULL; + char *profile_name = NULL; + char *domain_name = NULL; + char *id = NULL; + char *auth_var = NULL; + int x = 0, check = 0, auth = 0; + switch_channel_t *channel; + + channel = switch_core_session_get_channel(session); + assert(channel != NULL); + + if (switch_dir_make_recursive(SWITCH_GLOBAL_dirs.storage_dir, DEFAULT_DIR_PERMS, switch_core_session_get_pool(session)) != SWITCH_STATUS_SUCCESS) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error creating %s", SWITCH_GLOBAL_dirs.storage_dir); + return; + } + + if (!switch_strlen_zero(data)) { + mydata = switch_core_session_strdup(session, data); + argc = switch_separate_string(mydata, ' ', argv, (sizeof(argv) / sizeof(argv[0]))); + } + + if (argv[x] && !strcasecmp(argv[x], "check")) { + check++; + x++; + } + + if (argv[x] && !strcasecmp(argv[x], "auth")) { + auth++; + x++; + } + + if (argv[x]) { + profile_name = argv[x++]; + } + + if (argv[x]) { + domain_name = argv[x++]; + } + + if (argv[x]) { + id = argv[x++]; + } + + + if ((auth_var = switch_channel_get_variable(channel, "voicemail_authorized")) && switch_true(auth_var)) { + auth = 1; + } + + if (switch_strlen_zero(profile_name)) { + profile_name = switch_channel_get_variable(channel, "voicemail_profile_name"); + } + + if (switch_strlen_zero(domain_name)) { + domain_name = switch_channel_get_variable(channel, "voicemail_domain_name"); + } + + if (switch_strlen_zero(id)) { + id = switch_channel_get_variable(channel, "voicemail_id"); + } + + if (switch_strlen_zero(profile_name) || switch_strlen_zero(domain_name)) { + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error Usage: %s\n", VM_USAGE); + return; + } + + if (check) { + voicemail_check_main(session, profile_name, domain_name, id, auth); + } else { + voicemail_leave_main(session, profile_name, domain_name, id); + } +} + +SWITCH_MODULE_LOAD_FUNCTION(mod_voicemail_load) +{ + switch_application_interface_t *app_interface; + switch_status_t status; + + if ((status = load_config()) != SWITCH_STATUS_SUCCESS) { + return status; + } + /* connect my internal structure to the blank pointer passed to me */ + *module_interface = switch_loadable_module_create_module_interface(pool, modname); + SWITCH_ADD_APP(app_interface, "voicemail", "Voicemail", VM_DESC, voicemail_function, VM_USAGE, SAF_NONE); + + /* indicate that the module should continue to be loaded */ + return SWITCH_STATUS_NOUNLOAD; +} + +/* For Emacs: + * Local Variables: + * mode:c + * indent-tabs-mode:nil + * tab-width:4 + * c-basic-offset:4 + * End: + * For VIM: + * vim:set softtabstop=4 shiftwidth=4 tabstop=4 expandtab: + */ diff --git a/src/mod/applications/mod_voicemail/mod_voicemail.vcproj b/src/mod/applications/mod_voicemail/mod_voicemail.vcproj new file mode 100644 index 0000000000..58734f6c88 --- /dev/null +++ b/src/mod/applications/mod_voicemail/mod_voicemail.vcproj @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/switch_apr.c b/src/switch_apr.c index 52a0fc8796..6c11e6d336 100644 --- a/src/switch_apr.c +++ b/src/switch_apr.c @@ -394,6 +394,51 @@ SWITCH_DECLARE(switch_status_t) switch_file_exists(const char *filename, switch_ return status; } +/* #define SWITCH_FPROT_USETID 0x8000 /\**< Set user id *\/ */ +/* #define SWITCH_FPROT_UREAD 0x0400 /\**< Read by user *\/ */ +/* #define SWITCH_FPROT_UWRITE 0x0200 /\**< Write by user *\/ */ +/* #define SWITCH_FPROT_UEXECUTE 0x0100 /\**< Execute by user *\/ */ + +/* #define SWITCH_FPROT_GSETID 0x4000 /\**< Set group id *\/ */ +/* #define SWITCH_FPROT_GREAD 0x0040 /\**< Read by group *\/ */ +/* #define SWITCH_FPROT_GWRITE 0x0020 /\**< Write by group *\/ */ +/* #define SWITCH_FPROT_GEXECUTE 0x0010 /\**< Execute by group *\/ */ + +/* #define SWITCH_FPROT_WSTICKY 0x2000 /\**< Sticky bit *\/ */ +/* #define SWITCH_FPROT_WREAD 0x0004 /\**< Read by others *\/ */ +/* #define SWITCH_FPROT_WWRITE 0x0002 /\**< Write by others *\/ */ +/* #define SWITCH_FPROT_WEXECUTE 0x0001 /\**< Execute by others *\/ */ + +/* #define SWITCH_FPROT_OS_DEFAULT 0x0FFF /\**< use OS's default permissions *\/ */ + + +/** + * Create a new directory on the file system. + * @param path the path for the directory to be created. (use / on all systems) + * @param perm Permissions for the new direcoty. + * @param pool the pool to use. + */ +SWITCH_DECLARE(switch_status_t) switch_dir_make(const char *path, switch_fileperms_t perm, + switch_memory_pool_t *pool) +{ + return apr_dir_make(path, perm, pool); +} + +/** Creates a new directory on the file system, but behaves like + * 'mkdir -p'. Creates intermediate directories as required. No error + * will be reported if PATH already exists. + * @param path the path for the directory to be created. (use / on all systems) + * @param perm Permissions for the new direcoty. + * @param pool the pool to use. + */ +SWITCH_DECLARE(switch_status_t) switch_dir_make_recursive(const char *path, + switch_fileperms_t perm, + switch_memory_pool_t *pool) +{ + return apr_dir_make_recursive(path, perm, pool); +} + + struct switch_dir { apr_dir_t *dir_handle; apr_finfo_t finfo; diff --git a/src/switch_core.c b/src/switch_core.c index 18718c687c..8f80c25f19 100644 --- a/src/switch_core.c +++ b/src/switch_core.c @@ -295,6 +295,14 @@ SWITCH_DECLARE(void) switch_core_set_globals(void) #endif } + if (!SWITCH_GLOBAL_dirs.storage_dir && (SWITCH_GLOBAL_dirs.storage_dir = (char *) malloc(BUFSIZE))) { +#ifdef SWITCH_STORAGE_DIR + switch_snprintf(SWITCH_GLOBAL_dirs.storage_dir, BUFSIZE, "%s", SWITCH_STORAGE_DIR); +#else + switch_snprintf(SWITCH_GLOBAL_dirs.storage_dir, BUFSIZE, "%s%sstorage", base_dir, SWITCH_PATH_SEPARATOR); +#endif + } + if (!SWITCH_GLOBAL_dirs.db_dir && (SWITCH_GLOBAL_dirs.db_dir = (char *) malloc(BUFSIZE))) { #ifdef SWITCH_DB_DIR switch_snprintf(SWITCH_GLOBAL_dirs.db_dir, BUFSIZE, "%s", SWITCH_DB_DIR); diff --git a/src/switch_core_file.c b/src/switch_core_file.c index f4b0beda71..3c5d68492f 100644 --- a/src/switch_core_file.c +++ b/src/switch_core_file.c @@ -55,7 +55,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_file_open(switch_file_handle_t *fh, } if ((fh->file_interface = switch_loadable_module_get_file_interface(ext)) == 0) { - switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid file format [%s]!\n", ext); + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "invalid file format [%s] for [%s]!\n", ext, file_path); return SWITCH_STATUS_GENERR; } diff --git a/src/switch_xml.c b/src/switch_xml.c index e417918895..b2cf7bc8bd 100644 --- a/src/switch_xml.c +++ b/src/switch_xml.c @@ -1241,7 +1241,8 @@ SWITCH_DECLARE(switch_status_t) switch_xml_locate_domain(char *domain_name, char } -SWITCH_DECLARE(switch_status_t) switch_xml_locate_user(char *user_name, char *domain_name, +SWITCH_DECLARE(switch_status_t) switch_xml_locate_user(char *user_name, + char *domain_name, char *ip, switch_xml_t *root, switch_xml_t *domain, @@ -1272,7 +1273,8 @@ SWITCH_DECLARE(switch_status_t) switch_xml_locate_user(char *user_name, char *do } if (user_name) { - if (!(*user = switch_xml_find_child(*domain, "user", "id", user_name))) { + if (!(*user = switch_xml_find_child(*domain, "user", "id", user_name)) && strstr(xtra_params, "mailbox") && + !(*user = switch_xml_find_child(*domain, "user", "mailbox", user_name))) { return SWITCH_STATUS_FALSE; } return SWITCH_STATUS_SUCCESS;