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