00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144 #include "asterisk.h"
00145
00146 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 276347 $")
00147
00148 #include <ctype.h>
00149 #include <sys/time.h>
00150 #include <sys/stat.h>
00151 #include <sys/mman.h>
00152 #include <time.h>
00153 #include <dirent.h>
00154 #include <locale.h>
00155
00156
00157 #include "asterisk/paths.h"
00158 #include "asterisk/lock.h"
00159 #include "asterisk/file.h"
00160 #include "asterisk/channel.h"
00161 #include "asterisk/pbx.h"
00162 #include "asterisk/config.h"
00163 #include "asterisk/say.h"
00164 #include "asterisk/module.h"
00165 #include "asterisk/app.h"
00166 #include "asterisk/manager.h"
00167 #include "asterisk/dsp.h"
00168 #include "asterisk/localtime.h"
00169 #include "asterisk/cli.h"
00170 #include "asterisk/utils.h"
00171 #include "asterisk/linkedlists.h"
00172 #include "asterisk/callerid.h"
00173 #include "asterisk/event.h"
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404
00405
00406
00407
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432
00433
00434
00435
00436
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466
00467
00468
00469
00470
00471
00472
00473
00474
00475
00476
00477
00478
00479
00480
00481
00482
00483
00484
00485
00486
00487
00488
00489
00490
00491
00492
00493
00494
00495
00496
00497
00498 #ifndef TRUE
00499 #define TRUE 1
00500 #endif
00501 #ifndef FALSE
00502 #define FALSE 0
00503 #endif
00504
00505
00506 #define MVM_REVIEW (1 << 0)
00507 #define MVM_OPERATOR (1 << 1)
00508 #define MVM_REALTIME (1 << 2)
00509 #define MVM_SVMAIL (1 << 3)
00510 #define MVM_ENVELOPE (1 << 4)
00511 #define MVM_PBXSKIP (1 << 9)
00512 #define MVM_ALLOCED (1 << 13)
00513
00514
00515
00516 #define SENDMAIL "/usr/sbin/sendmail -t"
00517
00518 #define SOUND_INTRO "vm-intro"
00519 #define B64_BASEMAXINLINE 256
00520 #define B64_BASELINELEN 72
00521 #define EOL "\r\n"
00522
00523 #define MAX_DATETIME_FORMAT 512
00524 #define MAX_NUM_CID_CONTEXTS 10
00525
00526 #define ERROR_LOCK_PATH -100
00527 #define VOICEMAIL_DIR_MODE 0700
00528
00529 #define VOICEMAIL_CONFIG "minivm.conf"
00530 #define ASTERISK_USERNAME "asterisk"
00531
00532
00533 enum mvm_messagetype {
00534 MVM_MESSAGE_EMAIL,
00535 MVM_MESSAGE_PAGE
00536
00537 };
00538
00539 static char MVM_SPOOL_DIR[PATH_MAX];
00540
00541
00542 static char *app_minivm_record = "MinivmRecord";
00543 static char *app_minivm_greet = "MinivmGreet";
00544 static char *app_minivm_notify = "MinivmNotify";
00545 static char *app_minivm_delete = "MinivmDelete";
00546 static char *app_minivm_accmess = "MinivmAccMess";
00547 static char *app_minivm_mwi = "MinivmMWI";
00548
00549
00550
00551 enum minivm_option_flags {
00552 OPT_SILENT = (1 << 0),
00553 OPT_BUSY_GREETING = (1 << 1),
00554 OPT_UNAVAIL_GREETING = (1 << 2),
00555 OPT_TEMP_GREETING = (1 << 3),
00556 OPT_NAME_GREETING = (1 << 4),
00557 OPT_RECORDGAIN = (1 << 5),
00558 };
00559
00560 enum minivm_option_args {
00561 OPT_ARG_RECORDGAIN = 0,
00562 OPT_ARG_ARRAY_SIZE = 1,
00563 };
00564
00565 AST_APP_OPTIONS(minivm_app_options, {
00566 AST_APP_OPTION('s', OPT_SILENT),
00567 AST_APP_OPTION('b', OPT_BUSY_GREETING),
00568 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
00569 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
00570 });
00571
00572 AST_APP_OPTIONS(minivm_accmess_options, {
00573 AST_APP_OPTION('b', OPT_BUSY_GREETING),
00574 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
00575 AST_APP_OPTION('t', OPT_TEMP_GREETING),
00576 AST_APP_OPTION('n', OPT_NAME_GREETING),
00577 });
00578
00579
00580
00581 struct minivm_account {
00582 char username[AST_MAX_CONTEXT];
00583 char domain[AST_MAX_CONTEXT];
00584
00585 char pincode[10];
00586 char fullname[120];
00587 char email[80];
00588 char pager[80];
00589 char accountcode[AST_MAX_ACCOUNT_CODE];
00590 char serveremail[80];
00591 char externnotify[160];
00592 char language[MAX_LANGUAGE];
00593 char zonetag[80];
00594 char uniqueid[20];
00595 char exit[80];
00596 char attachfmt[80];
00597 char etemplate[80];
00598 char ptemplate[80];
00599 unsigned int flags;
00600 struct ast_variable *chanvars;
00601 double volgain;
00602 AST_LIST_ENTRY(minivm_account) list;
00603 };
00604
00605
00606
00607 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
00608
00609
00610
00611
00612
00613
00614 struct minivm_template {
00615 char name[80];
00616 char *body;
00617 char fromaddress[100];
00618 char serveremail[80];
00619 char subject[100];
00620 char charset[32];
00621 char locale[20];
00622 char dateformat[80];
00623 int attachment;
00624 AST_LIST_ENTRY(minivm_template) list;
00625 };
00626
00627
00628 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
00629
00630
00631 struct leave_vm_options {
00632 unsigned int flags;
00633 signed char record_gain;
00634 };
00635
00636
00637 struct b64_baseio {
00638 int iocp;
00639 int iolen;
00640 int linelength;
00641 int ateof;
00642 unsigned char iobuf[B64_BASEMAXINLINE];
00643 };
00644
00645
00646 struct minivm_zone {
00647 char name[80];
00648 char timezone[80];
00649 char msg_format[BUFSIZ];
00650 AST_LIST_ENTRY(minivm_zone) list;
00651 };
00652
00653
00654 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
00655
00656
00657 struct minivm_stats {
00658 int voicemailaccounts;
00659 int timezones;
00660 int templates;
00661
00662 struct timeval reset;
00663 int receivedmessages;
00664 struct timeval lastreceived;
00665 };
00666
00667
00668 static struct minivm_stats global_stats;
00669
00670 AST_MUTEX_DEFINE_STATIC(minivmlock);
00671 AST_MUTEX_DEFINE_STATIC(minivmloglock);
00672
00673 static FILE *minivmlogfile;
00674
00675 static int global_vmminmessage;
00676 static int global_vmmaxmessage;
00677 static int global_maxsilence;
00678 static int global_maxgreet;
00679 static int global_silencethreshold = 128;
00680 static char global_mailcmd[160];
00681 static char global_externnotify[160];
00682 static char global_logfile[PATH_MAX];
00683 static char default_vmformat[80];
00684
00685 static struct ast_flags globalflags = {0};
00686 static int global_saydurationminfo;
00687
00688 static double global_volgain;
00689
00690
00691
00692 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
00693 #define DEFAULT_CHARSET "ISO-8859-1"
00694
00695
00696 static char *message_template_parse_filebody(const char *filename);
00697 static char *message_template_parse_emailbody(const char *body);
00698 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
00699 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
00700 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
00701
00702
00703
00704 static struct minivm_template *message_template_create(const char *name)
00705 {
00706 struct minivm_template *template;
00707
00708 template = ast_calloc(1, sizeof(*template));
00709 if (!template)
00710 return NULL;
00711
00712
00713 ast_copy_string(template->name, name, sizeof(template->name));
00714 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
00715 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
00716 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
00717 template->attachment = TRUE;
00718
00719 return template;
00720 }
00721
00722
00723
00724 static void message_template_free(struct minivm_template *template)
00725 {
00726 if (template->body)
00727 ast_free(template->body);
00728
00729 ast_free (template);
00730 }
00731
00732
00733
00734 static int message_template_build(const char *name, struct ast_variable *var)
00735 {
00736 struct minivm_template *template;
00737 int error = 0;
00738
00739 template = message_template_create(name);
00740 if (!template) {
00741 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
00742 return -1;
00743 }
00744
00745 while (var) {
00746 ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
00747 if (!strcasecmp(var->name, "fromaddress")) {
00748 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
00749 } else if (!strcasecmp(var->name, "fromemail")) {
00750 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
00751 } else if (!strcasecmp(var->name, "subject")) {
00752 ast_copy_string(template->subject, var->value, sizeof(template->subject));
00753 } else if (!strcasecmp(var->name, "locale")) {
00754 ast_copy_string(template->locale, var->value, sizeof(template->locale));
00755 } else if (!strcasecmp(var->name, "attachmedia")) {
00756 template->attachment = ast_true(var->value);
00757 } else if (!strcasecmp(var->name, "dateformat")) {
00758 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
00759 } else if (!strcasecmp(var->name, "charset")) {
00760 ast_copy_string(template->charset, var->value, sizeof(template->charset));
00761 } else if (!strcasecmp(var->name, "templatefile")) {
00762 if (template->body)
00763 ast_free(template->body);
00764 template->body = message_template_parse_filebody(var->value);
00765 if (!template->body) {
00766 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
00767 error++;
00768 }
00769 } else if (!strcasecmp(var->name, "messagebody")) {
00770 if (template->body)
00771 ast_free(template->body);
00772 template->body = message_template_parse_emailbody(var->value);
00773 if (!template->body) {
00774 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
00775 error++;
00776 }
00777 } else {
00778 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
00779 error++;
00780 }
00781 var = var->next;
00782 }
00783 if (error)
00784 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
00785
00786 AST_LIST_LOCK(&message_templates);
00787 AST_LIST_INSERT_TAIL(&message_templates, template, list);
00788 AST_LIST_UNLOCK(&message_templates);
00789
00790 global_stats.templates++;
00791
00792 return error;
00793 }
00794
00795
00796
00797 static struct minivm_template *message_template_find(const char *name)
00798 {
00799 struct minivm_template *this, *res = NULL;
00800
00801 if (ast_strlen_zero(name))
00802 return NULL;
00803
00804 AST_LIST_LOCK(&message_templates);
00805 AST_LIST_TRAVERSE(&message_templates, this, list) {
00806 if (!strcasecmp(this->name, name)) {
00807 res = this;
00808 break;
00809 }
00810 }
00811 AST_LIST_UNLOCK(&message_templates);
00812
00813 return res;
00814 }
00815
00816
00817
00818
00819 static void message_destroy_list(void)
00820 {
00821 struct minivm_template *this;
00822 AST_LIST_LOCK(&message_templates);
00823 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
00824 message_template_free(this);
00825 }
00826
00827 AST_LIST_UNLOCK(&message_templates);
00828 }
00829
00830
00831
00832 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
00833 {
00834 int l;
00835
00836 if (bio->ateof)
00837 return 0;
00838
00839 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
00840 if (ferror(fi))
00841 return -1;
00842
00843 bio->ateof = 1;
00844 return 0;
00845 }
00846
00847 bio->iolen= l;
00848 bio->iocp= 0;
00849
00850 return 1;
00851 }
00852
00853
00854
00855 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
00856 {
00857 if (bio->iocp >= bio->iolen) {
00858 if (!b64_inbuf(bio, fi))
00859 return EOF;
00860 }
00861
00862 return bio->iobuf[bio->iocp++];
00863 }
00864
00865
00866
00867 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
00868 {
00869 if (bio->linelength >= B64_BASELINELEN) {
00870 if (fputs(EOL,so) == EOF)
00871 return -1;
00872
00873 bio->linelength= 0;
00874 }
00875
00876 if (putc(((unsigned char) c), so) == EOF)
00877 return -1;
00878
00879 bio->linelength++;
00880
00881 return 1;
00882 }
00883
00884
00885
00886 static int base_encode(char *filename, FILE *so)
00887 {
00888 unsigned char dtable[B64_BASEMAXINLINE];
00889 int i,hiteof= 0;
00890 FILE *fi;
00891 struct b64_baseio bio;
00892
00893 memset(&bio, 0, sizeof(bio));
00894 bio.iocp = B64_BASEMAXINLINE;
00895
00896 if (!(fi = fopen(filename, "rb"))) {
00897 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
00898 return -1;
00899 }
00900
00901 for (i= 0; i<9; i++) {
00902 dtable[i]= 'A'+i;
00903 dtable[i+9]= 'J'+i;
00904 dtable[26+i]= 'a'+i;
00905 dtable[26+i+9]= 'j'+i;
00906 }
00907 for (i= 0; i < 8; i++) {
00908 dtable[i+18]= 'S'+i;
00909 dtable[26+i+18]= 's'+i;
00910 }
00911 for (i= 0; i < 10; i++) {
00912 dtable[52+i]= '0'+i;
00913 }
00914 dtable[62]= '+';
00915 dtable[63]= '/';
00916
00917 while (!hiteof){
00918 unsigned char igroup[3], ogroup[4];
00919 int c,n;
00920
00921 igroup[0]= igroup[1]= igroup[2]= 0;
00922
00923 for (n= 0; n < 3; n++) {
00924 if ((c = b64_inchar(&bio, fi)) == EOF) {
00925 hiteof= 1;
00926 break;
00927 }
00928 igroup[n]= (unsigned char)c;
00929 }
00930
00931 if (n> 0) {
00932 ogroup[0]= dtable[igroup[0]>>2];
00933 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
00934 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
00935 ogroup[3]= dtable[igroup[2]&0x3F];
00936
00937 if (n<3) {
00938 ogroup[3]= '=';
00939
00940 if (n<2)
00941 ogroup[2]= '=';
00942 }
00943
00944 for (i= 0;i<4;i++)
00945 b64_ochar(&bio, ogroup[i], so);
00946 }
00947 }
00948
00949
00950 if (fputs(EOL, so) == EOF)
00951 return 0;
00952
00953 fclose(fi);
00954
00955 return 1;
00956 }
00957
00958 static int get_date(char *s, int len)
00959 {
00960 struct ast_tm tm;
00961 struct timeval now = ast_tvnow();
00962
00963 ast_localtime(&now, &tm, NULL);
00964 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
00965 }
00966
00967
00968
00969
00970 static void free_user(struct minivm_account *vmu)
00971 {
00972 if (vmu->chanvars)
00973 ast_variables_destroy(vmu->chanvars);
00974 ast_free(vmu);
00975 }
00976
00977
00978
00979
00980
00981
00982
00983 static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter)
00984 {
00985 char callerid[256];
00986 struct ast_variable *var;
00987
00988 if (!channel) {
00989 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
00990 return;
00991 }
00992
00993 for (var = vmu->chanvars ; var ; var = var->next) {
00994 pbx_builtin_setvar_helper(channel, var->name, var->value);
00995 }
00996
00997
00998 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
00999 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
01000 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
01001 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
01002 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
01003 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
01004 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
01005 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
01006 if (!ast_strlen_zero(counter))
01007 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
01008 }
01009
01010
01011
01012 static void populate_defaults(struct minivm_account *vmu)
01013 {
01014 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
01015 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
01016 vmu->volgain = global_volgain;
01017 }
01018
01019
01020
01021 static struct minivm_account *mvm_user_alloc(void)
01022 {
01023 struct minivm_account *new;
01024
01025 new = ast_calloc(1, sizeof(*new));
01026 if (!new)
01027 return NULL;
01028 populate_defaults(new);
01029
01030 return new;
01031 }
01032
01033
01034
01035
01036 static void vmaccounts_destroy_list(void)
01037 {
01038 struct minivm_account *this;
01039 AST_LIST_LOCK(&minivm_accounts);
01040 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
01041 ast_free(this);
01042 AST_LIST_UNLOCK(&minivm_accounts);
01043 }
01044
01045
01046
01047
01048 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
01049 {
01050 struct minivm_account *vmu = NULL, *cur;
01051
01052
01053 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
01054 ast_log(LOG_NOTICE, "No username or domain? \n");
01055 return NULL;
01056 }
01057 ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
01058
01059 AST_LIST_LOCK(&minivm_accounts);
01060 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
01061
01062 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
01063 break;
01064 }
01065 AST_LIST_UNLOCK(&minivm_accounts);
01066
01067 if (cur) {
01068 ast_debug(3, "Found account for %s@%s\n", username, domain);
01069 vmu = cur;
01070
01071 } else
01072 vmu = find_user_realtime(domain, username);
01073
01074 if (createtemp && !vmu) {
01075
01076 vmu = mvm_user_alloc();
01077 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
01078 if (vmu) {
01079 ast_copy_string(vmu->username, username, sizeof(vmu->username));
01080 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
01081 ast_debug(1, "Created temporary account\n");
01082 }
01083
01084 }
01085 return vmu;
01086 }
01087
01088
01089
01090
01091
01092 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
01093 {
01094 struct ast_variable *var;
01095 struct minivm_account *retval;
01096 char name[MAXHOSTNAMELEN];
01097
01098 retval = mvm_user_alloc();
01099 if (!retval)
01100 return NULL;
01101
01102 if (username)
01103 ast_copy_string(retval->username, username, sizeof(retval->username));
01104
01105 populate_defaults(retval);
01106 var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
01107
01108 if (!var) {
01109 ast_free(retval);
01110 return NULL;
01111 }
01112
01113 snprintf(name, sizeof(name), "%s@%s", username, domain);
01114 create_vmaccount(name, var, TRUE);
01115
01116 ast_variables_destroy(var);
01117 return retval;
01118 }
01119
01120
01121
01122
01123
01124
01125 static int check_mime(const char *str)
01126 {
01127 for (; *str; str++) {
01128 if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
01129 return 1;
01130 }
01131 }
01132 return 0;
01133 }
01134
01135
01136
01137
01138
01139
01140
01141
01142
01143
01144
01145
01146
01147
01148
01149
01150
01151
01152
01153
01154 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *charset, const char *start, size_t preamble, size_t postamble)
01155 {
01156 struct ast_str *tmp = ast_str_alloca(80);
01157 int first_section = 1;
01158 *end = '\0';
01159
01160 ast_str_reset(*end);
01161 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01162 for (; *start; start++) {
01163 int need_encoding = 0;
01164 if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
01165 need_encoding = 1;
01166 }
01167 if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
01168 (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
01169 (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
01170 (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
01171
01172 ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
01173 ast_str_set(&tmp, -1, "=?%s?Q?", charset);
01174 first_section = 0;
01175 }
01176 if (need_encoding && *start == ' ') {
01177 ast_str_append(&tmp, -1, "_");
01178 } else if (need_encoding) {
01179 ast_str_append(&tmp, -1, "=%hhX", *start);
01180 } else {
01181 ast_str_append(&tmp, -1, "%c", *start);
01182 }
01183 }
01184 ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
01185 return ast_str_buffer(*end);
01186 }
01187
01188
01189
01190
01191
01192
01193
01194
01195
01196 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
01197 {
01198 const char *ptr;
01199
01200
01201 ast_str_set(buf, maxlen, "\"");
01202 for (ptr = from; *ptr; ptr++) {
01203 if (*ptr == '"' || *ptr == '\\') {
01204 ast_str_append(buf, maxlen, "\\%c", *ptr);
01205 } else {
01206 ast_str_append(buf, maxlen, "%c", *ptr);
01207 }
01208 }
01209 ast_str_append(buf, maxlen, "\"");
01210
01211 return ast_str_buffer(*buf);
01212 }
01213
01214
01215
01216 static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
01217 {
01218 FILE *p = NULL;
01219 int pfd;
01220 char email[256] = "";
01221 char who[256] = "";
01222 char date[256];
01223 char bound[256];
01224 char fname[PATH_MAX];
01225 char dur[PATH_MAX];
01226 char tmp[80] = "/tmp/astmail-XXXXXX";
01227 char tmp2[PATH_MAX];
01228 struct timeval now;
01229 struct ast_tm tm;
01230 struct minivm_zone *the_zone = NULL;
01231 struct ast_channel *ast;
01232 char *finalfilename;
01233 struct ast_str *str1 = ast_str_create(16), *str2 = ast_str_create(16);
01234 char *fromaddress;
01235 char *fromemail;
01236
01237 if (!str1 || !str2) {
01238 ast_free(str1);
01239 ast_free(str2);
01240 return -1;
01241 }
01242
01243 if (type == MVM_MESSAGE_EMAIL) {
01244 if (vmu && !ast_strlen_zero(vmu->email)) {
01245 ast_copy_string(email, vmu->email, sizeof(email));
01246 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
01247 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
01248 } else if (type == MVM_MESSAGE_PAGE) {
01249 ast_copy_string(email, vmu->pager, sizeof(email));
01250 }
01251
01252 if (ast_strlen_zero(email)) {
01253 ast_log(LOG_WARNING, "No address to send message to.\n");
01254 return -1;
01255 }
01256
01257 ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
01258
01259 if (!strcmp(format, "wav49"))
01260 format = "WAV";
01261
01262
01263
01264 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
01265 char newtmp[PATH_MAX];
01266 char tmpcmd[PATH_MAX];
01267 int tmpfd;
01268
01269 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
01270 ast_debug(3, "newtmp: %s\n", newtmp);
01271 tmpfd = mkstemp(newtmp);
01272 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
01273 ast_safe_system(tmpcmd);
01274 finalfilename = newtmp;
01275 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
01276 } else {
01277 finalfilename = ast_strdupa(filename);
01278 }
01279
01280
01281 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
01282
01283 if (template->attachment)
01284 ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
01285
01286
01287
01288 pfd = mkstemp(tmp);
01289 if (pfd > -1) {
01290 p = fdopen(pfd, "w");
01291 if (!p) {
01292 close(pfd);
01293 pfd = -1;
01294 }
01295 ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
01296 }
01297 if (!p) {
01298 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
01299 return -1;
01300 }
01301
01302 ast = ast_dummy_channel_alloc();
01303
01304 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
01305
01306
01307 if (!ast_strlen_zero(vmu->zonetag)) {
01308
01309 struct minivm_zone *z;
01310 AST_LIST_LOCK(&minivm_zones);
01311 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
01312 if (strcmp(z->name, vmu->zonetag))
01313 continue;
01314 the_zone = z;
01315 }
01316 AST_LIST_UNLOCK(&minivm_zones);
01317 }
01318
01319 now = ast_tvnow();
01320 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
01321 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
01322
01323
01324 fprintf(p, "Date: %s\n", date);
01325
01326
01327 ast_strftime(date, sizeof(date), template->dateformat, &tm);
01328
01329
01330
01331 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
01332
01333
01334
01335 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
01336
01337
01338 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
01339
01340
01341 if (ast_strlen_zero(fromemail))
01342 fromemail = "asterisk";
01343
01344 if (strchr(fromemail, '@'))
01345 ast_copy_string(who, fromemail, sizeof(who));
01346 else {
01347 char host[MAXHOSTNAMELEN];
01348 gethostname(host, sizeof(host)-1);
01349 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
01350 }
01351
01352 if (ast_strlen_zero(fromaddress)) {
01353 fprintf(p, "From: Asterisk PBX <%s>\n", who);
01354 } else {
01355 ast_debug(4, "Fromaddress template: %s\n", fromaddress);
01356 ast_str_substitute_variables(&str1, 0, ast, fromaddress);
01357 if (check_mime(ast_str_buffer(str1))) {
01358 int first_line = 1;
01359 char *ptr;
01360 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
01361 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01362 *ptr = '\0';
01363 fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
01364 first_line = 0;
01365
01366 ast_str_set(&str2, 0, "%s", ptr + 1);
01367 }
01368 fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
01369 } else {
01370 fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
01371 }
01372 }
01373
01374 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
01375
01376 if (ast_strlen_zero(vmu->email)) {
01377 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
01378 } else {
01379 ast_copy_string(email, vmu->email, sizeof(email));
01380 }
01381
01382 if (check_mime(vmu->fullname)) {
01383 int first_line = 1;
01384 char *ptr;
01385 ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
01386 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01387 *ptr = '\0';
01388 fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
01389 first_line = 0;
01390
01391 ast_str_set(&str2, 0, "%s", ptr + 1);
01392 }
01393 fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
01394 } else {
01395 fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
01396 }
01397
01398 if (!ast_strlen_zero(template->subject)) {
01399 ast_str_substitute_variables(&str1, 0, ast, template->subject);
01400 if (check_mime(ast_str_buffer(str1))) {
01401 int first_line = 1;
01402 char *ptr;
01403 ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
01404 while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
01405 *ptr = '\0';
01406 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01407 first_line = 0;
01408
01409 ast_str_set(&str2, 0, "%s", ptr + 1);
01410 }
01411 fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
01412 } else {
01413 fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
01414 }
01415 } else {
01416 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
01417 ast_debug(1, "Using default subject for this email \n");
01418 }
01419
01420 if (option_debug > 2)
01421 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
01422 fprintf(p, "MIME-Version: 1.0\n");
01423
01424
01425 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)ast_random());
01426
01427 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
01428
01429 fprintf(p, "--%s\n", bound);
01430 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
01431 if (!ast_strlen_zero(template->body)) {
01432 ast_str_substitute_variables(&str1, 0, ast, template->body);
01433 ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
01434 fprintf(p, "%s\n", ast_str_buffer(str1));
01435 } else {
01436 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
01437 "in mailbox %s from %s, on %s so you might\n"
01438 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
01439 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
01440 ast_debug(3, "Using default message body (no template)\n-----\n");
01441 }
01442
01443 if (template->attachment) {
01444 char *ctype = "audio/x-";
01445 ast_debug(3, "Attaching file to message: %s\n", fname);
01446 if (!strcasecmp(format, "ogg"))
01447 ctype = "application/";
01448
01449 fprintf(p, "--%s\n", bound);
01450 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
01451 fprintf(p, "Content-Transfer-Encoding: base64\n");
01452 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
01453 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
01454
01455 base_encode(fname, p);
01456 fprintf(p, "\n\n--%s--\n.\n", bound);
01457 }
01458 fclose(p);
01459 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
01460 ast_safe_system(tmp2);
01461 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
01462 ast_debug(3, "Actual command used: %s\n", tmp2);
01463 if (ast)
01464 ast = ast_channel_release(ast);
01465 ast_free(str1);
01466 ast_free(str2);
01467 return 0;
01468 }
01469
01470
01471
01472 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
01473 {
01474 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
01475 }
01476
01477
01478
01479
01480
01481
01482
01483
01484
01485
01486 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01487 {
01488 struct stat filestat;
01489 make_dir(dest, len, domain, username, folder ? folder : "");
01490 if (stat(dest, &filestat)== -1)
01491 return FALSE;
01492 else
01493 return TRUE;
01494 }
01495
01496
01497
01498
01499
01500
01501
01502
01503
01504
01505 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
01506 {
01507 int res;
01508 make_dir(dest, len, domain, username, folder);
01509 if ((res = ast_mkdir(dest, 0777))) {
01510 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
01511 return -1;
01512 }
01513 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
01514 return 0;
01515 }
01516
01517
01518
01519
01520
01521 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
01522 {
01523 int res;
01524 char fn[PATH_MAX];
01525
01526 ast_debug(2, "Still preparing to play message ...\n");
01527
01528 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
01529
01530 if (ast_fileexists(fn, NULL, NULL) > 0) {
01531 res = ast_streamfile(chan, fn, chan->language);
01532 if (res)
01533 return -1;
01534 res = ast_waitstream(chan, ecodes);
01535 if (res)
01536 return res;
01537 } else {
01538 int numericusername = 1;
01539 char *i = username;
01540
01541 ast_debug(2, "No personal prompts. Using default prompt set for language\n");
01542
01543 while (*i) {
01544 ast_debug(2, "Numeric? Checking %c\n", *i);
01545 if (!isdigit(*i)) {
01546 numericusername = FALSE;
01547 break;
01548 }
01549 i++;
01550 }
01551
01552 if (numericusername) {
01553 if (ast_streamfile(chan, "vm-theperson", chan->language))
01554 return -1;
01555 if ((res = ast_waitstream(chan, ecodes)))
01556 return res;
01557
01558 res = ast_say_digit_str(chan, username, ecodes, chan->language);
01559 if (res)
01560 return res;
01561 } else {
01562 if (ast_streamfile(chan, "vm-theextensionis", chan->language))
01563 return -1;
01564 if ((res = ast_waitstream(chan, ecodes)))
01565 return res;
01566 }
01567 }
01568
01569 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
01570 if (res)
01571 return -1;
01572 res = ast_waitstream(chan, ecodes);
01573 return res;
01574 }
01575
01576
01577
01578 static int vm_delete(char *file)
01579 {
01580 int res;
01581
01582 ast_debug(1, "Deleting voicemail file %s\n", file);
01583
01584 res = unlink(file);
01585 res |= ast_filedelete(file, NULL);
01586 return res;
01587 }
01588
01589
01590
01591
01592 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
01593 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
01594 signed char record_gain)
01595 {
01596 int cmd = 0;
01597 int max_attempts = 3;
01598 int attempts = 0;
01599 int recorded = 0;
01600 int message_exists = 0;
01601 signed char zero_gain = 0;
01602 char *acceptdtmf = "#";
01603 char *canceldtmf = "";
01604
01605
01606
01607
01608 if (duration == NULL) {
01609 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
01610 return -1;
01611 }
01612
01613 cmd = '3';
01614
01615 while ((cmd >= 0) && (cmd != 't')) {
01616 switch (cmd) {
01617 case '1':
01618 ast_verb(3, "Saving message as is\n");
01619 ast_stream_and_wait(chan, "vm-msgsaved", "");
01620 cmd = 't';
01621 break;
01622 case '2':
01623
01624 ast_verb(3, "Reviewing the message\n");
01625 ast_streamfile(chan, recordfile, chan->language);
01626 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
01627 break;
01628 case '3':
01629 message_exists = 0;
01630
01631 if (recorded == 1)
01632 ast_verb(3, "Re-recording the message\n");
01633 else
01634 ast_verb(3, "Recording the message\n");
01635 if (recorded && outsidecaller)
01636 cmd = ast_play_and_wait(chan, "beep");
01637 recorded = 1;
01638
01639 if (record_gain)
01640 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
01641 if (ast_test_flag(vmu, MVM_OPERATOR))
01642 canceldtmf = "0";
01643 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
01644 if (record_gain)
01645 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
01646 if (cmd == -1)
01647 return cmd;
01648 if (cmd == '0')
01649 break;
01650 else if (cmd == '*')
01651 break;
01652 else {
01653
01654 message_exists = 1;
01655 cmd = 0;
01656 }
01657 break;
01658 case '4':
01659 case '5':
01660 case '6':
01661 case '7':
01662 case '8':
01663 case '9':
01664 case '*':
01665 case '#':
01666 cmd = ast_play_and_wait(chan, "vm-sorry");
01667 break;
01668 case '0':
01669 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
01670 cmd = ast_play_and_wait(chan, "vm-sorry");
01671 break;
01672 }
01673 if (message_exists || recorded) {
01674 cmd = ast_play_and_wait(chan, "vm-saveoper");
01675 if (!cmd)
01676 cmd = ast_waitfordigit(chan, 3000);
01677 if (cmd == '1') {
01678 ast_play_and_wait(chan, "vm-msgsaved");
01679 cmd = '0';
01680 } else {
01681 ast_play_and_wait(chan, "vm-deleted");
01682 vm_delete(recordfile);
01683 cmd = '0';
01684 }
01685 }
01686 return cmd;
01687 default:
01688
01689
01690
01691 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
01692 return cmd;
01693 if (message_exists) {
01694 cmd = ast_play_and_wait(chan, "vm-review");
01695 } else {
01696 cmd = ast_play_and_wait(chan, "vm-torerecord");
01697 if (!cmd)
01698 cmd = ast_waitfordigit(chan, 600);
01699 }
01700
01701 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
01702 cmd = ast_play_and_wait(chan, "vm-reachoper");
01703 if (!cmd)
01704 cmd = ast_waitfordigit(chan, 600);
01705 }
01706 if (!cmd)
01707 cmd = ast_waitfordigit(chan, 6000);
01708 if (!cmd) {
01709 attempts++;
01710 }
01711 if (attempts > max_attempts) {
01712 cmd = 't';
01713 }
01714 }
01715 }
01716 if (outsidecaller)
01717 ast_play_and_wait(chan, "vm-goodbye");
01718 if (cmd == 't')
01719 cmd = 0;
01720 return cmd;
01721 }
01722
01723
01724 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
01725 {
01726 char arguments[BUFSIZ];
01727
01728 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
01729 return;
01730
01731 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
01732 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
01733 vmu->username, vmu->domain,
01734 (chan->caller.id.name.valid && chan->caller.id.name.str)
01735 ? chan->caller.id.name.str : "",
01736 (chan->caller.id.number.valid && chan->caller.id.number.str)
01737 ? chan->caller.id.number.str : "");
01738
01739 ast_debug(1, "Executing: %s\n", arguments);
01740 ast_safe_system(arguments);
01741 }
01742
01743
01744
01745 static int notify_new_message(struct ast_channel *chan, const char *templatename, struct minivm_account *vmu, const char *filename, long duration, const char *format, char *cidnum, char *cidname)
01746 {
01747 char *stringp;
01748 struct minivm_template *etemplate;
01749 char *messageformat;
01750 int res = 0;
01751 char oldlocale[100];
01752 const char *counter;
01753
01754 if (!ast_strlen_zero(vmu->attachfmt)) {
01755 if (strstr(format, vmu->attachfmt)) {
01756 format = vmu->attachfmt;
01757 } else {
01758 ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'. Falling back to default format for '%s@%s'.\n", vmu->attachfmt, format, vmu->username, vmu->domain);
01759 }
01760 }
01761
01762 etemplate = message_template_find(vmu->etemplate);
01763 if (!etemplate)
01764 etemplate = message_template_find(templatename);
01765 if (!etemplate)
01766 etemplate = message_template_find("email-default");
01767
01768
01769 stringp = messageformat = ast_strdupa(format);
01770 strsep(&stringp, "|");
01771
01772 if (!ast_strlen_zero(etemplate->locale)) {
01773 char *new_locale;
01774 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
01775 ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
01776 new_locale = setlocale(LC_TIME, etemplate->locale);
01777 if (new_locale == NULL) {
01778 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
01779 }
01780 }
01781
01782
01783
01784
01785 ast_channel_lock(chan);
01786 if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
01787 counter = ast_strdupa(counter);
01788 }
01789 ast_channel_unlock(chan);
01790
01791 if (ast_strlen_zero(counter)) {
01792 ast_debug(2, "MVM_COUNTER not found\n");
01793 } else {
01794 ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
01795 }
01796
01797 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
01798
01799 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
01800
01801 etemplate = message_template_find(vmu->ptemplate);
01802 if (!etemplate)
01803 etemplate = message_template_find("pager-default");
01804 if (etemplate->locale) {
01805 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
01806 setlocale(LC_TIME, etemplate->locale);
01807 }
01808
01809 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
01810 }
01811
01812 ast_manager_event(chan, EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
01813
01814 run_externnotify(chan, vmu);
01815
01816 if (etemplate->locale) {
01817 setlocale(LC_TIME, oldlocale);
01818 }
01819 return res;
01820 }
01821
01822
01823
01824
01825 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
01826 {
01827 char tmptxtfile[PATH_MAX];
01828 char callerid[256];
01829 FILE *txt;
01830 int res = 0, txtdes;
01831 int msgnum;
01832 int duration = 0;
01833 char date[256];
01834 char tmpdir[PATH_MAX];
01835 char ext_context[256] = "";
01836 char fmt[80];
01837 char *domain;
01838 char tmp[256] = "";
01839 struct minivm_account *vmu;
01840 int userdir;
01841
01842 ast_copy_string(tmp, username, sizeof(tmp));
01843 username = tmp;
01844 domain = strchr(tmp, '@');
01845 if (domain) {
01846 *domain = '\0';
01847 domain++;
01848 }
01849
01850 if (!(vmu = find_account(domain, username, TRUE))) {
01851
01852 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
01853 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01854 return 0;
01855 }
01856
01857
01858 if (strcmp(vmu->domain, "localhost"))
01859 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
01860 else
01861 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
01862
01863
01864 if (ast_strlen_zero(vmu->attachfmt))
01865 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
01866 else
01867 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
01868
01869 if (ast_strlen_zero(fmt)) {
01870 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
01871 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01872 return res;
01873 }
01874 msgnum = 0;
01875
01876 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
01877
01878
01879 if (!userdir) {
01880 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
01881 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
01882 }
01883
01884
01885 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
01886
01887
01888 txtdes = mkstemp(tmptxtfile);
01889 if (txtdes < 0) {
01890 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
01891 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
01892 if (!res)
01893 res = ast_waitstream(chan, "");
01894 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01895 return res;
01896 }
01897
01898 if (res >= 0) {
01899
01900 res = ast_streamfile(chan, "beep", chan->language);
01901 if (!res)
01902 res = ast_waitstream(chan, "");
01903 }
01904
01905
01906
01907 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
01908
01909 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
01910
01911 txt = fdopen(txtdes, "w+");
01912 if (!txt) {
01913 ast_log(LOG_WARNING, "Error opening text file for output\n");
01914 } else {
01915 struct ast_tm tm;
01916 struct timeval now = ast_tvnow();
01917 char timebuf[30];
01918 char logbuf[BUFSIZ];
01919 get_date(date, sizeof(date));
01920 ast_localtime(&now, &tm, NULL);
01921 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
01922
01923 ast_callerid_merge(callerid, sizeof(callerid),
01924 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
01925 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
01926 "Unknown");
01927 snprintf(logbuf, sizeof(logbuf),
01928
01929 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
01930 username,
01931 chan->context,
01932 chan->macrocontext,
01933 chan->exten,
01934 chan->priority,
01935 chan->name,
01936 callerid,
01937 date,
01938 timebuf,
01939 duration,
01940 duration < global_vmminmessage ? "IGNORED" : "OK",
01941 vmu->accountcode
01942 );
01943 fprintf(txt, "%s", logbuf);
01944 if (minivmlogfile) {
01945 ast_mutex_lock(&minivmloglock);
01946 fprintf(minivmlogfile, "%s", logbuf);
01947 ast_mutex_unlock(&minivmloglock);
01948 }
01949
01950 if (duration < global_vmminmessage) {
01951 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
01952 fclose(txt);
01953 ast_filedelete(tmptxtfile, NULL);
01954 unlink(tmptxtfile);
01955 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01956 return 0;
01957 }
01958 fclose(txt);
01959 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
01960 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
01961 unlink(tmptxtfile);
01962 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
01963 if(ast_test_flag(vmu, MVM_ALLOCED))
01964 free_user(vmu);
01965 return 0;
01966 }
01967
01968
01969 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
01970 snprintf(timebuf, sizeof(timebuf), "%d", duration);
01971 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
01972 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
01973
01974 }
01975 global_stats.lastreceived = ast_tvnow();
01976 global_stats.receivedmessages++;
01977 #if 0
01978
01979 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
01980 ast_filedelete(tmptxtfile, NULL);
01981
01982 ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
01983 }
01984 #endif
01985
01986 if (res > 0)
01987 res = 0;
01988
01989 if(ast_test_flag(vmu, MVM_ALLOCED))
01990 free_user(vmu);
01991
01992 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
01993 return res;
01994 }
01995
01996
01997
01998 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
01999 {
02000 struct ast_event *event;
02001 char *mailbox, *context;
02002
02003 mailbox = ast_strdupa(mbx);
02004 context = ast_strdupa(ctx);
02005 if (ast_strlen_zero(context)) {
02006 context = "default";
02007 }
02008
02009 if (!(event = ast_event_new(AST_EVENT_MWI,
02010 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
02011 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
02012 AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
02013 AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
02014 AST_EVENT_IE_END))) {
02015 return;
02016 }
02017
02018 ast_event_queue_and_cache(event);
02019 }
02020
02021
02022
02023 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
02024 {
02025 int argc;
02026 char *argv[4];
02027 int res = 0;
02028 char *tmpptr;
02029 char tmp[PATH_MAX];
02030 char *mailbox;
02031 char *domain;
02032 if (ast_strlen_zero(data)) {
02033 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02034 return -1;
02035 }
02036 tmpptr = ast_strdupa((char *)data);
02037 if (!tmpptr) {
02038 ast_log(LOG_ERROR, "Out of memory\n");
02039 return -1;
02040 }
02041 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02042 if (argc < 4) {
02043 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
02044 return -1;
02045 }
02046 ast_copy_string(tmp, argv[0], sizeof(tmp));
02047 mailbox = tmp;
02048 domain = strchr(tmp, '@');
02049 if (domain) {
02050 *domain = '\0';
02051 domain++;
02052 }
02053 if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
02054 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
02055 return -1;
02056 }
02057 queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
02058
02059 return res;
02060 }
02061
02062
02063
02064
02065 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
02066 {
02067 int argc;
02068 char *argv[2];
02069 int res = 0;
02070 char tmp[PATH_MAX];
02071 char *domain;
02072 char *tmpptr;
02073 struct minivm_account *vmu;
02074 char *username = argv[0];
02075 const char *template = "";
02076 const char *filename;
02077 const char *format;
02078 const char *duration_string;
02079
02080 if (ast_strlen_zero(data)) {
02081 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02082 return -1;
02083 }
02084 tmpptr = ast_strdupa((char *)data);
02085 if (!tmpptr) {
02086 ast_log(LOG_ERROR, "Out of memory\n");
02087 return -1;
02088 }
02089 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02090
02091 if (argc == 2 && !ast_strlen_zero(argv[1]))
02092 template = argv[1];
02093
02094 ast_copy_string(tmp, argv[0], sizeof(tmp));
02095 username = tmp;
02096 domain = strchr(tmp, '@');
02097 if (domain) {
02098 *domain = '\0';
02099 domain++;
02100 }
02101 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02102 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02103 return -1;
02104 }
02105
02106 if(!(vmu = find_account(domain, username, TRUE))) {
02107
02108 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02109 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
02110 return -1;
02111 }
02112
02113 ast_channel_lock(chan);
02114 if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
02115 filename = ast_strdupa(filename);
02116 }
02117 ast_channel_unlock(chan);
02118
02119 if (!ast_strlen_zero(filename)) {
02120 ast_channel_lock(chan);
02121 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
02122 format = ast_strdupa(format);
02123 }
02124 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
02125 duration_string = ast_strdupa(duration_string);
02126 }
02127 ast_channel_unlock(chan);
02128 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
02129 format,
02130 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
02131 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL));
02132 }
02133
02134 pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
02135
02136
02137 if(ast_test_flag(vmu, MVM_ALLOCED))
02138 free_user(vmu);
02139
02140
02141
02142 return res;
02143
02144 }
02145
02146
02147
02148 static int minivm_record_exec(struct ast_channel *chan, const char *data)
02149 {
02150 int res = 0;
02151 char *tmp;
02152 struct leave_vm_options leave_options;
02153 int argc;
02154 char *argv[2];
02155 struct ast_flags flags = { 0 };
02156 char *opts[OPT_ARG_ARRAY_SIZE];
02157
02158 memset(&leave_options, 0, sizeof(leave_options));
02159
02160
02161 if (chan->_state != AST_STATE_UP)
02162 ast_answer(chan);
02163
02164 if (ast_strlen_zero(data)) {
02165 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02166 return -1;
02167 }
02168 tmp = ast_strdupa((char *)data);
02169 if (!tmp) {
02170 ast_log(LOG_ERROR, "Out of memory\n");
02171 return -1;
02172 }
02173 argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
02174 if (argc == 2) {
02175 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
02176 return -1;
02177 }
02178 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02179 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
02180 int gain;
02181
02182 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
02183 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
02184 return -1;
02185 } else
02186 leave_options.record_gain = (signed char) gain;
02187 }
02188 }
02189
02190
02191 res = leave_voicemail(chan, argv[0], &leave_options);
02192
02193 if (res == ERROR_LOCK_PATH) {
02194 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
02195 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
02196 res = 0;
02197 }
02198 pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
02199
02200 return res;
02201 }
02202
02203
02204
02205 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
02206 {
02207 struct leave_vm_options leave_options = { 0, '\0'};
02208 int argc;
02209 char *argv[2];
02210 struct ast_flags flags = { 0 };
02211 char *opts[OPT_ARG_ARRAY_SIZE];
02212 int res = 0;
02213 int ausemacro = 0;
02214 int ousemacro = 0;
02215 int ouseexten = 0;
02216 char tmp[PATH_MAX];
02217 char dest[PATH_MAX];
02218 char prefile[PATH_MAX] = "";
02219 char tempfile[PATH_MAX] = "";
02220 char ext_context[256] = "";
02221 char *domain;
02222 char ecodes[16] = "#";
02223 char *tmpptr;
02224 struct minivm_account *vmu;
02225 char *username = argv[0];
02226
02227 if (ast_strlen_zero(data)) {
02228 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
02229 return -1;
02230 }
02231 tmpptr = ast_strdupa((char *)data);
02232 if (!tmpptr) {
02233 ast_log(LOG_ERROR, "Out of memory\n");
02234 return -1;
02235 }
02236 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02237
02238 if (argc == 2) {
02239 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
02240 return -1;
02241 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
02242 }
02243
02244 ast_copy_string(tmp, argv[0], sizeof(tmp));
02245 username = tmp;
02246 domain = strchr(tmp, '@');
02247 if (domain) {
02248 *domain = '\0';
02249 domain++;
02250 }
02251 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02252 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
02253 return -1;
02254 }
02255 ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
02256
02257 if (!(vmu = find_account(domain, username, TRUE))) {
02258 ast_log(LOG_ERROR, "Could not allocate memory. \n");
02259 return -1;
02260 }
02261
02262
02263 if (chan->_state != AST_STATE_UP)
02264 ast_answer(chan);
02265
02266
02267 if (strcmp(vmu->domain, "localhost"))
02268 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
02269 else
02270 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
02271
02272 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
02273 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
02274 if (res)
02275 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
02276 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
02277 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
02278 if (res)
02279 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
02280 }
02281
02282 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
02283 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
02284 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
02285 ast_copy_string(prefile, tempfile, sizeof(prefile));
02286 }
02287 ast_debug(2, "Preparing to play message ...\n");
02288
02289
02290 if (ast_test_flag(vmu, MVM_OPERATOR)) {
02291 if (!ast_strlen_zero(vmu->exit)) {
02292 if (ast_exists_extension(chan, vmu->exit, "o", 1,
02293 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02294 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02295 ouseexten = 1;
02296 }
02297 } else if (ast_exists_extension(chan, chan->context, "o", 1,
02298 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02299 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02300 ouseexten = 1;
02301 }
02302 else if (!ast_strlen_zero(chan->macrocontext)
02303 && ast_exists_extension(chan, chan->macrocontext, "o", 1,
02304 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02305 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
02306 ousemacro = 1;
02307 }
02308 }
02309
02310 if (!ast_strlen_zero(vmu->exit)) {
02311 if (ast_exists_extension(chan, vmu->exit, "a", 1,
02312 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02313 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
02314 }
02315 } else if (ast_exists_extension(chan, chan->context, "a", 1,
02316 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02317 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
02318 } else if (!ast_strlen_zero(chan->macrocontext)
02319 && ast_exists_extension(chan, chan->macrocontext, "a", 1,
02320 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
02321 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
02322 ausemacro = 1;
02323 }
02324
02325 res = 0;
02326
02327 if (!ast_strlen_zero(prefile)) {
02328 if (ast_streamfile(chan, prefile, chan->language) > -1)
02329 res = ast_waitstream(chan, ecodes);
02330 } else {
02331 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
02332 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
02333 }
02334 if (res < 0) {
02335 ast_debug(2, "Hang up during prefile playback\n");
02336 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02337 if(ast_test_flag(vmu, MVM_ALLOCED))
02338 free_user(vmu);
02339 return -1;
02340 }
02341 if (res == '#') {
02342
02343 ast_set_flag(&leave_options, OPT_SILENT);
02344 res = 0;
02345 }
02346 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
02347 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
02348 if (!res)
02349 res = ast_waitstream(chan, ecodes);
02350 if (res == '#') {
02351 ast_set_flag(&leave_options, OPT_SILENT);
02352 res = 0;
02353 }
02354 }
02355 if (res > 0)
02356 ast_stopstream(chan);
02357
02358
02359 if (res == '*') {
02360 chan->exten[0] = 'a';
02361 chan->exten[1] = '\0';
02362 if (!ast_strlen_zero(vmu->exit)) {
02363 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
02364 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
02365 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
02366 }
02367 chan->priority = 0;
02368 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02369 res = 0;
02370 } else if (res == '0') {
02371 if(ouseexten || ousemacro) {
02372 chan->exten[0] = 'o';
02373 chan->exten[1] = '\0';
02374 if (!ast_strlen_zero(vmu->exit)) {
02375 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
02376 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
02377 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
02378 }
02379 ast_play_and_wait(chan, "transfer");
02380 chan->priority = 0;
02381 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
02382 }
02383 res = 0;
02384 } else if (res < 0) {
02385 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
02386 res = -1;
02387 } else
02388 pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
02389
02390 if(ast_test_flag(vmu, MVM_ALLOCED))
02391 free_user(vmu);
02392
02393
02394
02395 return res;
02396
02397 }
02398
02399
02400
02401 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
02402 {
02403 int res = 0;
02404 char filename[BUFSIZ];
02405
02406 if (!ast_strlen_zero(data)) {
02407 ast_copy_string(filename, (char *) data, sizeof(filename));
02408 } else {
02409 ast_channel_lock(chan);
02410 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
02411 ast_channel_unlock(chan);
02412 }
02413
02414 if (ast_strlen_zero(filename)) {
02415 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
02416 return res;
02417 }
02418
02419
02420
02421 if (ast_fileexists(filename, NULL, NULL) > 0) {
02422 res = vm_delete(filename);
02423 if (res) {
02424 ast_debug(2, "Can't delete file: %s\n", filename);
02425 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02426 } else {
02427 ast_debug(2, "Deleted voicemail file :: %s \n", filename);
02428 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
02429 }
02430 } else {
02431 ast_debug(2, "Filename does not exist: %s\n", filename);
02432 pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
02433 }
02434
02435 return res;
02436 }
02437
02438
02439 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
02440 {
02441 int argc = 0;
02442 char *argv[2];
02443 char filename[PATH_MAX];
02444 char tmp[PATH_MAX];
02445 char *domain;
02446 char *tmpptr = NULL;
02447 struct minivm_account *vmu;
02448 char *username = argv[0];
02449 struct ast_flags flags = { 0 };
02450 char *opts[OPT_ARG_ARRAY_SIZE];
02451 int error = FALSE;
02452 char *message = NULL;
02453 char *prompt = NULL;
02454 int duration;
02455 int cmd;
02456
02457 if (ast_strlen_zero(data)) {
02458 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02459 error = TRUE;
02460 } else
02461 tmpptr = ast_strdupa((char *)data);
02462 if (!error) {
02463 if (!tmpptr) {
02464 ast_log(LOG_ERROR, "Out of memory\n");
02465 error = TRUE;
02466 } else
02467 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
02468 }
02469
02470 if (argc <=1) {
02471 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
02472 error = TRUE;
02473 }
02474 if (!error && strlen(argv[1]) > 1) {
02475 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
02476 error = TRUE;
02477 }
02478
02479 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
02480 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
02481 error = TRUE;
02482 }
02483
02484 if (error) {
02485 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02486 return -1;
02487 }
02488
02489 ast_copy_string(tmp, argv[0], sizeof(tmp));
02490 username = tmp;
02491 domain = strchr(tmp, '@');
02492 if (domain) {
02493 *domain = '\0';
02494 domain++;
02495 }
02496 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
02497 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
02498 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02499 return -1;
02500 }
02501
02502 if(!(vmu = find_account(domain, username, TRUE))) {
02503
02504 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
02505 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
02506 return -1;
02507 }
02508
02509
02510 if (chan->_state != AST_STATE_UP)
02511 ast_answer(chan);
02512
02513
02514 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
02515 message = "busy";
02516 prompt = "vm-rec-busy";
02517 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
02518 message = "unavailable";
02519 prompt = "vm-rec-unv";
02520 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
02521 message = "temp";
02522 prompt = "vm-rec-temp";
02523 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
02524 message = "greet";
02525 prompt = "vm-rec-name";
02526 }
02527 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
02528
02529 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
02530
02531 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
02532
02533 if(ast_test_flag(vmu, MVM_ALLOCED))
02534 free_user(vmu);
02535
02536 pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
02537
02538
02539 return 0;
02540 }
02541
02542
02543 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
02544 {
02545 struct minivm_account *vmu;
02546 char *domain;
02547 char *username;
02548 char accbuf[BUFSIZ];
02549
02550 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
02551
02552 ast_copy_string(accbuf, name, sizeof(accbuf));
02553 username = accbuf;
02554 domain = strchr(accbuf, '@');
02555 if (domain) {
02556 *domain = '\0';
02557 domain++;
02558 }
02559 if (ast_strlen_zero(domain)) {
02560 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
02561 return 0;
02562 }
02563
02564 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
02565
02566
02567 vmu = ast_calloc(1, sizeof(*vmu));
02568 if (!vmu)
02569 return 0;
02570
02571 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
02572 ast_copy_string(vmu->username, username, sizeof(vmu->username));
02573
02574 populate_defaults(vmu);
02575
02576 ast_debug(3, "...Configuring account %s\n", name);
02577
02578 while (var) {
02579 ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
02580 if (!strcasecmp(var->name, "serveremail")) {
02581 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
02582 } else if (!strcasecmp(var->name, "email")) {
02583 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
02584 } else if (!strcasecmp(var->name, "accountcode")) {
02585 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
02586 } else if (!strcasecmp(var->name, "pincode")) {
02587 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
02588 } else if (!strcasecmp(var->name, "domain")) {
02589 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
02590 } else if (!strcasecmp(var->name, "language")) {
02591 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
02592 } else if (!strcasecmp(var->name, "timezone")) {
02593 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
02594 } else if (!strcasecmp(var->name, "externnotify")) {
02595 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
02596 } else if (!strcasecmp(var->name, "etemplate")) {
02597 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
02598 } else if (!strcasecmp(var->name, "ptemplate")) {
02599 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
02600 } else if (!strcasecmp(var->name, "fullname")) {
02601 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
02602 } else if (!strcasecmp(var->name, "setvar")) {
02603 char *varval;
02604 char *varname = ast_strdupa(var->value);
02605 struct ast_variable *tmpvar;
02606
02607 if (varname && (varval = strchr(varname, '='))) {
02608 *varval = '\0';
02609 varval++;
02610 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
02611 tmpvar->next = vmu->chanvars;
02612 vmu->chanvars = tmpvar;
02613 }
02614 }
02615 } else if (!strcasecmp(var->name, "pager")) {
02616 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
02617 } else if (!strcasecmp(var->name, "volgain")) {
02618 sscanf(var->value, "%30lf", &vmu->volgain);
02619 } else {
02620 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
02621 }
02622 var = var->next;
02623 }
02624 ast_debug(3, "...Linking account %s\n", name);
02625
02626 AST_LIST_LOCK(&minivm_accounts);
02627 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
02628 AST_LIST_UNLOCK(&minivm_accounts);
02629
02630 global_stats.voicemailaccounts++;
02631
02632 ast_debug(2, "MVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : "");
02633 return 0;
02634 }
02635
02636
02637 static void free_zone(struct minivm_zone *z)
02638 {
02639 ast_free(z);
02640 }
02641
02642
02643 static void timezone_destroy_list(void)
02644 {
02645 struct minivm_zone *this;
02646
02647 AST_LIST_LOCK(&minivm_zones);
02648 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
02649 free_zone(this);
02650
02651 AST_LIST_UNLOCK(&minivm_zones);
02652 }
02653
02654
02655 static int timezone_add(const char *zonename, const char *config)
02656 {
02657 struct minivm_zone *newzone;
02658 char *msg_format, *timezone_str;
02659
02660 newzone = ast_calloc(1, sizeof(*newzone));
02661 if (newzone == NULL)
02662 return 0;
02663
02664 msg_format = ast_strdupa(config);
02665 if (msg_format == NULL) {
02666 ast_log(LOG_WARNING, "Out of memory.\n");
02667 ast_free(newzone);
02668 return 0;
02669 }
02670
02671 timezone_str = strsep(&msg_format, "|");
02672 if (!msg_format) {
02673 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
02674 ast_free(newzone);
02675 return 0;
02676 }
02677
02678 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
02679 ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
02680 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
02681
02682 AST_LIST_LOCK(&minivm_zones);
02683 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
02684 AST_LIST_UNLOCK(&minivm_zones);
02685
02686 global_stats.timezones++;
02687
02688 return 0;
02689 }
02690
02691
02692 static char *message_template_parse_filebody(const char *filename) {
02693 char buf[BUFSIZ * 6];
02694 char readbuf[BUFSIZ];
02695 char filenamebuf[BUFSIZ];
02696 char *writepos;
02697 char *messagebody;
02698 FILE *fi;
02699 int lines = 0;
02700
02701 if (ast_strlen_zero(filename))
02702 return NULL;
02703 if (*filename == '/')
02704 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
02705 else
02706 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
02707
02708 if (!(fi = fopen(filenamebuf, "r"))) {
02709 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
02710 return NULL;
02711 }
02712 writepos = buf;
02713 while (fgets(readbuf, sizeof(readbuf), fi)) {
02714 lines ++;
02715 if (writepos != buf) {
02716 *writepos = '\n';
02717 writepos++;
02718 }
02719 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
02720 writepos += strlen(readbuf) - 1;
02721 }
02722 fclose(fi);
02723 messagebody = ast_calloc(1, strlen(buf + 1));
02724 ast_copy_string(messagebody, buf, strlen(buf) + 1);
02725 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
02726 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
02727
02728 return messagebody;
02729 }
02730
02731
02732 static char *message_template_parse_emailbody(const char *configuration)
02733 {
02734 char *tmpread, *tmpwrite;
02735 char *emailbody = ast_strdup(configuration);
02736
02737
02738 tmpread = tmpwrite = emailbody;
02739 while ((tmpwrite = strchr(tmpread,'\\'))) {
02740 int len = strlen("\n");
02741 switch (tmpwrite[1]) {
02742 case 'n':
02743 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02744 strncpy(tmpwrite, "\n", len);
02745 break;
02746 case 't':
02747 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
02748 strncpy(tmpwrite, "\t", len);
02749 break;
02750 default:
02751 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
02752 }
02753 tmpread = tmpwrite + len;
02754 }
02755 return emailbody;
02756 }
02757
02758
02759 static int apply_general_options(struct ast_variable *var)
02760 {
02761 int error = 0;
02762
02763 while (var) {
02764
02765 if (!strcmp(var->name, "mailcmd")) {
02766 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd));
02767 } else if (!strcmp(var->name, "maxgreet")) {
02768 global_maxgreet = atoi(var->value);
02769 } else if (!strcmp(var->name, "maxsilence")) {
02770 global_maxsilence = atoi(var->value);
02771 if (global_maxsilence > 0)
02772 global_maxsilence *= 1000;
02773 } else if (!strcmp(var->name, "logfile")) {
02774 if (!ast_strlen_zero(var->value) ) {
02775 if(*(var->value) == '/')
02776 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
02777 else
02778 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
02779 }
02780 } else if (!strcmp(var->name, "externnotify")) {
02781
02782 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
02783 } else if (!strcmp(var->name, "silencetreshold")) {
02784
02785 global_silencethreshold = atoi(var->value);
02786 } else if (!strcmp(var->name, "maxmessage")) {
02787 int x;
02788 if (sscanf(var->value, "%30d", &x) == 1) {
02789 global_vmmaxmessage = x;
02790 } else {
02791 error ++;
02792 ast_log(LOG_WARNING, "Invalid max message time length\n");
02793 }
02794 } else if (!strcmp(var->name, "minmessage")) {
02795 int x;
02796 if (sscanf(var->value, "%30d", &x) == 1) {
02797 global_vmminmessage = x;
02798 if (global_maxsilence <= global_vmminmessage)
02799 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
02800 } else {
02801 error ++;
02802 ast_log(LOG_WARNING, "Invalid min message time length\n");
02803 }
02804 } else if (!strcmp(var->name, "format")) {
02805 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
02806 } else if (!strcmp(var->name, "review")) {
02807 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
02808 } else if (!strcmp(var->name, "operator")) {
02809 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
02810 }
02811 var = var->next;
02812 }
02813 return error;
02814 }
02815
02816
02817 static int load_config(int reload)
02818 {
02819 struct ast_config *cfg;
02820 struct ast_variable *var;
02821 char *cat;
02822 const char *chanvar;
02823 int error = 0;
02824 struct minivm_template *template;
02825 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
02826
02827 cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
02828 if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
02829 return 0;
02830 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
02831 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
02832 return 0;
02833 }
02834
02835 ast_mutex_lock(&minivmlock);
02836
02837
02838 message_destroy_list();
02839 timezone_destroy_list();
02840 vmaccounts_destroy_list();
02841 ast_debug(2, "Destroyed memory objects...\n");
02842
02843
02844 global_externnotify[0] = '\0';
02845 global_logfile[0] = '\0';
02846 global_vmmaxmessage = 2000;
02847 global_maxgreet = 2000;
02848 global_vmminmessage = 0;
02849 strcpy(global_mailcmd, SENDMAIL);
02850 global_maxsilence = 0;
02851 global_saydurationminfo = 2;
02852 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
02853 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
02854 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
02855
02856 memset(&global_stats, 0, sizeof(global_stats));
02857 global_stats.reset = ast_tvnow();
02858
02859 global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
02860
02861
02862 if (!cfg) {
02863 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
02864 ast_mutex_unlock(&minivmlock);
02865 return 0;
02866 }
02867
02868 ast_debug(2, "Loaded configuration file, now parsing\n");
02869
02870
02871
02872 cat = ast_category_browse(cfg, NULL);
02873 while (cat) {
02874 ast_debug(3, "Found configuration section [%s]\n", cat);
02875 if (!strcasecmp(cat, "general")) {
02876
02877 error += apply_general_options(ast_variable_browse(cfg, cat));
02878 } else if (!strncasecmp(cat, "template-", 9)) {
02879
02880 char *name = cat + 9;
02881
02882
02883 error += message_template_build(name, ast_variable_browse(cfg, cat));
02884 } else {
02885 var = ast_variable_browse(cfg, cat);
02886 if (!strcasecmp(cat, "zonemessages")) {
02887
02888 while (var) {
02889 timezone_add(var->name, var->value);
02890 var = var->next;
02891 }
02892 } else {
02893
02894 error += create_vmaccount(cat, var, FALSE);
02895 }
02896 }
02897
02898 cat = ast_category_browse(cfg, cat);
02899 }
02900
02901
02902 message_template_build("email-default", NULL);
02903 template = message_template_find("email-default");
02904
02905
02906 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
02907 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
02908 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
02909 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02910 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
02911 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02912 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
02913 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02914 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
02915 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
02916 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
02917 template->body = message_template_parse_emailbody(chanvar);
02918 template->attachment = TRUE;
02919
02920 message_template_build("pager-default", NULL);
02921 template = message_template_find("pager-default");
02922 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
02923 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
02924 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
02925 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
02926 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
02927 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
02928 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
02929 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
02930 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
02931 template->body = message_template_parse_emailbody(chanvar);
02932 template->attachment = FALSE;
02933
02934 if (error)
02935 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
02936
02937 ast_mutex_unlock(&minivmlock);
02938 ast_config_destroy(cfg);
02939
02940
02941 if(minivmlogfile)
02942 fclose(minivmlogfile);
02943
02944
02945 if(!ast_strlen_zero(global_logfile)) {
02946 minivmlogfile = fopen(global_logfile, "a");
02947 if(!minivmlogfile)
02948 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
02949 if (minivmlogfile)
02950 ast_debug(3, "Opened log file %s \n", global_logfile);
02951 }
02952
02953 return 0;
02954 }
02955
02956
02957 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
02958 {
02959 struct minivm_template *this;
02960 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
02961 int count = 0;
02962
02963 switch (cmd) {
02964 case CLI_INIT:
02965 e->command = "minivm list templates";
02966 e->usage =
02967 "Usage: minivm list templates\n"
02968 " Lists message templates for e-mail, paging and IM\n";
02969 return NULL;
02970 case CLI_GENERATE:
02971 return NULL;
02972 }
02973
02974 if (a->argc > 3)
02975 return CLI_SHOWUSAGE;
02976
02977 AST_LIST_LOCK(&message_templates);
02978 if (AST_LIST_EMPTY(&message_templates)) {
02979 ast_cli(a->fd, "There are no message templates defined\n");
02980 AST_LIST_UNLOCK(&message_templates);
02981 return CLI_FAILURE;
02982 }
02983 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
02984 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
02985 AST_LIST_TRAVERSE(&message_templates, this, list) {
02986 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
02987 this->charset ? this->charset : "-",
02988 this->locale ? this->locale : "-",
02989 this->attachment ? "Yes" : "No",
02990 this->subject ? this->subject : "-");
02991 count++;
02992 }
02993 AST_LIST_UNLOCK(&message_templates);
02994 ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
02995 return CLI_SUCCESS;
02996 }
02997
02998 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
02999 {
03000 int which = 0;
03001 int wordlen;
03002 struct minivm_account *vmu;
03003 const char *domain = "";
03004
03005
03006 if (pos > 4)
03007 return NULL;
03008 if (pos == 3)
03009 return (state == 0) ? ast_strdup("for") : NULL;
03010 wordlen = strlen(word);
03011 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03012 if (!strncasecmp(word, vmu->domain, wordlen)) {
03013 if (domain && strcmp(domain, vmu->domain) && ++which > state)
03014 return ast_strdup(vmu->domain);
03015
03016 domain = vmu->domain;
03017 }
03018 }
03019 return NULL;
03020 }
03021
03022
03023 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03024 {
03025 struct minivm_account *vmu;
03026 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
03027 int count = 0;
03028
03029 switch (cmd) {
03030 case CLI_INIT:
03031 e->command = "minivm list accounts";
03032 e->usage =
03033 "Usage: minivm list accounts\n"
03034 " Lists all mailboxes currently set up\n";
03035 return NULL;
03036 case CLI_GENERATE:
03037 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
03038 }
03039
03040 if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
03041 return CLI_SHOWUSAGE;
03042 if ((a->argc == 5) && strcmp(a->argv[3],"for"))
03043 return CLI_SHOWUSAGE;
03044
03045 AST_LIST_LOCK(&minivm_accounts);
03046 if (AST_LIST_EMPTY(&minivm_accounts)) {
03047 ast_cli(a->fd, "There are no voicemail users currently defined\n");
03048 AST_LIST_UNLOCK(&minivm_accounts);
03049 return CLI_FAILURE;
03050 }
03051 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
03052 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
03053 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
03054 char tmp[256] = "";
03055 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
03056 count++;
03057 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
03058 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
03059 vmu->ptemplate ? vmu->ptemplate : "-",
03060 vmu->zonetag ? vmu->zonetag : "-",
03061 vmu->attachfmt ? vmu->attachfmt : "-",
03062 vmu->fullname);
03063 }
03064 }
03065 AST_LIST_UNLOCK(&minivm_accounts);
03066 ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
03067 return CLI_SUCCESS;
03068 }
03069
03070
03071 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03072 {
03073 struct minivm_zone *zone;
03074 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
03075 char *res = CLI_SUCCESS;
03076
03077 switch (cmd) {
03078 case CLI_INIT:
03079 e->command = "minivm list zones";
03080 e->usage =
03081 "Usage: minivm list zones\n"
03082 " Lists zone message formats\n";
03083 return NULL;
03084 case CLI_GENERATE:
03085 return NULL;
03086 }
03087
03088 if (a->argc != e->args)
03089 return CLI_SHOWUSAGE;
03090
03091 AST_LIST_LOCK(&minivm_zones);
03092 if (!AST_LIST_EMPTY(&minivm_zones)) {
03093 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
03094 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
03095 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
03096 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
03097 }
03098 } else {
03099 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
03100 res = CLI_FAILURE;
03101 }
03102 AST_LIST_UNLOCK(&minivm_zones);
03103
03104 return res;
03105 }
03106
03107
03108 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03109 {
03110 switch (cmd) {
03111 case CLI_INIT:
03112 e->command = "minivm show settings";
03113 e->usage =
03114 "Usage: minivm show settings\n"
03115 " Display Mini-Voicemail general settings\n";
03116 return NULL;
03117 case CLI_GENERATE:
03118 return NULL;
03119 }
03120
03121 ast_cli(a->fd, "* Mini-Voicemail general settings\n");
03122 ast_cli(a->fd, " -------------------------------\n");
03123 ast_cli(a->fd, "\n");
03124 ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
03125 ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
03126 ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
03127 ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
03128 ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
03129 ast_cli(a->fd, " Default format: %s\n", default_vmformat);
03130 ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
03131 ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
03132 ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
03133 ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
03134
03135 ast_cli(a->fd, "\n");
03136 return CLI_SUCCESS;
03137 }
03138
03139
03140 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03141 {
03142 struct ast_tm timebuf;
03143 char buf[BUFSIZ];
03144
03145 switch (cmd) {
03146
03147 case CLI_INIT:
03148 e->command = "minivm show stats";
03149 e->usage =
03150 "Usage: minivm show stats\n"
03151 " Display Mini-Voicemail counters\n";
03152 return NULL;
03153 case CLI_GENERATE:
03154 return NULL;
03155 }
03156
03157 ast_cli(a->fd, "* Mini-Voicemail statistics\n");
03158 ast_cli(a->fd, " -------------------------\n");
03159 ast_cli(a->fd, "\n");
03160 ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
03161 ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
03162 ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
03163 if (global_stats.receivedmessages == 0) {
03164 ast_cli(a->fd, " Received messages since last reset: <none>\n");
03165 } else {
03166 ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
03167 ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
03168 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03169 ast_cli(a->fd, " Last received voicemail: %s\n", buf);
03170 }
03171 ast_localtime(&global_stats.reset, &timebuf, NULL);
03172 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
03173 ast_cli(a->fd, " Last reset: %s\n", buf);
03174
03175 ast_cli(a->fd, "\n");
03176 return CLI_SUCCESS;
03177 }
03178
03179
03180 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03181 {
03182 struct minivm_account *vmu;
03183 char *username, *domain, *colname;
03184
03185 if (!(username = ast_strdupa(data))) {
03186 ast_log(LOG_ERROR, "Memory Error!\n");
03187 return -1;
03188 }
03189
03190 if ((colname = strchr(username, ':'))) {
03191 *colname = '\0';
03192 colname++;
03193 } else {
03194 colname = "path";
03195 }
03196 if ((domain = strchr(username, '@'))) {
03197 *domain = '\0';
03198 domain++;
03199 }
03200 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
03201 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
03202 return 0;
03203 }
03204
03205 if (!(vmu = find_account(domain, username, TRUE)))
03206 return 0;
03207
03208 if (!strcasecmp(colname, "hasaccount")) {
03209 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
03210 } else if (!strcasecmp(colname, "fullname")) {
03211 ast_copy_string(buf, vmu->fullname, len);
03212 } else if (!strcasecmp(colname, "email")) {
03213 if (!ast_strlen_zero(vmu->email))
03214 ast_copy_string(buf, vmu->email, len);
03215 else
03216 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
03217 } else if (!strcasecmp(colname, "pager")) {
03218 ast_copy_string(buf, vmu->pager, len);
03219 } else if (!strcasecmp(colname, "etemplate")) {
03220 if (!ast_strlen_zero(vmu->etemplate))
03221 ast_copy_string(buf, vmu->etemplate, len);
03222 else
03223 ast_copy_string(buf, "email-default", len);
03224 } else if (!strcasecmp(colname, "language")) {
03225 ast_copy_string(buf, vmu->language, len);
03226 } else if (!strcasecmp(colname, "timezone")) {
03227 ast_copy_string(buf, vmu->zonetag, len);
03228 } else if (!strcasecmp(colname, "ptemplate")) {
03229 if (!ast_strlen_zero(vmu->ptemplate))
03230 ast_copy_string(buf, vmu->ptemplate, len);
03231 else
03232 ast_copy_string(buf, "email-default", len);
03233 } else if (!strcasecmp(colname, "accountcode")) {
03234 ast_copy_string(buf, vmu->accountcode, len);
03235 } else if (!strcasecmp(colname, "pincode")) {
03236 ast_copy_string(buf, vmu->pincode, len);
03237 } else if (!strcasecmp(colname, "path")) {
03238 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
03239 } else {
03240 struct ast_variable *var;
03241 int found = 0;
03242
03243 for (var = vmu->chanvars ; var ; var = var->next)
03244 if (!strcmp(var->name, colname)) {
03245 ast_copy_string(buf, var->value, len);
03246 found = 1;
03247 break;
03248 }
03249 }
03250
03251 if(ast_test_flag(vmu, MVM_ALLOCED))
03252 free_user(vmu);
03253
03254 return 0;
03255 }
03256
03257
03258
03259
03260
03261
03262 static int vm_lock_path(const char *path)
03263 {
03264 switch (ast_lock_path(path)) {
03265 case AST_LOCK_TIMEOUT:
03266 return -1;
03267 default:
03268 return 0;
03269 }
03270 }
03271
03272
03273
03274
03275
03276
03277
03278
03279 static int access_counter_file(char *directory, char *countername, int value, int operand)
03280 {
03281 char filename[BUFSIZ];
03282 char readbuf[BUFSIZ];
03283 FILE *counterfile;
03284 int old = 0, counter = 0;
03285
03286
03287 if (vm_lock_path(directory)) {
03288 return -1;
03289 }
03290 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
03291 if (operand != 1) {
03292 counterfile = fopen(filename, "r");
03293 if (counterfile) {
03294 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
03295 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
03296 old = counter = atoi(readbuf);
03297 }
03298 fclose(counterfile);
03299 }
03300 }
03301 switch (operand) {
03302 case 0:
03303 ast_unlock_path(directory);
03304 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
03305 return counter;
03306 break;
03307 case 1:
03308 counter = value;
03309 break;
03310 case 2:
03311 counter += value;
03312 if (counter < 0)
03313 counter = 0;
03314 break;
03315 }
03316
03317
03318 counterfile = fopen(filename, "w");
03319 if (!counterfile) {
03320 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
03321 ast_unlock_path(directory);
03322 return -1;
03323 }
03324 fprintf(counterfile, "%d\n\n", counter);
03325 fclose(counterfile);
03326 ast_unlock_path(directory);
03327 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
03328 return counter;
03329 }
03330
03331
03332 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
03333 {
03334 char *username, *domain, *countername;
03335 struct minivm_account *vmu = NULL;
03336 char userpath[BUFSIZ];
03337 int res;
03338
03339 *buf = '\0';
03340
03341 if (!(username = ast_strdupa(data))) {
03342 ast_log(LOG_WARNING, "Memory error!\n");
03343 return -1;
03344 }
03345 if ((countername = strchr(username, ':'))) {
03346 *countername = '\0';
03347 countername++;
03348 }
03349
03350 if ((domain = strchr(username, '@'))) {
03351 *domain = '\0';
03352 domain++;
03353 }
03354
03355
03356 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03357 ast_log(LOG_ERROR, "No account given\n");
03358 return -1;
03359 }
03360
03361 if (ast_strlen_zero(countername)) {
03362 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
03363 return -1;
03364 }
03365
03366
03367 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03368 domain = username;
03369 username = NULL;
03370 }
03371
03372
03373 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
03374 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03375 return 0;
03376 }
03377
03378 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03379
03380
03381 res = access_counter_file(userpath, countername, 0, 0);
03382 if (res >= 0)
03383 snprintf(buf, len, "%d", res);
03384 return 0;
03385 }
03386
03387
03388 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
03389 {
03390 char *username, *domain, *countername, *operand;
03391 char userpath[BUFSIZ];
03392 struct minivm_account *vmu;
03393 int change = 0;
03394 int operation = 0;
03395
03396 if(!value)
03397 return -1;
03398 change = atoi(value);
03399
03400 if (!(username = ast_strdupa(data))) {
03401 ast_log(LOG_WARNING, "Memory error!\n");
03402 return -1;
03403 }
03404
03405 if ((countername = strchr(username, ':'))) {
03406 *countername = '\0';
03407 countername++;
03408 }
03409 if ((operand = strchr(countername, ':'))) {
03410 *operand = '\0';
03411 operand++;
03412 }
03413
03414 if ((domain = strchr(username, '@'))) {
03415 *domain = '\0';
03416 domain++;
03417 }
03418
03419
03420 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03421 ast_log(LOG_ERROR, "No account given\n");
03422 return -1;
03423 }
03424
03425
03426 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
03427 domain = username;
03428 username = NULL;
03429 }
03430
03431 if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
03432 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
03433 return -1;
03434 }
03435
03436
03437 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
03438 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
03439 return 0;
03440 }
03441
03442 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
03443
03444 if (*operand == 'i')
03445 operation = 2;
03446 else if (*operand == 'd') {
03447 change = change * -1;
03448 operation = 2;
03449 } else if (*operand == 's')
03450 operation = 1;
03451 else {
03452 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
03453 return -1;
03454 }
03455
03456
03457 access_counter_file(userpath, countername, change, operation);
03458 return 0;
03459 }
03460
03461
03462
03463 static struct ast_cli_entry cli_minivm[] = {
03464 AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
03465 AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
03466 AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"),
03467 AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
03468 AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
03469 AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
03470 };
03471
03472 static struct ast_custom_function minivm_counter_function = {
03473 .name = "MINIVMCOUNTER",
03474 .read = minivm_counter_func_read,
03475 .write = minivm_counter_func_write,
03476 };
03477
03478 static struct ast_custom_function minivm_account_function = {
03479 .name = "MINIVMACCOUNT",
03480 .read = minivm_account_func_read,
03481 };
03482
03483
03484 static int load_module(void)
03485 {
03486 int res;
03487
03488 res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
03489 res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
03490 res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
03491 res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
03492 res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
03493 res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
03494
03495 ast_custom_function_register(&minivm_account_function);
03496 ast_custom_function_register(&minivm_counter_function);
03497 if (res)
03498 return(res);
03499
03500 if ((res = load_config(0)))
03501 return(res);
03502
03503 ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03504
03505
03506 snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
03507
03508 return res;
03509 }
03510
03511
03512 static int reload(void)
03513 {
03514 return(load_config(1));
03515 }
03516
03517
03518 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
03519 {
03520
03521 switch (cmd) {
03522 case CLI_INIT:
03523 e->command = "minivm reload";
03524 e->usage =
03525 "Usage: minivm reload\n"
03526 " Reload mini-voicemail configuration and reset statistics\n";
03527 return NULL;
03528 case CLI_GENERATE:
03529 return NULL;
03530 }
03531
03532 reload();
03533 ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
03534 return CLI_SUCCESS;
03535 }
03536
03537
03538 static int unload_module(void)
03539 {
03540 int res;
03541
03542 res = ast_unregister_application(app_minivm_record);
03543 res |= ast_unregister_application(app_minivm_greet);
03544 res |= ast_unregister_application(app_minivm_notify);
03545 res |= ast_unregister_application(app_minivm_delete);
03546 res |= ast_unregister_application(app_minivm_accmess);
03547 res |= ast_unregister_application(app_minivm_mwi);
03548
03549 ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
03550 ast_custom_function_unregister(&minivm_account_function);
03551 ast_custom_function_unregister(&minivm_counter_function);
03552
03553 message_destroy_list();
03554 timezone_destroy_list();
03555 vmaccounts_destroy_list();
03556
03557 return res;
03558 }
03559
03560
03561 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
03562 .load = load_module,
03563 .unload = unload_module,
03564 .reload = reload,
03565 );