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 #include "asterisk.h"
00031
00032 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 375299 $")
00033
00034 #ifdef HAVE_SYS_STAT_H
00035 #include <sys/stat.h>
00036 #endif
00037 #include <regex.h>
00038 #include <sys/file.h>
00039 #include <signal.h>
00040 #include <stdlib.h>
00041 #include <sys/types.h>
00042 #include <sys/wait.h>
00043 #ifndef HAVE_CLOSEFROM
00044 #include <dirent.h>
00045 #endif
00046 #ifdef HAVE_CAP
00047 #include <sys/capability.h>
00048 #endif
00049
00050 #include "asterisk/paths.h"
00051 #include "asterisk/channel.h"
00052 #include "asterisk/pbx.h"
00053 #include "asterisk/file.h"
00054 #include "asterisk/app.h"
00055 #include "asterisk/dsp.h"
00056 #include "asterisk/utils.h"
00057 #include "asterisk/lock.h"
00058 #include "asterisk/indications.h"
00059 #include "asterisk/linkedlists.h"
00060 #include "asterisk/threadstorage.h"
00061 #include "asterisk/test.h"
00062
00063 AST_THREADSTORAGE_PUBLIC(ast_str_thread_global_buf);
00064
00065 static pthread_t shaun_of_the_dead_thread = AST_PTHREADT_NULL;
00066
00067 struct zombie {
00068 pid_t pid;
00069 AST_LIST_ENTRY(zombie) list;
00070 };
00071
00072 static AST_LIST_HEAD_STATIC(zombies, zombie);
00073
00074 static void *shaun_of_the_dead(void *data)
00075 {
00076 struct zombie *cur;
00077 int status;
00078 for (;;) {
00079 if (!AST_LIST_EMPTY(&zombies)) {
00080
00081 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
00082 AST_LIST_LOCK(&zombies);
00083 AST_LIST_TRAVERSE_SAFE_BEGIN(&zombies, cur, list) {
00084 if (waitpid(cur->pid, &status, WNOHANG) != 0) {
00085 AST_LIST_REMOVE_CURRENT(list);
00086 ast_free(cur);
00087 }
00088 }
00089 AST_LIST_TRAVERSE_SAFE_END
00090 AST_LIST_UNLOCK(&zombies);
00091 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
00092 }
00093 pthread_testcancel();
00094
00095 ast_poll(NULL, 0, AST_LIST_FIRST(&zombies) ? 5000 : 60000);
00096 }
00097 return NULL;
00098 }
00099
00100
00101 #define AST_MAX_FORMATS 10
00102
00103 static AST_RWLIST_HEAD_STATIC(groups, ast_group_info);
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119 int ast_app_dtget(struct ast_channel *chan, const char *context, char *collect, size_t size, int maxlen, int timeout)
00120 {
00121 struct ast_tone_zone_sound *ts;
00122 int res = 0, x = 0;
00123
00124 if (maxlen > size) {
00125 maxlen = size;
00126 }
00127
00128 if (!timeout) {
00129 if (chan->pbx && chan->pbx->dtimeoutms) {
00130 timeout = chan->pbx->dtimeoutms;
00131 } else {
00132 timeout = 5000;
00133 }
00134 }
00135
00136 if ((ts = ast_get_indication_tone(chan->zone, "dial"))) {
00137 res = ast_playtones_start(chan, 0, ts->data, 0);
00138 ts = ast_tone_zone_sound_unref(ts);
00139 } else {
00140 ast_log(LOG_NOTICE, "Huh....? no dial for indications?\n");
00141 }
00142
00143 for (x = strlen(collect); x < maxlen; ) {
00144 res = ast_waitfordigit(chan, timeout);
00145 if (!ast_ignore_pattern(context, collect)) {
00146 ast_playtones_stop(chan);
00147 }
00148 if (res < 1) {
00149 break;
00150 }
00151 if (res == '#') {
00152 break;
00153 }
00154 collect[x++] = res;
00155 if (!ast_matchmore_extension(chan, context, collect, 1,
00156 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL))) {
00157 break;
00158 }
00159 }
00160
00161 if (res >= 0) {
00162 res = ast_exists_extension(chan, context, collect, 1,
00163 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL)) ? 1 : 0;
00164 }
00165
00166 return res;
00167 }
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177 enum ast_getdata_result ast_app_getdata(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout)
00178 {
00179 int res = 0, to, fto;
00180 char *front, *filename;
00181
00182
00183
00184 if (maxlen)
00185 s[0] = '\0';
00186
00187 if (!prompt)
00188 prompt = "";
00189
00190 filename = ast_strdupa(prompt);
00191 while ((front = strsep(&filename, "&"))) {
00192 if (!ast_strlen_zero(front)) {
00193 res = ast_streamfile(c, front, c->language);
00194 if (res)
00195 continue;
00196 }
00197 if (ast_strlen_zero(filename)) {
00198
00199 fto = c->pbx ? c->pbx->rtimeoutms : 6000;
00200 to = c->pbx ? c->pbx->dtimeoutms : 2000;
00201
00202 if (timeout > 0) {
00203 fto = to = timeout;
00204 }
00205 if (timeout < 0) {
00206 fto = to = 1000000000;
00207 }
00208 } else {
00209
00210
00211
00212 fto = 50;
00213 to = c->pbx ? c->pbx->dtimeoutms : 2000;
00214 }
00215 res = ast_readstring(c, s, maxlen, to, fto, "#");
00216 if (res == AST_GETDATA_EMPTY_END_TERMINATED) {
00217 return res;
00218 }
00219 if (!ast_strlen_zero(s)) {
00220 return res;
00221 }
00222 }
00223
00224 return res;
00225 }
00226
00227
00228 static enum AST_LOCK_TYPE ast_lock_type = AST_LOCK_TYPE_LOCKFILE;
00229
00230 int ast_app_getdata_full(struct ast_channel *c, const char *prompt, char *s, int maxlen, int timeout, int audiofd, int ctrlfd)
00231 {
00232 int res, to = 2000, fto = 6000;
00233
00234 if (!ast_strlen_zero(prompt)) {
00235 res = ast_streamfile(c, prompt, c->language);
00236 if (res < 0) {
00237 return res;
00238 }
00239 }
00240
00241 if (timeout > 0) {
00242 fto = to = timeout;
00243 }
00244 if (timeout < 0) {
00245 fto = to = 1000000000;
00246 }
00247
00248 res = ast_readstring_full(c, s, maxlen, to, fto, "#", audiofd, ctrlfd);
00249
00250 return res;
00251 }
00252
00253 int ast_app_run_macro(struct ast_channel *autoservice_chan, struct ast_channel *macro_chan, const char * const macro_name, const char * const macro_args)
00254 {
00255 struct ast_app *macro_app;
00256 int res;
00257 char buf[1024];
00258
00259 macro_app = pbx_findapp("Macro");
00260 if (!macro_app) {
00261 ast_log(LOG_WARNING, "Cannot run macro '%s' because the 'Macro' application in not available\n", macro_name);
00262 return -1;
00263 }
00264 snprintf(buf, sizeof(buf), "%s%s%s", macro_name, ast_strlen_zero(macro_args) ? "" : ",", S_OR(macro_args, ""));
00265 if (autoservice_chan) {
00266 ast_autoservice_start(autoservice_chan);
00267 }
00268 res = pbx_exec(macro_chan, macro_app, buf);
00269 if (autoservice_chan) {
00270 ast_autoservice_stop(autoservice_chan);
00271 }
00272 return res;
00273 }
00274
00275 static int (*ast_has_voicemail_func)(const char *mailbox, const char *folder) = NULL;
00276 static int (*ast_inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs) = NULL;
00277 static int (*ast_inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs) = NULL;
00278 static int (*ast_sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context) = NULL;
00279 static int (*ast_messagecount_func)(const char *context, const char *mailbox, const char *folder) = NULL;
00280
00281 void ast_install_vm_functions(int (*has_voicemail_func)(const char *mailbox, const char *folder),
00282 int (*inboxcount_func)(const char *mailbox, int *newmsgs, int *oldmsgs),
00283 int (*inboxcount2_func)(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs),
00284 int (*messagecount_func)(const char *context, const char *mailbox, const char *folder),
00285 int (*sayname_func)(struct ast_channel *chan, const char *mailbox, const char *context))
00286 {
00287 ast_has_voicemail_func = has_voicemail_func;
00288 ast_inboxcount_func = inboxcount_func;
00289 ast_inboxcount2_func = inboxcount2_func;
00290 ast_messagecount_func = messagecount_func;
00291 ast_sayname_func = sayname_func;
00292 }
00293
00294 void ast_uninstall_vm_functions(void)
00295 {
00296 ast_has_voicemail_func = NULL;
00297 ast_inboxcount_func = NULL;
00298 ast_inboxcount2_func = NULL;
00299 ast_messagecount_func = NULL;
00300 ast_sayname_func = NULL;
00301 }
00302
00303 int ast_app_has_voicemail(const char *mailbox, const char *folder)
00304 {
00305 static int warned = 0;
00306 if (ast_has_voicemail_func) {
00307 return ast_has_voicemail_func(mailbox, folder);
00308 }
00309
00310 if (warned++ % 10 == 0) {
00311 ast_verb(3, "Message check requested for mailbox %s/folder %s but voicemail not loaded.\n", mailbox, folder ? folder : "INBOX");
00312 }
00313 return 0;
00314 }
00315
00316
00317 int ast_app_inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
00318 {
00319 static int warned = 0;
00320 if (newmsgs) {
00321 *newmsgs = 0;
00322 }
00323 if (oldmsgs) {
00324 *oldmsgs = 0;
00325 }
00326 if (ast_inboxcount_func) {
00327 return ast_inboxcount_func(mailbox, newmsgs, oldmsgs);
00328 }
00329
00330 if (warned++ % 10 == 0) {
00331 ast_verb(3, "Message count requested for mailbox %s but voicemail not loaded.\n", mailbox);
00332 }
00333
00334 return 0;
00335 }
00336
00337 int ast_app_inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
00338 {
00339 static int warned = 0;
00340 if (newmsgs) {
00341 *newmsgs = 0;
00342 }
00343 if (oldmsgs) {
00344 *oldmsgs = 0;
00345 }
00346 if (urgentmsgs) {
00347 *urgentmsgs = 0;
00348 }
00349 if (ast_inboxcount2_func) {
00350 return ast_inboxcount2_func(mailbox, urgentmsgs, newmsgs, oldmsgs);
00351 }
00352
00353 if (warned++ % 10 == 0) {
00354 ast_verb(3, "Message count requested for mailbox %s but voicemail not loaded.\n", mailbox);
00355 }
00356
00357 return 0;
00358 }
00359
00360 int ast_app_sayname(struct ast_channel *chan, const char *mailbox, const char *context)
00361 {
00362 if (ast_sayname_func) {
00363 return ast_sayname_func(chan, mailbox, context);
00364 }
00365 return -1;
00366 }
00367
00368 int ast_app_messagecount(const char *context, const char *mailbox, const char *folder)
00369 {
00370 static int warned = 0;
00371 if (ast_messagecount_func) {
00372 return ast_messagecount_func(context, mailbox, folder);
00373 }
00374
00375 if (!warned) {
00376 warned++;
00377 ast_verb(3, "Message count requested for mailbox %s@%s/%s but voicemail not loaded.\n", mailbox, context, folder);
00378 }
00379
00380 return 0;
00381 }
00382
00383 int ast_dtmf_stream(struct ast_channel *chan, struct ast_channel *peer, const char *digits, int between, unsigned int duration)
00384 {
00385 const char *ptr;
00386 int res = 0;
00387 struct ast_silence_generator *silgen = NULL;
00388
00389 if (!between) {
00390 between = 100;
00391 }
00392
00393 if (peer) {
00394 res = ast_autoservice_start(peer);
00395 }
00396
00397 if (!res) {
00398 res = ast_waitfor(chan, 100);
00399 }
00400
00401
00402 if (res < 0) {
00403 if (peer) {
00404 ast_autoservice_stop(peer);
00405 }
00406 return res;
00407 }
00408
00409 if (ast_opt_transmit_silence) {
00410 silgen = ast_channel_start_silence_generator(chan);
00411 }
00412
00413 for (ptr = digits; *ptr; ptr++) {
00414 if (*ptr == 'w') {
00415
00416 if ((res = ast_safe_sleep(chan, 500))) {
00417 break;
00418 }
00419 } else if (strchr("0123456789*#abcdfABCDF", *ptr)) {
00420
00421 if (*ptr == 'f' || *ptr == 'F') {
00422
00423 ast_indicate(chan, AST_CONTROL_FLASH);
00424 } else {
00425 ast_senddigit(chan, *ptr, duration);
00426 }
00427
00428 if ((res = ast_safe_sleep(chan, between))) {
00429 break;
00430 }
00431 } else {
00432 ast_log(LOG_WARNING, "Illegal DTMF character '%c' in string. (0-9*#aAbBcCdD allowed)\n", *ptr);
00433 }
00434 }
00435
00436 if (peer) {
00437
00438
00439 if (ast_autoservice_stop(peer) && !res) {
00440 res = -1;
00441 }
00442 }
00443
00444 if (silgen) {
00445 ast_channel_stop_silence_generator(chan, silgen);
00446 }
00447
00448 return res;
00449 }
00450
00451 struct linear_state {
00452 int fd;
00453 int autoclose;
00454 int allowoverride;
00455 int origwfmt;
00456 };
00457
00458 static void linear_release(struct ast_channel *chan, void *params)
00459 {
00460 struct linear_state *ls = params;
00461
00462 if (ls->origwfmt && ast_set_write_format(chan, ls->origwfmt)) {
00463 ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%d'\n", chan->name, ls->origwfmt);
00464 }
00465
00466 if (ls->autoclose) {
00467 close(ls->fd);
00468 }
00469
00470 ast_free(params);
00471 }
00472
00473 static int linear_generator(struct ast_channel *chan, void *data, int len, int samples)
00474 {
00475 short buf[2048 + AST_FRIENDLY_OFFSET / 2];
00476 struct linear_state *ls = data;
00477 struct ast_frame f = {
00478 .frametype = AST_FRAME_VOICE,
00479 .subclass.codec = AST_FORMAT_SLINEAR,
00480 .data.ptr = buf + AST_FRIENDLY_OFFSET / 2,
00481 .offset = AST_FRIENDLY_OFFSET,
00482 };
00483 int res;
00484
00485 len = samples * 2;
00486 if (len > sizeof(buf) - AST_FRIENDLY_OFFSET) {
00487 ast_log(LOG_WARNING, "Can't generate %d bytes of data!\n" , len);
00488 len = sizeof(buf) - AST_FRIENDLY_OFFSET;
00489 }
00490 res = read(ls->fd, buf + AST_FRIENDLY_OFFSET/2, len);
00491 if (res > 0) {
00492 f.datalen = res;
00493 f.samples = res / 2;
00494 ast_write(chan, &f);
00495 if (res == len) {
00496 return 0;
00497 }
00498 }
00499 return -1;
00500 }
00501
00502 static void *linear_alloc(struct ast_channel *chan, void *params)
00503 {
00504 struct linear_state *ls = params;
00505
00506 if (!params) {
00507 return NULL;
00508 }
00509
00510
00511 if (ls->allowoverride) {
00512 ast_set_flag(chan, AST_FLAG_WRITE_INT);
00513 } else {
00514 ast_clear_flag(chan, AST_FLAG_WRITE_INT);
00515 }
00516
00517 ls->origwfmt = chan->writeformat;
00518
00519 if (ast_set_write_format(chan, AST_FORMAT_SLINEAR)) {
00520 ast_log(LOG_WARNING, "Unable to set '%s' to linear format (write)\n", chan->name);
00521 ast_free(ls);
00522 ls = params = NULL;
00523 }
00524
00525 return params;
00526 }
00527
00528 static struct ast_generator linearstream =
00529 {
00530 .alloc = linear_alloc,
00531 .release = linear_release,
00532 .generate = linear_generator,
00533 };
00534
00535 int ast_linear_stream(struct ast_channel *chan, const char *filename, int fd, int allowoverride)
00536 {
00537 struct linear_state *lin;
00538 char tmpf[256];
00539 int res = -1;
00540 int autoclose = 0;
00541 if (fd < 0) {
00542 if (ast_strlen_zero(filename)) {
00543 return -1;
00544 }
00545 autoclose = 1;
00546 if (filename[0] == '/') {
00547 ast_copy_string(tmpf, filename, sizeof(tmpf));
00548 } else {
00549 snprintf(tmpf, sizeof(tmpf), "%s/%s/%s", ast_config_AST_DATA_DIR, "sounds", filename);
00550 }
00551 if ((fd = open(tmpf, O_RDONLY)) < 0) {
00552 ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", tmpf, strerror(errno));
00553 return -1;
00554 }
00555 }
00556 if ((lin = ast_calloc(1, sizeof(*lin)))) {
00557 lin->fd = fd;
00558 lin->allowoverride = allowoverride;
00559 lin->autoclose = autoclose;
00560 res = ast_activate_generator(chan, &linearstream, lin);
00561 }
00562 return res;
00563 }
00564
00565 int ast_control_streamfile(struct ast_channel *chan, const char *file,
00566 const char *fwd, const char *rev,
00567 const char *stop, const char *suspend,
00568 const char *restart, int skipms, long *offsetms)
00569 {
00570 char *breaks = NULL;
00571 char *end = NULL;
00572 int blen = 2;
00573 int res;
00574 long pause_restart_point = 0;
00575 long offset = 0;
00576
00577 if (!file) {
00578 return -1;
00579 }
00580 if (offsetms) {
00581 offset = *offsetms * 8;
00582 }
00583
00584 if (stop) {
00585 blen += strlen(stop);
00586 }
00587 if (suspend) {
00588 blen += strlen(suspend);
00589 }
00590 if (restart) {
00591 blen += strlen(restart);
00592 }
00593
00594 if (blen > 2) {
00595 breaks = ast_alloca(blen + 1);
00596 breaks[0] = '\0';
00597 if (stop) {
00598 strcat(breaks, stop);
00599 }
00600 if (suspend) {
00601 strcat(breaks, suspend);
00602 }
00603 if (restart) {
00604 strcat(breaks, restart);
00605 }
00606 }
00607 if (chan->_state != AST_STATE_UP) {
00608 res = ast_answer(chan);
00609 }
00610
00611 if ((end = strchr(file, ':'))) {
00612 if (!strcasecmp(end, ":end")) {
00613 *end = '\0';
00614 end++;
00615 }
00616 }
00617
00618 for (;;) {
00619 ast_stopstream(chan);
00620 res = ast_streamfile(chan, file, chan->language);
00621 if (!res) {
00622 if (pause_restart_point) {
00623 ast_seekstream(chan->stream, pause_restart_point, SEEK_SET);
00624 pause_restart_point = 0;
00625 }
00626 else if (end || offset < 0) {
00627 if (offset == -8) {
00628 offset = 0;
00629 }
00630 ast_verb(3, "ControlPlayback seek to offset %ld from end\n", offset);
00631
00632 ast_seekstream(chan->stream, offset, SEEK_END);
00633 end = NULL;
00634 offset = 0;
00635 } else if (offset) {
00636 ast_verb(3, "ControlPlayback seek to offset %ld\n", offset);
00637 ast_seekstream(chan->stream, offset, SEEK_SET);
00638 offset = 0;
00639 }
00640 res = ast_waitstream_fr(chan, breaks, fwd, rev, skipms);
00641 }
00642
00643 if (res < 1) {
00644 break;
00645 }
00646
00647
00648 if (restart && strchr(restart, res)) {
00649 ast_debug(1, "we'll restart the stream here at next loop\n");
00650 pause_restart_point = 0;
00651 continue;
00652 }
00653
00654 if (suspend && strchr(suspend, res)) {
00655 pause_restart_point = ast_tellstream(chan->stream);
00656 for (;;) {
00657 ast_stopstream(chan);
00658 if (!(res = ast_waitfordigit(chan, 1000))) {
00659 continue;
00660 } else if (res == -1 || strchr(suspend, res) || (stop && strchr(stop, res))) {
00661 break;
00662 }
00663 }
00664 if (res == *suspend) {
00665 res = 0;
00666 continue;
00667 }
00668 }
00669
00670 if (res == -1) {
00671 break;
00672 }
00673
00674
00675 if (stop && strchr(stop, res)) {
00676 break;
00677 }
00678 }
00679
00680 if (pause_restart_point) {
00681 offset = pause_restart_point;
00682 } else {
00683 if (chan->stream) {
00684 offset = ast_tellstream(chan->stream);
00685 } else {
00686 offset = -8;
00687 }
00688 }
00689
00690 if (offsetms) {
00691 *offsetms = offset / 8;
00692 }
00693
00694
00695 if (res > 0 || chan->stream) {
00696 res = (char)res;
00697 }
00698
00699 ast_stopstream(chan);
00700
00701 return res;
00702 }
00703
00704 int ast_play_and_wait(struct ast_channel *chan, const char *fn)
00705 {
00706 int d = 0;
00707
00708 ast_test_suite_event_notify("PLAYBACK", "Message: %s", fn);
00709 if ((d = ast_streamfile(chan, fn, chan->language))) {
00710 return d;
00711 }
00712
00713 d = ast_waitstream(chan, AST_DIGIT_ANY);
00714
00715 ast_stopstream(chan);
00716
00717 return d;
00718 }
00719
00720 static int global_silence_threshold = 128;
00721 static int global_maxsilence = 0;
00722
00723
00724
00725
00726
00727
00728
00729
00730
00731
00732
00733
00734
00735
00736
00737
00738
00739
00740
00741
00742
00743
00744
00745 static int __ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence, const char *path, int prepend, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound)
00746 {
00747 int d = 0;
00748 char *fmts;
00749 char comment[256];
00750 int x, fmtcnt = 1, res = -1, outmsg = 0;
00751 struct ast_filestream *others[AST_MAX_FORMATS];
00752 char *sfmt[AST_MAX_FORMATS];
00753 char *stringp = NULL;
00754 time_t start, end;
00755 struct ast_dsp *sildet = NULL;
00756 int totalsilence = 0;
00757 int dspsilence = 0;
00758 int olddspsilence = 0;
00759 int rfmt = 0;
00760 struct ast_silence_generator *silgen = NULL;
00761 char prependfile[80];
00762
00763 if (silencethreshold < 0) {
00764 silencethreshold = global_silence_threshold;
00765 }
00766
00767 if (maxsilence < 0) {
00768 maxsilence = global_maxsilence;
00769 }
00770
00771
00772 if (!duration) {
00773 ast_log(LOG_WARNING, "Error play_and_record called without duration pointer\n");
00774 return -1;
00775 }
00776
00777 ast_debug(1, "play_and_record: %s, %s, '%s'\n", playfile ? playfile : "<None>", recordfile, fmt);
00778 snprintf(comment, sizeof(comment), "Playing %s, Recording to: %s on %s\n", playfile ? playfile : "<None>", recordfile, chan->name);
00779
00780 if (playfile || beep) {
00781 if (!beep) {
00782 d = ast_play_and_wait(chan, playfile);
00783 }
00784 if (d > -1) {
00785 d = ast_stream_and_wait(chan, "beep", "");
00786 }
00787 if (d < 0) {
00788 return -1;
00789 }
00790 }
00791
00792 if (prepend) {
00793 ast_copy_string(prependfile, recordfile, sizeof(prependfile));
00794 strncat(prependfile, "-prepend", sizeof(prependfile) - strlen(prependfile) - 1);
00795 }
00796
00797 fmts = ast_strdupa(fmt);
00798
00799 stringp = fmts;
00800 strsep(&stringp, "|");
00801 ast_debug(1, "Recording Formats: sfmts=%s\n", fmts);
00802 sfmt[0] = ast_strdupa(fmts);
00803
00804 while ((fmt = strsep(&stringp, "|"))) {
00805 if (fmtcnt > AST_MAX_FORMATS - 1) {
00806 ast_log(LOG_WARNING, "Please increase AST_MAX_FORMATS in file.h\n");
00807 break;
00808 }
00809 sfmt[fmtcnt++] = ast_strdupa(fmt);
00810 }
00811
00812 end = start = time(NULL);
00813 for (x = 0; x < fmtcnt; x++) {
00814 others[x] = ast_writefile(prepend ? prependfile : recordfile, sfmt[x], comment, O_TRUNC, 0, AST_FILE_MODE);
00815 ast_verb(3, "x=%d, open writing: %s format: %s, %p\n", x, prepend ? prependfile : recordfile, sfmt[x], others[x]);
00816
00817 if (!others[x]) {
00818 break;
00819 }
00820 }
00821
00822 if (path) {
00823 ast_unlock_path(path);
00824 }
00825
00826 if (maxsilence > 0) {
00827 sildet = ast_dsp_new();
00828 if (!sildet) {
00829 ast_log(LOG_WARNING, "Unable to create silence detector :(\n");
00830 return -1;
00831 }
00832 ast_dsp_set_threshold(sildet, silencethreshold);
00833 rfmt = chan->readformat;
00834 res = ast_set_read_format(chan, AST_FORMAT_SLINEAR);
00835 if (res < 0) {
00836 ast_log(LOG_WARNING, "Unable to set to linear mode, giving up\n");
00837 ast_dsp_free(sildet);
00838 return -1;
00839 }
00840 }
00841
00842 if (!prepend) {
00843
00844 ast_indicate(chan, AST_CONTROL_VIDUPDATE);
00845
00846 if (ast_opt_transmit_silence) {
00847 silgen = ast_channel_start_silence_generator(chan);
00848 }
00849 }
00850
00851 if (x == fmtcnt) {
00852
00853
00854 struct ast_frame *f;
00855 for (;;) {
00856 if (!(res = ast_waitfor(chan, 2000))) {
00857 ast_debug(1, "One waitfor failed, trying another\n");
00858
00859 if (!(res = ast_waitfor(chan, 2000))) {
00860 ast_log(LOG_WARNING, "No audio available on %s??\n", chan->name);
00861 res = -1;
00862 }
00863 }
00864
00865 if (res < 0) {
00866 f = NULL;
00867 break;
00868 }
00869 if (!(f = ast_read(chan))) {
00870 break;
00871 }
00872 if (f->frametype == AST_FRAME_VOICE) {
00873
00874 for (x = 0; x < fmtcnt; x++) {
00875 if (prepend && !others[x]) {
00876 break;
00877 }
00878 res = ast_writestream(others[x], f);
00879 }
00880
00881
00882 if (maxsilence > 0) {
00883 dspsilence = 0;
00884 ast_dsp_silence(sildet, f, &dspsilence);
00885 if (olddspsilence > dspsilence) {
00886 totalsilence += olddspsilence;
00887 }
00888 olddspsilence = dspsilence;
00889
00890 if (dspsilence > maxsilence) {
00891
00892 ast_verb(3, "Recording automatically stopped after a silence of %d seconds\n", dspsilence/1000);
00893 res = 'S';
00894 outmsg = 2;
00895 break;
00896 }
00897 }
00898
00899 if (res) {
00900 ast_log(LOG_WARNING, "Error writing frame\n");
00901 break;
00902 }
00903 } else if (f->frametype == AST_FRAME_VIDEO) {
00904
00905 ast_writestream(others[0], f);
00906 } else if (f->frametype == AST_FRAME_DTMF) {
00907 if (prepend) {
00908
00909 ast_verb(3, "User ended message by pressing %c\n", f->subclass.integer);
00910 res = 't';
00911 outmsg = 2;
00912 break;
00913 }
00914 if (strchr(acceptdtmf, f->subclass.integer)) {
00915 ast_verb(3, "User ended message by pressing %c\n", f->subclass.integer);
00916 res = f->subclass.integer;
00917 outmsg = 2;
00918 break;
00919 }
00920 if (strchr(canceldtmf, f->subclass.integer)) {
00921 ast_verb(3, "User cancelled message by pressing %c\n", f->subclass.integer);
00922 res = f->subclass.integer;
00923 outmsg = 0;
00924 break;
00925 }
00926 }
00927 if (maxtime) {
00928 end = time(NULL);
00929 if (maxtime < (end - start)) {
00930 ast_verb(3, "Took too long, cutting it short...\n");
00931 res = 't';
00932 outmsg = 2;
00933 break;
00934 }
00935 }
00936 ast_frfree(f);
00937 }
00938 if (!f) {
00939 ast_verb(3, "User hung up\n");
00940 res = -1;
00941 outmsg = 1;
00942 } else {
00943 ast_frfree(f);
00944 }
00945 } else {
00946 ast_log(LOG_WARNING, "Error creating writestream '%s', format '%s'\n", recordfile, sfmt[x]);
00947 }
00948
00949 if (!prepend) {
00950 if (silgen) {
00951 ast_channel_stop_silence_generator(chan, silgen);
00952 }
00953 }
00954
00955
00956
00957
00958
00959
00960
00961
00962
00963
00964 *duration = others[0] ? ast_tellstream(others[0]) / 8000 : 0;
00965 if (sound_duration) {
00966 *sound_duration = *duration;
00967 }
00968
00969 if (!prepend) {
00970
00971 if (olddspsilence <= dspsilence) {
00972 totalsilence += dspsilence;
00973 }
00974
00975 if (sound_duration) {
00976 if (totalsilence > 0) {
00977 *sound_duration -= (totalsilence - 200) / 1000;
00978 }
00979 if (*sound_duration < 0) {
00980 *sound_duration = 0;
00981 }
00982 }
00983
00984 if (dspsilence > 0) {
00985 *duration -= (dspsilence - 200) / 1000;
00986 }
00987 if (*duration < 0) {
00988 *duration = 0;
00989 }
00990
00991 for (x = 0; x < fmtcnt; x++) {
00992 if (!others[x]) {
00993 break;
00994 }
00995
00996
00997
00998
00999
01000 if (res > 0 && dspsilence) {
01001
01002 ast_stream_rewind(others[x], dspsilence - 200);
01003 }
01004 ast_truncstream(others[x]);
01005 ast_closestream(others[x]);
01006 }
01007 }
01008
01009 if (prepend && outmsg) {
01010 struct ast_filestream *realfiles[AST_MAX_FORMATS];
01011 struct ast_frame *fr;
01012
01013 for (x = 0; x < fmtcnt; x++) {
01014 snprintf(comment, sizeof(comment), "Opening the real file %s.%s\n", recordfile, sfmt[x]);
01015 realfiles[x] = ast_readfile(recordfile, sfmt[x], comment, O_RDONLY, 0, 0);
01016 if (!others[x] || !realfiles[x]) {
01017 break;
01018 }
01019
01020 if (dspsilence) {
01021 ast_stream_rewind(others[x], dspsilence - 200);
01022 }
01023 ast_truncstream(others[x]);
01024
01025 while ((fr = ast_readframe(realfiles[x]))) {
01026 ast_writestream(others[x], fr);
01027 ast_frfree(fr);
01028 }
01029 ast_closestream(others[x]);
01030 ast_closestream(realfiles[x]);
01031 ast_filerename(prependfile, recordfile, sfmt[x]);
01032 ast_verb(4, "Recording Format: sfmts=%s, prependfile %s, recordfile %s\n", sfmt[x], prependfile, recordfile);
01033 ast_filedelete(prependfile, sfmt[x]);
01034 }
01035 }
01036 if (rfmt && ast_set_read_format(chan, rfmt)) {
01037 ast_log(LOG_WARNING, "Unable to restore format %s to channel '%s'\n", ast_getformatname(rfmt), chan->name);
01038 }
01039 if ((outmsg == 2) && (!skip_confirmation_sound)) {
01040 ast_stream_and_wait(chan, "auth-thankyou", "");
01041 }
01042 if (sildet) {
01043 ast_dsp_free(sildet);
01044 }
01045 return res;
01046 }
01047
01048 static const char default_acceptdtmf[] = "#";
01049 static const char default_canceldtmf[] = "";
01050
01051 int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path, const char *acceptdtmf, const char *canceldtmf)
01052 {
01053 return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, S_OR(acceptdtmf, default_acceptdtmf), S_OR(canceldtmf, default_canceldtmf), 0);
01054 }
01055
01056 int ast_play_and_record(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, int *sound_duration, int silencethreshold, int maxsilence, const char *path)
01057 {
01058 return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, silencethreshold, maxsilence, path, 0, default_acceptdtmf, default_canceldtmf, 0);
01059 }
01060
01061 int ast_play_and_prepend(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence)
01062 {
01063 return __ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, beep, silencethreshold, maxsilence, NULL, 1, default_acceptdtmf, default_canceldtmf, 1);
01064 }
01065
01066
01067
01068 int ast_app_group_split_group(const char *data, char *group, int group_max, char *category, int category_max)
01069 {
01070 int res = 0;
01071 char tmp[256];
01072 char *grp = NULL, *cat = NULL;
01073
01074 if (!ast_strlen_zero(data)) {
01075 ast_copy_string(tmp, data, sizeof(tmp));
01076 grp = tmp;
01077 if ((cat = strchr(tmp, '@'))) {
01078 *cat++ = '\0';
01079 }
01080 }
01081
01082 if (!ast_strlen_zero(grp)) {
01083 ast_copy_string(group, grp, group_max);
01084 } else {
01085 *group = '\0';
01086 }
01087
01088 if (!ast_strlen_zero(cat)) {
01089 ast_copy_string(category, cat, category_max);
01090 }
01091
01092 return res;
01093 }
01094
01095 int ast_app_group_set_channel(struct ast_channel *chan, const char *data)
01096 {
01097 int res = 0;
01098 char group[80] = "", category[80] = "";
01099 struct ast_group_info *gi = NULL;
01100 size_t len = 0;
01101
01102 if (ast_app_group_split_group(data, group, sizeof(group), category, sizeof(category))) {
01103 return -1;
01104 }
01105
01106
01107 len = sizeof(*gi) + strlen(group) + 1;
01108 if (!ast_strlen_zero(category)) {
01109 len += strlen(category) + 1;
01110 }
01111
01112 AST_RWLIST_WRLOCK(&groups);
01113 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
01114 if ((gi->chan == chan) && ((ast_strlen_zero(category) && ast_strlen_zero(gi->category)) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
01115 AST_RWLIST_REMOVE_CURRENT(group_list);
01116 free(gi);
01117 break;
01118 }
01119 }
01120 AST_RWLIST_TRAVERSE_SAFE_END;
01121
01122 if (ast_strlen_zero(group)) {
01123
01124 } else if ((gi = calloc(1, len))) {
01125 gi->chan = chan;
01126 gi->group = (char *) gi + sizeof(*gi);
01127 strcpy(gi->group, group);
01128 if (!ast_strlen_zero(category)) {
01129 gi->category = (char *) gi + sizeof(*gi) + strlen(group) + 1;
01130 strcpy(gi->category, category);
01131 }
01132 AST_RWLIST_INSERT_TAIL(&groups, gi, group_list);
01133 } else {
01134 res = -1;
01135 }
01136
01137 AST_RWLIST_UNLOCK(&groups);
01138
01139 return res;
01140 }
01141
01142 int ast_app_group_get_count(const char *group, const char *category)
01143 {
01144 struct ast_group_info *gi = NULL;
01145 int count = 0;
01146
01147 if (ast_strlen_zero(group)) {
01148 return 0;
01149 }
01150
01151 AST_RWLIST_RDLOCK(&groups);
01152 AST_RWLIST_TRAVERSE(&groups, gi, group_list) {
01153 if (!strcasecmp(gi->group, group) && (ast_strlen_zero(category) || (!ast_strlen_zero(gi->category) && !strcasecmp(gi->category, category)))) {
01154 count++;
01155 }
01156 }
01157 AST_RWLIST_UNLOCK(&groups);
01158
01159 return count;
01160 }
01161
01162 int ast_app_group_match_get_count(const char *groupmatch, const char *category)
01163 {
01164 struct ast_group_info *gi = NULL;
01165 regex_t regexbuf_group;
01166 regex_t regexbuf_category;
01167 int count = 0;
01168
01169 if (ast_strlen_zero(groupmatch)) {
01170 ast_log(LOG_NOTICE, "groupmatch empty\n");
01171 return 0;
01172 }
01173
01174
01175 if (regcomp(®exbuf_group, groupmatch, REG_EXTENDED | REG_NOSUB)) {
01176 ast_log(LOG_ERROR, "Regex compile failed on: %s\n", groupmatch);
01177 return 0;
01178 }
01179
01180 if (!ast_strlen_zero(category) && regcomp(®exbuf_category, category, REG_EXTENDED | REG_NOSUB)) {
01181 ast_log(LOG_ERROR, "Regex compile failed on: %s\n", category);
01182 regfree(®exbuf_group);
01183 return 0;
01184 }
01185
01186 AST_RWLIST_RDLOCK(&groups);
01187 AST_RWLIST_TRAVERSE(&groups, gi, group_list) {
01188 if (!regexec(®exbuf_group, gi->group, 0, NULL, 0) && (ast_strlen_zero(category) || (!ast_strlen_zero(gi->category) && !regexec(®exbuf_category, gi->category, 0, NULL, 0)))) {
01189 count++;
01190 }
01191 }
01192 AST_RWLIST_UNLOCK(&groups);
01193
01194 regfree(®exbuf_group);
01195 if (!ast_strlen_zero(category)) {
01196 regfree(®exbuf_category);
01197 }
01198
01199 return count;
01200 }
01201
01202 int ast_app_group_update(struct ast_channel *old, struct ast_channel *new)
01203 {
01204 struct ast_group_info *gi = NULL;
01205
01206 AST_RWLIST_WRLOCK(&groups);
01207 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
01208 if (gi->chan == old) {
01209 gi->chan = new;
01210 } else if (gi->chan == new) {
01211 AST_RWLIST_REMOVE_CURRENT(group_list);
01212 ast_free(gi);
01213 }
01214 }
01215 AST_RWLIST_TRAVERSE_SAFE_END;
01216 AST_RWLIST_UNLOCK(&groups);
01217
01218 return 0;
01219 }
01220
01221 int ast_app_group_discard(struct ast_channel *chan)
01222 {
01223 struct ast_group_info *gi = NULL;
01224
01225 AST_RWLIST_WRLOCK(&groups);
01226 AST_RWLIST_TRAVERSE_SAFE_BEGIN(&groups, gi, group_list) {
01227 if (gi->chan == chan) {
01228 AST_RWLIST_REMOVE_CURRENT(group_list);
01229 ast_free(gi);
01230 }
01231 }
01232 AST_RWLIST_TRAVERSE_SAFE_END;
01233 AST_RWLIST_UNLOCK(&groups);
01234
01235 return 0;
01236 }
01237
01238 int ast_app_group_list_wrlock(void)
01239 {
01240 return AST_RWLIST_WRLOCK(&groups);
01241 }
01242
01243 int ast_app_group_list_rdlock(void)
01244 {
01245 return AST_RWLIST_RDLOCK(&groups);
01246 }
01247
01248 struct ast_group_info *ast_app_group_list_head(void)
01249 {
01250 return AST_RWLIST_FIRST(&groups);
01251 }
01252
01253 int ast_app_group_list_unlock(void)
01254 {
01255 return AST_RWLIST_UNLOCK(&groups);
01256 }
01257
01258 #undef ast_app_separate_args
01259 unsigned int ast_app_separate_args(char *buf, char delim, char **array, int arraylen);
01260
01261 unsigned int __ast_app_separate_args(char *buf, char delim, int remove_chars, char **array, int arraylen)
01262 {
01263 int argc;
01264 char *scan, *wasdelim = NULL;
01265 int paren = 0, quote = 0, bracket = 0;
01266
01267 if (!array || !arraylen) {
01268 return 0;
01269 }
01270
01271 memset(array, 0, arraylen * sizeof(*array));
01272
01273 if (!buf) {
01274 return 0;
01275 }
01276
01277 scan = buf;
01278
01279 for (argc = 0; *scan && (argc < arraylen - 1); argc++) {
01280 array[argc] = scan;
01281 for (; *scan; scan++) {
01282 if (*scan == '(') {
01283 paren++;
01284 } else if (*scan == ')') {
01285 if (paren) {
01286 paren--;
01287 }
01288 } else if (*scan == '[') {
01289 bracket++;
01290 } else if (*scan == ']') {
01291 if (bracket) {
01292 bracket--;
01293 }
01294 } else if (*scan == '"' && delim != '"') {
01295 quote = quote ? 0 : 1;
01296 if (remove_chars) {
01297
01298 memmove(scan, scan + 1, strlen(scan));
01299 scan--;
01300 }
01301 } else if (*scan == '\\') {
01302 if (remove_chars) {
01303
01304 memmove(scan, scan + 1, strlen(scan));
01305 } else {
01306 scan++;
01307 }
01308 } else if ((*scan == delim) && !paren && !quote && !bracket) {
01309 wasdelim = scan;
01310 *scan++ = '\0';
01311 break;
01312 }
01313 }
01314 }
01315
01316
01317
01318 if (*scan || (scan > buf && (scan - 1) == wasdelim)) {
01319 array[argc++] = scan;
01320 }
01321
01322 return argc;
01323 }
01324
01325
01326 unsigned int ast_app_separate_args(char *buf, char delim, char **array, int arraylen)
01327 {
01328 return __ast_app_separate_args(buf, delim, 1, array, arraylen);
01329 }
01330
01331 static enum AST_LOCK_RESULT ast_lock_path_lockfile(const char *path)
01332 {
01333 char *s;
01334 char *fs;
01335 int res;
01336 int fd;
01337 int lp = strlen(path);
01338 time_t start;
01339
01340 s = ast_alloca(lp + 10);
01341 fs = ast_alloca(lp + 20);
01342
01343 snprintf(fs, strlen(path) + 19, "%s/.lock-%08lx", path, ast_random());
01344 fd = open(fs, O_WRONLY | O_CREAT | O_EXCL, AST_FILE_MODE);
01345 if (fd < 0) {
01346 ast_log(LOG_ERROR, "Unable to create lock file '%s': %s\n", path, strerror(errno));
01347 return AST_LOCK_PATH_NOT_FOUND;
01348 }
01349 close(fd);
01350
01351 snprintf(s, strlen(path) + 9, "%s/.lock", path);
01352 start = time(NULL);
01353 while (((res = link(fs, s)) < 0) && (errno == EEXIST) && (time(NULL) - start < 5)) {
01354 sched_yield();
01355 }
01356
01357 unlink(fs);
01358
01359 if (res) {
01360 ast_log(LOG_WARNING, "Failed to lock path '%s': %s\n", path, strerror(errno));
01361 return AST_LOCK_TIMEOUT;
01362 } else {
01363 ast_debug(1, "Locked path '%s'\n", path);
01364 return AST_LOCK_SUCCESS;
01365 }
01366 }
01367
01368 static int ast_unlock_path_lockfile(const char *path)
01369 {
01370 char *s;
01371 int res;
01372
01373 s = ast_alloca(strlen(path) + 10);
01374
01375 snprintf(s, strlen(path) + 9, "%s/%s", path, ".lock");
01376
01377 if ((res = unlink(s))) {
01378 ast_log(LOG_ERROR, "Could not unlock path '%s': %s\n", path, strerror(errno));
01379 } else {
01380 ast_debug(1, "Unlocked path '%s'\n", path);
01381 }
01382
01383 return res;
01384 }
01385
01386 struct path_lock {
01387 AST_LIST_ENTRY(path_lock) le;
01388 int fd;
01389 char *path;
01390 };
01391
01392 static AST_LIST_HEAD_STATIC(path_lock_list, path_lock);
01393
01394 static void path_lock_destroy(struct path_lock *obj)
01395 {
01396 if (obj->fd >= 0) {
01397 close(obj->fd);
01398 }
01399 if (obj->path) {
01400 free(obj->path);
01401 }
01402 free(obj);
01403 }
01404
01405 static enum AST_LOCK_RESULT ast_lock_path_flock(const char *path)
01406 {
01407 char *fs;
01408 int res;
01409 int fd;
01410 time_t start;
01411 struct path_lock *pl;
01412 struct stat st, ost;
01413
01414 fs = ast_alloca(strlen(path) + 20);
01415
01416 snprintf(fs, strlen(path) + 19, "%s/lock", path);
01417 if (lstat(fs, &st) == 0) {
01418 if ((st.st_mode & S_IFMT) == S_IFLNK) {
01419 ast_log(LOG_WARNING, "Unable to create lock file "
01420 "'%s': it's already a symbolic link\n",
01421 fs);
01422 return AST_LOCK_FAILURE;
01423 }
01424 if (st.st_nlink > 1) {
01425 ast_log(LOG_WARNING, "Unable to create lock file "
01426 "'%s': %u hard links exist\n",
01427 fs, (unsigned int) st.st_nlink);
01428 return AST_LOCK_FAILURE;
01429 }
01430 }
01431 if ((fd = open(fs, O_WRONLY | O_CREAT, 0600)) < 0) {
01432 ast_log(LOG_WARNING, "Unable to create lock file '%s': %s\n",
01433 fs, strerror(errno));
01434 return AST_LOCK_PATH_NOT_FOUND;
01435 }
01436 if (!(pl = ast_calloc(1, sizeof(*pl)))) {
01437
01438
01439
01440
01441
01442 close(fd);
01443 return AST_LOCK_FAILURE;
01444 }
01445 pl->fd = fd;
01446 pl->path = strdup(path);
01447
01448 time(&start);
01449 while (
01450 #ifdef SOLARIS
01451 ((res = fcntl(pl->fd, F_SETLK, fcntl(pl->fd, F_GETFL) | O_NONBLOCK)) < 0) &&
01452 #else
01453 ((res = flock(pl->fd, LOCK_EX | LOCK_NB)) < 0) &&
01454 #endif
01455 (errno == EWOULDBLOCK) &&
01456 (time(NULL) - start < 5))
01457 usleep(1000);
01458 if (res) {
01459 ast_log(LOG_WARNING, "Failed to lock path '%s': %s\n",
01460 path, strerror(errno));
01461
01462
01463
01464 path_lock_destroy(pl);
01465 return AST_LOCK_TIMEOUT;
01466 }
01467
01468
01469
01470
01471 if (lstat(fs, &st) != 0 && fstat(pl->fd, &ost) != 0 &&
01472 st.st_dev != ost.st_dev &&
01473 st.st_ino != ost.st_ino) {
01474 ast_log(LOG_WARNING, "Unable to create lock file '%s': "
01475 "file changed underneath us\n", fs);
01476 path_lock_destroy(pl);
01477 return AST_LOCK_FAILURE;
01478 }
01479
01480
01481 AST_LIST_LOCK(&path_lock_list);
01482 AST_LIST_INSERT_TAIL(&path_lock_list, pl, le);
01483 AST_LIST_UNLOCK(&path_lock_list);
01484
01485 ast_debug(1, "Locked path '%s'\n", path);
01486
01487 return AST_LOCK_SUCCESS;
01488 }
01489
01490 static int ast_unlock_path_flock(const char *path)
01491 {
01492 char *s;
01493 struct path_lock *p;
01494
01495 s = ast_alloca(strlen(path) + 20);
01496
01497 AST_LIST_LOCK(&path_lock_list);
01498 AST_LIST_TRAVERSE_SAFE_BEGIN(&path_lock_list, p, le) {
01499 if (!strcmp(p->path, path)) {
01500 AST_LIST_REMOVE_CURRENT(le);
01501 break;
01502 }
01503 }
01504 AST_LIST_TRAVERSE_SAFE_END;
01505 AST_LIST_UNLOCK(&path_lock_list);
01506
01507 if (p) {
01508 snprintf(s, strlen(path) + 19, "%s/lock", path);
01509 unlink(s);
01510 path_lock_destroy(p);
01511 ast_log(LOG_DEBUG, "Unlocked path '%s'\n", path);
01512 } else {
01513 ast_log(LOG_DEBUG, "Failed to unlock path '%s': "
01514 "lock not found\n", path);
01515 }
01516
01517 return 0;
01518 }
01519
01520 void ast_set_lock_type(enum AST_LOCK_TYPE type)
01521 {
01522 ast_lock_type = type;
01523 }
01524
01525 enum AST_LOCK_RESULT ast_lock_path(const char *path)
01526 {
01527 enum AST_LOCK_RESULT r = AST_LOCK_FAILURE;
01528
01529 switch (ast_lock_type) {
01530 case AST_LOCK_TYPE_LOCKFILE:
01531 r = ast_lock_path_lockfile(path);
01532 break;
01533 case AST_LOCK_TYPE_FLOCK:
01534 r = ast_lock_path_flock(path);
01535 break;
01536 }
01537
01538 return r;
01539 }
01540
01541 int ast_unlock_path(const char *path)
01542 {
01543 int r = 0;
01544
01545 switch (ast_lock_type) {
01546 case AST_LOCK_TYPE_LOCKFILE:
01547 r = ast_unlock_path_lockfile(path);
01548 break;
01549 case AST_LOCK_TYPE_FLOCK:
01550 r = ast_unlock_path_flock(path);
01551 break;
01552 }
01553
01554 return r;
01555 }
01556
01557 int ast_record_review(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime, const char *fmt, int *duration, const char *path)
01558 {
01559 int silencethreshold;
01560 int maxsilence = 0;
01561 int res = 0;
01562 int cmd = 0;
01563 int max_attempts = 3;
01564 int attempts = 0;
01565 int recorded = 0;
01566 int message_exists = 0;
01567
01568
01569
01570 if (!duration) {
01571 ast_log(LOG_WARNING, "Error ast_record_review called without duration pointer\n");
01572 return -1;
01573 }
01574
01575 cmd = '3';
01576
01577 silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
01578
01579 while ((cmd >= 0) && (cmd != 't')) {
01580 switch (cmd) {
01581 case '1':
01582 if (!message_exists) {
01583
01584 cmd = '3';
01585 break;
01586 } else {
01587 ast_stream_and_wait(chan, "vm-msgsaved", "");
01588 cmd = 't';
01589 return res;
01590 }
01591 case '2':
01592
01593 ast_verb(3, "Reviewing the recording\n");
01594 cmd = ast_stream_and_wait(chan, recordfile, AST_DIGIT_ANY);
01595 break;
01596 case '3':
01597 message_exists = 0;
01598
01599 ast_verb(3, "R%secording\n", recorded == 1 ? "e-r" : "");
01600 recorded = 1;
01601 if ((cmd = ast_play_and_record(chan, playfile, recordfile, maxtime, fmt, duration, NULL, silencethreshold, maxsilence, path)) == -1) {
01602
01603 return cmd;
01604 }
01605 if (cmd == '0') {
01606 break;
01607 } else if (cmd == '*') {
01608 break;
01609 } else {
01610
01611 message_exists = 1;
01612 cmd = 0;
01613 }
01614 break;
01615 case '4':
01616 case '5':
01617 case '6':
01618 case '7':
01619 case '8':
01620 case '9':
01621 case '*':
01622 case '#':
01623 cmd = ast_play_and_wait(chan, "vm-sorry");
01624 break;
01625 default:
01626 if (message_exists) {
01627 cmd = ast_play_and_wait(chan, "vm-review");
01628 } else {
01629 if (!(cmd = ast_play_and_wait(chan, "vm-torerecord"))) {
01630 cmd = ast_waitfordigit(chan, 600);
01631 }
01632 }
01633
01634 if (!cmd) {
01635 cmd = ast_waitfordigit(chan, 6000);
01636 }
01637 if (!cmd) {
01638 attempts++;
01639 }
01640 if (attempts > max_attempts) {
01641 cmd = 't';
01642 }
01643 }
01644 }
01645 if (cmd == 't') {
01646 cmd = 0;
01647 }
01648 return cmd;
01649 }
01650
01651 #define RES_UPONE (1 << 16)
01652 #define RES_EXIT (1 << 17)
01653 #define RES_REPEAT (1 << 18)
01654 #define RES_RESTART ((1 << 19) | RES_REPEAT)
01655
01656 static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata);
01657
01658 static int ivr_dispatch(struct ast_channel *chan, struct ast_ivr_option *option, char *exten, void *cbdata)
01659 {
01660 int res;
01661 int (*ivr_func)(struct ast_channel *, void *);
01662 char *c;
01663 char *n;
01664
01665 switch (option->action) {
01666 case AST_ACTION_UPONE:
01667 return RES_UPONE;
01668 case AST_ACTION_EXIT:
01669 return RES_EXIT | (((unsigned long)(option->adata)) & 0xffff);
01670 case AST_ACTION_REPEAT:
01671 return RES_REPEAT | (((unsigned long)(option->adata)) & 0xffff);
01672 case AST_ACTION_RESTART:
01673 return RES_RESTART ;
01674 case AST_ACTION_NOOP:
01675 return 0;
01676 case AST_ACTION_BACKGROUND:
01677 res = ast_stream_and_wait(chan, (char *)option->adata, AST_DIGIT_ANY);
01678 if (res < 0) {
01679 ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata);
01680 res = 0;
01681 }
01682 return res;
01683 case AST_ACTION_PLAYBACK:
01684 res = ast_stream_and_wait(chan, (char *)option->adata, "");
01685 if (res < 0) {
01686 ast_log(LOG_NOTICE, "Unable to find file '%s'!\n", (char *)option->adata);
01687 res = 0;
01688 }
01689 return res;
01690 case AST_ACTION_MENU:
01691 if ((res = ast_ivr_menu_run_internal(chan, (struct ast_ivr_menu *)option->adata, cbdata)) == -2) {
01692
01693 res = 0;
01694 }
01695 return res;
01696 case AST_ACTION_WAITOPTION:
01697 if (!(res = ast_waitfordigit(chan, chan->pbx ? chan->pbx->rtimeoutms : 10000))) {
01698 return 't';
01699 }
01700 return res;
01701 case AST_ACTION_CALLBACK:
01702 ivr_func = option->adata;
01703 res = ivr_func(chan, cbdata);
01704 return res;
01705 case AST_ACTION_TRANSFER:
01706 res = ast_parseable_goto(chan, option->adata);
01707 return 0;
01708 case AST_ACTION_PLAYLIST:
01709 case AST_ACTION_BACKLIST:
01710 res = 0;
01711 c = ast_strdupa(option->adata);
01712 while ((n = strsep(&c, ";"))) {
01713 if ((res = ast_stream_and_wait(chan, n,
01714 (option->action == AST_ACTION_BACKLIST) ? AST_DIGIT_ANY : ""))) {
01715 break;
01716 }
01717 }
01718 ast_stopstream(chan);
01719 return res;
01720 default:
01721 ast_log(LOG_NOTICE, "Unknown dispatch function %d, ignoring!\n", option->action);
01722 return 0;
01723 }
01724 return -1;
01725 }
01726
01727 static int option_exists(struct ast_ivr_menu *menu, char *option)
01728 {
01729 int x;
01730 for (x = 0; menu->options[x].option; x++) {
01731 if (!strcasecmp(menu->options[x].option, option)) {
01732 return x;
01733 }
01734 }
01735 return -1;
01736 }
01737
01738 static int option_matchmore(struct ast_ivr_menu *menu, char *option)
01739 {
01740 int x;
01741 for (x = 0; menu->options[x].option; x++) {
01742 if ((!strncasecmp(menu->options[x].option, option, strlen(option))) &&
01743 (menu->options[x].option[strlen(option)])) {
01744 return x;
01745 }
01746 }
01747 return -1;
01748 }
01749
01750 static int read_newoption(struct ast_channel *chan, struct ast_ivr_menu *menu, char *exten, int maxexten)
01751 {
01752 int res = 0;
01753 int ms;
01754 while (option_matchmore(menu, exten)) {
01755 ms = chan->pbx ? chan->pbx->dtimeoutms : 5000;
01756 if (strlen(exten) >= maxexten - 1) {
01757 break;
01758 }
01759 if ((res = ast_waitfordigit(chan, ms)) < 1) {
01760 break;
01761 }
01762 exten[strlen(exten) + 1] = '\0';
01763 exten[strlen(exten)] = res;
01764 }
01765 return res > 0 ? 0 : res;
01766 }
01767
01768 static int ast_ivr_menu_run_internal(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata)
01769 {
01770
01771 int res = 0;
01772 int pos = 0;
01773 int retries = 0;
01774 char exten[AST_MAX_EXTENSION] = "s";
01775 if (option_exists(menu, "s") < 0) {
01776 strcpy(exten, "g");
01777 if (option_exists(menu, "g") < 0) {
01778 ast_log(LOG_WARNING, "No 's' nor 'g' extension in menu '%s'!\n", menu->title);
01779 return -1;
01780 }
01781 }
01782 while (!res) {
01783 while (menu->options[pos].option) {
01784 if (!strcasecmp(menu->options[pos].option, exten)) {
01785 res = ivr_dispatch(chan, menu->options + pos, exten, cbdata);
01786 ast_debug(1, "IVR Dispatch of '%s' (pos %d) yields %d\n", exten, pos, res);
01787 if (res < 0) {
01788 break;
01789 } else if (res & RES_UPONE) {
01790 return 0;
01791 } else if (res & RES_EXIT) {
01792 return res;
01793 } else if (res & RES_REPEAT) {
01794 int maxretries = res & 0xffff;
01795 if ((res & RES_RESTART) == RES_RESTART) {
01796 retries = 0;
01797 } else {
01798 retries++;
01799 }
01800 if (!maxretries) {
01801 maxretries = 3;
01802 }
01803 if ((maxretries > 0) && (retries >= maxretries)) {
01804 ast_debug(1, "Max retries %d exceeded\n", maxretries);
01805 return -2;
01806 } else {
01807 if (option_exists(menu, "g") > -1) {
01808 strcpy(exten, "g");
01809 } else if (option_exists(menu, "s") > -1) {
01810 strcpy(exten, "s");
01811 }
01812 }
01813 pos = 0;
01814 continue;
01815 } else if (res && strchr(AST_DIGIT_ANY, res)) {
01816 ast_debug(1, "Got start of extension, %c\n", res);
01817 exten[1] = '\0';
01818 exten[0] = res;
01819 if ((res = read_newoption(chan, menu, exten, sizeof(exten)))) {
01820 break;
01821 }
01822 if (option_exists(menu, exten) < 0) {
01823 if (option_exists(menu, "i")) {
01824 ast_debug(1, "Invalid extension entered, going to 'i'!\n");
01825 strcpy(exten, "i");
01826 pos = 0;
01827 continue;
01828 } else {
01829 ast_debug(1, "Aborting on invalid entry, with no 'i' option!\n");
01830 res = -2;
01831 break;
01832 }
01833 } else {
01834 ast_debug(1, "New existing extension: %s\n", exten);
01835 pos = 0;
01836 continue;
01837 }
01838 }
01839 }
01840 pos++;
01841 }
01842 ast_debug(1, "Stopping option '%s', res is %d\n", exten, res);
01843 pos = 0;
01844 if (!strcasecmp(exten, "s")) {
01845 strcpy(exten, "g");
01846 } else {
01847 break;
01848 }
01849 }
01850 return res;
01851 }
01852
01853 int ast_ivr_menu_run(struct ast_channel *chan, struct ast_ivr_menu *menu, void *cbdata)
01854 {
01855 int res = ast_ivr_menu_run_internal(chan, menu, cbdata);
01856
01857 return res > 0 ? 0 : res;
01858 }
01859
01860 char *ast_read_textfile(const char *filename)
01861 {
01862 int fd, count = 0, res;
01863 char *output = NULL;
01864 struct stat filesize;
01865
01866 if (stat(filename, &filesize) == -1) {
01867 ast_log(LOG_WARNING, "Error can't stat %s\n", filename);
01868 return NULL;
01869 }
01870
01871 count = filesize.st_size + 1;
01872
01873 if ((fd = open(filename, O_RDONLY)) < 0) {
01874 ast_log(LOG_WARNING, "Cannot open file '%s' for reading: %s\n", filename, strerror(errno));
01875 return NULL;
01876 }
01877
01878 if ((output = ast_malloc(count))) {
01879 res = read(fd, output, count - 1);
01880 if (res == count - 1) {
01881 output[res] = '\0';
01882 } else {
01883 ast_log(LOG_WARNING, "Short read of %s (%d of %d): %s\n", filename, res, count - 1, strerror(errno));
01884 ast_free(output);
01885 output = NULL;
01886 }
01887 }
01888
01889 close(fd);
01890
01891 return output;
01892 }
01893
01894 static int parse_options(const struct ast_app_option *options, void *_flags, char **args, char *optstr, int flaglen)
01895 {
01896 char *s, *arg;
01897 int curarg, res = 0;
01898 unsigned int argloc;
01899 struct ast_flags *flags = _flags;
01900 struct ast_flags64 *flags64 = _flags;
01901
01902 if (flaglen == 32) {
01903 ast_clear_flag(flags, AST_FLAGS_ALL);
01904 } else {
01905 flags64->flags = 0;
01906 }
01907
01908 if (!optstr) {
01909 return 0;
01910 }
01911
01912 s = optstr;
01913 while (*s) {
01914 curarg = *s++ & 0x7f;
01915 argloc = options[curarg].arg_index;
01916 if (*s == '(') {
01917 int paren = 1, quote = 0;
01918 int parsequotes = (s[1] == '"') ? 1 : 0;
01919
01920
01921 arg = ++s;
01922 for (; *s; s++) {
01923 if (*s == '(' && !quote) {
01924 paren++;
01925 } else if (*s == ')' && !quote) {
01926
01927 paren--;
01928 } else if (*s == '"' && parsequotes) {
01929
01930 quote = quote ? 0 : 1;
01931 ast_copy_string(s, s + 1, INT_MAX);
01932 s--;
01933 } else if (*s == '\\') {
01934 if (!quote) {
01935
01936 ast_copy_string(s, s + 1, INT_MAX);
01937 } else if (quote && s[1] == '"') {
01938
01939 ast_copy_string(s, s + 1, INT_MAX);
01940 } else {
01941
01942 s++;
01943 }
01944 }
01945
01946 if (paren == 0) {
01947 break;
01948 }
01949 }
01950
01951 if ((s = strchr(s, ')'))) {
01952 if (argloc) {
01953 args[argloc - 1] = arg;
01954 }
01955 *s++ = '\0';
01956 } else {
01957 ast_log(LOG_WARNING, "Missing closing parenthesis for argument '%c' in string '%s'\n", curarg, arg);
01958 res = -1;
01959 break;
01960 }
01961 } else if (argloc) {
01962 args[argloc - 1] = "";
01963 }
01964 if (flaglen == 32) {
01965 ast_set_flag(flags, options[curarg].flag);
01966 } else {
01967 ast_set_flag64(flags64, options[curarg].flag);
01968 }
01969 }
01970
01971 return res;
01972 }
01973
01974 int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
01975 {
01976 return parse_options(options, flags, args, optstr, 32);
01977 }
01978
01979 int ast_app_parse_options64(const struct ast_app_option *options, struct ast_flags64 *flags, char **args, char *optstr)
01980 {
01981 return parse_options(options, flags, args, optstr, 64);
01982 }
01983
01984 void ast_app_options2str64(const struct ast_app_option *options, struct ast_flags64 *flags, char *buf, size_t len)
01985 {
01986 unsigned int i, found = 0;
01987 for (i = 32; i < 128 && found < len; i++) {
01988 if (ast_test_flag64(flags, options[i].flag)) {
01989 buf[found++] = i;
01990 }
01991 }
01992 buf[found] = '\0';
01993 }
01994
01995 int ast_get_encoded_char(const char *stream, char *result, size_t *consumed)
01996 {
01997 int i;
01998 *consumed = 1;
01999 *result = 0;
02000 if (ast_strlen_zero(stream)) {
02001 *consumed = 0;
02002 return -1;
02003 }
02004
02005 if (*stream == '\\') {
02006 *consumed = 2;
02007 switch (*(stream + 1)) {
02008 case 'n':
02009 *result = '\n';
02010 break;
02011 case 'r':
02012 *result = '\r';
02013 break;
02014 case 't':
02015 *result = '\t';
02016 break;
02017 case 'x':
02018
02019 if (strchr("0123456789ABCDEFabcdef", *(stream + 2)) && *(stream + 2) != '\0') {
02020 *consumed = 3;
02021 if (*(stream + 2) <= '9') {
02022 *result = *(stream + 2) - '0';
02023 } else if (*(stream + 2) <= 'F') {
02024 *result = *(stream + 2) - 'A' + 10;
02025 } else {
02026 *result = *(stream + 2) - 'a' + 10;
02027 }
02028 } else {
02029 ast_log(LOG_ERROR, "Illegal character '%c' in hexadecimal string\n", *(stream + 2));
02030 return -1;
02031 }
02032
02033 if (strchr("0123456789ABCDEFabcdef", *(stream + 3)) && *(stream + 3) != '\0') {
02034 *consumed = 4;
02035 *result <<= 4;
02036 if (*(stream + 3) <= '9') {
02037 *result += *(stream + 3) - '0';
02038 } else if (*(stream + 3) <= 'F') {
02039 *result += *(stream + 3) - 'A' + 10;
02040 } else {
02041 *result += *(stream + 3) - 'a' + 10;
02042 }
02043 }
02044 break;
02045 case '0':
02046
02047 *consumed = 2;
02048 for (i = 2; ; i++) {
02049 if (strchr("01234567", *(stream + i)) && *(stream + i) != '\0') {
02050 (*consumed)++;
02051 ast_debug(5, "result was %d, ", *result);
02052 *result <<= 3;
02053 *result += *(stream + i) - '0';
02054 ast_debug(5, "is now %d\n", *result);
02055 } else {
02056 break;
02057 }
02058 }
02059 break;
02060 default:
02061 *result = *(stream + 1);
02062 }
02063 } else {
02064 *result = *stream;
02065 *consumed = 1;
02066 }
02067 return 0;
02068 }
02069
02070 char *ast_get_encoded_str(const char *stream, char *result, size_t result_size)
02071 {
02072 char *cur = result;
02073 size_t consumed;
02074
02075 while (cur < result + result_size - 1 && !ast_get_encoded_char(stream, cur, &consumed)) {
02076 cur++;
02077 stream += consumed;
02078 }
02079 *cur = '\0';
02080 return result;
02081 }
02082
02083 int ast_str_get_encoded_str(struct ast_str **str, int maxlen, const char *stream)
02084 {
02085 char next, *buf;
02086 size_t offset = 0;
02087 size_t consumed;
02088
02089 if (strchr(stream, '\\')) {
02090 while (!ast_get_encoded_char(stream, &next, &consumed)) {
02091 if (offset + 2 > ast_str_size(*str) && maxlen > -1) {
02092 ast_str_make_space(str, maxlen > 0 ? maxlen : (ast_str_size(*str) + 48) * 2 - 48);
02093 }
02094 if (offset + 2 > ast_str_size(*str)) {
02095 break;
02096 }
02097 buf = ast_str_buffer(*str);
02098 buf[offset++] = next;
02099 stream += consumed;
02100 }
02101 buf = ast_str_buffer(*str);
02102 buf[offset++] = '\0';
02103 ast_str_update(*str);
02104 } else {
02105 ast_str_set(str, maxlen, "%s", stream);
02106 }
02107 return 0;
02108 }
02109
02110 void ast_close_fds_above_n(int n)
02111 {
02112 closefrom(n + 1);
02113 }
02114
02115 int ast_safe_fork(int stop_reaper)
02116 {
02117 sigset_t signal_set, old_set;
02118 int pid;
02119
02120
02121 if (stop_reaper) {
02122 ast_replace_sigchld();
02123 }
02124
02125 sigfillset(&signal_set);
02126 pthread_sigmask(SIG_BLOCK, &signal_set, &old_set);
02127
02128 pid = fork();
02129
02130 if (pid != 0) {
02131
02132 pthread_sigmask(SIG_SETMASK, &old_set, NULL);
02133 if (!stop_reaper && pid > 0) {
02134 struct zombie *cur = ast_calloc(1, sizeof(*cur));
02135 if (cur) {
02136 cur->pid = pid;
02137 AST_LIST_LOCK(&zombies);
02138 AST_LIST_INSERT_TAIL(&zombies, cur, list);
02139 AST_LIST_UNLOCK(&zombies);
02140 if (shaun_of_the_dead_thread == AST_PTHREADT_NULL) {
02141 if (ast_pthread_create_background(&shaun_of_the_dead_thread, NULL, shaun_of_the_dead, NULL)) {
02142 ast_log(LOG_ERROR, "Shaun of the Dead wants to kill zombies, but can't?!!\n");
02143 shaun_of_the_dead_thread = AST_PTHREADT_NULL;
02144 }
02145 }
02146 }
02147 }
02148 return pid;
02149 } else {
02150
02151 #ifdef HAVE_CAP
02152 cap_t cap = cap_from_text("cap_net_admin-eip");
02153
02154 if (cap_set_proc(cap)) {
02155 ast_log(LOG_WARNING, "Unable to remove capabilities.\n");
02156 }
02157 cap_free(cap);
02158 #endif
02159
02160
02161 signal(SIGHUP, SIG_DFL);
02162 signal(SIGCHLD, SIG_DFL);
02163 signal(SIGINT, SIG_DFL);
02164 signal(SIGURG, SIG_DFL);
02165 signal(SIGTERM, SIG_DFL);
02166 signal(SIGPIPE, SIG_DFL);
02167 signal(SIGXFSZ, SIG_DFL);
02168
02169
02170 if (pthread_sigmask(SIG_UNBLOCK, &signal_set, NULL)) {
02171 ast_log(LOG_WARNING, "unable to unblock signals: %s\n", strerror(errno));
02172 _exit(1);
02173 }
02174
02175 return pid;
02176 }
02177 }
02178
02179 void ast_safe_fork_cleanup(void)
02180 {
02181 ast_unreplace_sigchld();
02182 }
02183
02184 int ast_app_parse_timelen(const char *timestr, int *result, enum ast_timelen unit)
02185 {
02186 int res;
02187 char u[10];
02188 #ifdef HAVE_LONG_DOUBLE_WIDER
02189 long double amount;
02190 #define FMT "%30Lf%9s"
02191 #else
02192 double amount;
02193 #define FMT "%30lf%9s"
02194 #endif
02195 if (!timestr) {
02196 return -1;
02197 }
02198
02199 if ((res = sscanf(timestr, FMT, &amount, u)) == 0) {
02200 #undef FMT
02201 return -1;
02202 } else if (res == 2) {
02203 switch (u[0]) {
02204 case 'h':
02205 case 'H':
02206 unit = TIMELEN_HOURS;
02207 break;
02208 case 's':
02209 case 'S':
02210 unit = TIMELEN_SECONDS;
02211 break;
02212 case 'm':
02213 case 'M':
02214 if (toupper(u[1]) == 'S') {
02215 unit = TIMELEN_MILLISECONDS;
02216 } else if (u[1] == '\0') {
02217 unit = TIMELEN_MINUTES;
02218 }
02219 break;
02220 }
02221 }
02222
02223 switch (unit) {
02224 case TIMELEN_HOURS:
02225 amount *= 60;
02226
02227 case TIMELEN_MINUTES:
02228 amount *= 60;
02229
02230 case TIMELEN_SECONDS:
02231 amount *= 1000;
02232
02233 case TIMELEN_MILLISECONDS:
02234 ;
02235 }
02236 *result = amount > INT_MAX ? INT_MAX : (int) amount;
02237 return 0;
02238 }
02239