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 #include "asterisk.h"
00026
00027 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 211569 $")
00028
00029 #include <sys/stat.h>
00030 #include <time.h>
00031 #include <utime.h>
00032 #include <dirent.h>
00033
00034 #include "asterisk/paths.h"
00035 #include "asterisk/lock.h"
00036 #include "asterisk/file.h"
00037 #include "asterisk/logger.h"
00038 #include "asterisk/channel.h"
00039 #include "asterisk/callerid.h"
00040 #include "asterisk/pbx.h"
00041 #include "asterisk/module.h"
00042 #include "asterisk/utils.h"
00043 #include "asterisk/options.h"
00044
00045
00046
00047
00048
00049
00050 enum {
00051
00052
00053
00054
00055 SPOOL_FLAG_ALWAYS_DELETE = (1 << 0),
00056
00057 SPOOL_FLAG_ARCHIVE = (1 << 1)
00058 };
00059
00060 static char qdir[255];
00061 static char qdonedir[255];
00062
00063 struct outgoing {
00064 char fn[256];
00065 int retries;
00066 int maxretries;
00067 int retrytime;
00068 int waittime;
00069 long callingpid;
00070 int format;
00071
00072 char tech[256];
00073 char dest[256];
00074
00075 char app[256];
00076 char data[256];
00077
00078 char exten[AST_MAX_EXTENSION];
00079 char context[AST_MAX_CONTEXT];
00080 int priority;
00081
00082 char cid_num[256];
00083 char cid_name[256];
00084
00085 char account[AST_MAX_ACCOUNT_CODE];
00086
00087 struct ast_variable *vars;
00088
00089 int maxlen;
00090
00091 struct ast_flags options;
00092 };
00093
00094 static void init_outgoing(struct outgoing *o)
00095 {
00096 o->priority = 1;
00097 o->retrytime = 300;
00098 o->waittime = 45;
00099 o->format = AST_FORMAT_SLINEAR;
00100 ast_set_flag(&o->options, SPOOL_FLAG_ALWAYS_DELETE);
00101 }
00102
00103 static void free_outgoing(struct outgoing *o)
00104 {
00105 if (o->vars) {
00106 ast_variables_destroy(o->vars);
00107 }
00108 ast_free(o);
00109 }
00110
00111 static int apply_outgoing(struct outgoing *o, char *fn, FILE *f)
00112 {
00113 char buf[256];
00114 char *c, *c2;
00115 int lineno = 0;
00116 struct ast_variable *var, *last = o->vars;
00117
00118 while (last && last->next) {
00119 last = last->next;
00120 }
00121
00122 while(fgets(buf, sizeof(buf), f)) {
00123 lineno++;
00124
00125 c = buf;
00126 while ((c = strchr(c, '#'))) {
00127 if ((c == buf) || (*(c-1) == ' ') || (*(c-1) == '\t'))
00128 *c = '\0';
00129 else
00130 c++;
00131 }
00132
00133 c = buf;
00134 while ((c = strchr(c, ';'))) {
00135 if ((c > buf) && (c[-1] == '\\')) {
00136 memmove(c - 1, c, strlen(c) + 1);
00137 c++;
00138 } else {
00139 *c = '\0';
00140 break;
00141 }
00142 }
00143
00144
00145 while(!ast_strlen_zero(buf) && buf[strlen(buf) - 1] < 33)
00146 buf[strlen(buf) - 1] = '\0';
00147 if (!ast_strlen_zero(buf)) {
00148 c = strchr(buf, ':');
00149 if (c) {
00150 *c = '\0';
00151 c++;
00152 while ((*c) && (*c < 33))
00153 c++;
00154 #if 0
00155 printf("'%s' is '%s' at line %d\n", buf, c, lineno);
00156 #endif
00157 if (!strcasecmp(buf, "channel")) {
00158 ast_copy_string(o->tech, c, sizeof(o->tech));
00159 if ((c2 = strchr(o->tech, '/'))) {
00160 *c2 = '\0';
00161 c2++;
00162 ast_copy_string(o->dest, c2, sizeof(o->dest));
00163 } else {
00164 ast_log(LOG_NOTICE, "Channel should be in form Tech/Dest at line %d of %s\n", lineno, fn);
00165 o->tech[0] = '\0';
00166 }
00167 } else if (!strcasecmp(buf, "callerid")) {
00168 ast_callerid_split(c, o->cid_name, sizeof(o->cid_name), o->cid_num, sizeof(o->cid_num));
00169 } else if (!strcasecmp(buf, "application")) {
00170 ast_copy_string(o->app, c, sizeof(o->app));
00171 } else if (!strcasecmp(buf, "data")) {
00172 ast_copy_string(o->data, c, sizeof(o->data));
00173 } else if (!strcasecmp(buf, "maxretries")) {
00174 if (sscanf(c, "%30d", &o->maxretries) != 1) {
00175 ast_log(LOG_WARNING, "Invalid max retries at line %d of %s\n", lineno, fn);
00176 o->maxretries = 0;
00177 }
00178 } else if (!strcasecmp(buf, "codecs")) {
00179 ast_parse_allow_disallow(NULL, &o->format, c, 1);
00180 } else if (!strcasecmp(buf, "context")) {
00181 ast_copy_string(o->context, c, sizeof(o->context));
00182 } else if (!strcasecmp(buf, "extension")) {
00183 ast_copy_string(o->exten, c, sizeof(o->exten));
00184 } else if (!strcasecmp(buf, "priority")) {
00185 if ((sscanf(c, "%30d", &o->priority) != 1) || (o->priority < 1)) {
00186 ast_log(LOG_WARNING, "Invalid priority at line %d of %s\n", lineno, fn);
00187 o->priority = 1;
00188 }
00189 } else if (!strcasecmp(buf, "retrytime")) {
00190 if ((sscanf(c, "%30d", &o->retrytime) != 1) || (o->retrytime < 1)) {
00191 ast_log(LOG_WARNING, "Invalid retrytime at line %d of %s\n", lineno, fn);
00192 o->retrytime = 300;
00193 }
00194 } else if (!strcasecmp(buf, "waittime")) {
00195 if ((sscanf(c, "%30d", &o->waittime) != 1) || (o->waittime < 1)) {
00196 ast_log(LOG_WARNING, "Invalid waittime at line %d of %s\n", lineno, fn);
00197 o->waittime = 45;
00198 }
00199 } else if (!strcasecmp(buf, "retry")) {
00200 o->retries++;
00201 } else if (!strcasecmp(buf, "startretry")) {
00202 if (sscanf(c, "%30ld", &o->callingpid) != 1) {
00203 ast_log(LOG_WARNING, "Unable to retrieve calling PID!\n");
00204 o->callingpid = 0;
00205 }
00206 } else if (!strcasecmp(buf, "endretry") || !strcasecmp(buf, "abortretry")) {
00207 o->callingpid = 0;
00208 o->retries++;
00209 } else if (!strcasecmp(buf, "delayedretry")) {
00210 } else if (!strcasecmp(buf, "setvar") || !strcasecmp(buf, "set")) {
00211 c2 = c;
00212 strsep(&c2, "=");
00213 if (c2) {
00214 var = ast_variable_new(c, c2, fn);
00215 if (var) {
00216
00217 if (last) {
00218 last->next = var;
00219 } else {
00220 o->vars = var;
00221 }
00222 last = var;
00223 }
00224 } else
00225 ast_log(LOG_WARNING, "Malformed \"%s\" argument. Should be \"%s: variable=value\"\n", buf, buf);
00226 } else if (!strcasecmp(buf, "account")) {
00227 ast_copy_string(o->account, c, sizeof(o->account));
00228 } else if (!strcasecmp(buf, "alwaysdelete")) {
00229 ast_set2_flag(&o->options, ast_true(c), SPOOL_FLAG_ALWAYS_DELETE);
00230 } else if (!strcasecmp(buf, "archive")) {
00231 ast_set2_flag(&o->options, ast_true(c), SPOOL_FLAG_ARCHIVE);
00232 } else {
00233 ast_log(LOG_WARNING, "Unknown keyword '%s' at line %d of %s\n", buf, lineno, fn);
00234 }
00235 } else
00236 ast_log(LOG_NOTICE, "Syntax error at line %d of %s\n", lineno, fn);
00237 }
00238 }
00239 ast_copy_string(o->fn, fn, sizeof(o->fn));
00240 if (ast_strlen_zero(o->tech) || ast_strlen_zero(o->dest) || (ast_strlen_zero(o->app) && ast_strlen_zero(o->exten))) {
00241 ast_log(LOG_WARNING, "At least one of app or extension must be specified, along with tech and dest in file %s\n", fn);
00242 return -1;
00243 }
00244 return 0;
00245 }
00246
00247 static void safe_append(struct outgoing *o, time_t now, char *s)
00248 {
00249 int fd;
00250 FILE *f;
00251 struct utimbuf tbuf;
00252
00253 if ((fd = open(o->fn, O_WRONLY | O_APPEND)) < 0)
00254 return;
00255
00256 if ((f = fdopen(fd, "a"))) {
00257 fprintf(f, "\n%s: %ld %d (%ld)\n", s, (long)ast_mainpid, o->retries, (long) now);
00258 fclose(f);
00259 } else
00260 close(fd);
00261
00262
00263 tbuf.actime = now;
00264 tbuf.modtime = now + o->retrytime;
00265 if (utime(o->fn, &tbuf))
00266 ast_log(LOG_WARNING, "Unable to set utime on %s: %s\n", o->fn, strerror(errno));
00267 }
00268
00269
00270
00271
00272
00273
00274
00275 static int remove_from_queue(struct outgoing *o, const char *status)
00276 {
00277 int fd;
00278 FILE *f;
00279 char newfn[256];
00280 const char *bname;
00281
00282 if (!ast_test_flag(&o->options, SPOOL_FLAG_ALWAYS_DELETE)) {
00283 struct stat current_file_status;
00284
00285 if (!stat(o->fn, ¤t_file_status)) {
00286 if (time(NULL) < current_file_status.st_mtime)
00287 return 0;
00288 }
00289 }
00290
00291 if (!ast_test_flag(&o->options, SPOOL_FLAG_ARCHIVE)) {
00292 unlink(o->fn);
00293 return 0;
00294 }
00295
00296 if (ast_mkdir(qdonedir, 0777)) {
00297 ast_log(LOG_WARNING, "Unable to create queue directory %s -- outgoing spool archiving disabled\n", qdonedir);
00298 unlink(o->fn);
00299 return -1;
00300 }
00301
00302 if ((fd = open(o->fn, O_WRONLY | O_APPEND))) {
00303 if ((f = fdopen(fd, "a"))) {
00304 fprintf(f, "Status: %s\n", status);
00305 fclose(f);
00306 } else
00307 close(fd);
00308 }
00309
00310 if (!(bname = strrchr(o->fn, '/')))
00311 bname = o->fn;
00312 else
00313 bname++;
00314 snprintf(newfn, sizeof(newfn), "%s/%s", qdonedir, bname);
00315
00316 unlink(newfn);
00317 if (rename(o->fn, newfn) != 0) {
00318 unlink(o->fn);
00319 return -1;
00320 } else
00321 return 0;
00322 }
00323
00324 static void *attempt_thread(void *data)
00325 {
00326 struct outgoing *o = data;
00327 int res, reason;
00328 if (!ast_strlen_zero(o->app)) {
00329 ast_verb(3, "Attempting call on %s/%s for application %s(%s) (Retry %d)\n", o->tech, o->dest, o->app, o->data, o->retries);
00330 res = ast_pbx_outgoing_app(o->tech, o->format, o->dest, o->waittime * 1000, o->app, o->data, &reason, 2 , o->cid_num, o->cid_name, o->vars, o->account, NULL);
00331 o->vars = NULL;
00332 } else {
00333 ast_verb(3, "Attempting call on %s/%s for %s@%s:%d (Retry %d)\n", o->tech, o->dest, o->exten, o->context,o->priority, o->retries);
00334 res = ast_pbx_outgoing_exten(o->tech, o->format, o->dest, o->waittime * 1000, o->context, o->exten, o->priority, &reason, 2 , o->cid_num, o->cid_name, o->vars, o->account, NULL);
00335 o->vars = NULL;
00336 }
00337 if (res) {
00338 ast_log(LOG_NOTICE, "Call failed to go through, reason (%d) %s\n", reason, ast_channel_reason2str(reason));
00339 if (o->retries >= o->maxretries + 1) {
00340
00341 ast_log(LOG_EVENT, "Queued call to %s/%s expired without completion after %d attempt%s\n", o->tech, o->dest, o->retries - 1, ((o->retries - 1) != 1) ? "s" : "");
00342 remove_from_queue(o, "Expired");
00343 } else {
00344
00345 safe_append(o, time(NULL), "EndRetry");
00346 }
00347 } else {
00348 ast_log(LOG_NOTICE, "Call completed to %s/%s\n", o->tech, o->dest);
00349 ast_log(LOG_EVENT, "Queued call to %s/%s completed\n", o->tech, o->dest);
00350 remove_from_queue(o, "Completed");
00351 }
00352 free_outgoing(o);
00353 return NULL;
00354 }
00355
00356 static void launch_service(struct outgoing *o)
00357 {
00358 pthread_t t;
00359 int ret;
00360
00361 if ((ret = ast_pthread_create_detached(&t, NULL, attempt_thread, o))) {
00362 ast_log(LOG_WARNING, "Unable to create thread :( (returned error: %d)\n", ret);
00363 free_outgoing(o);
00364 }
00365 }
00366
00367 static int scan_service(char *fn, time_t now, time_t atime)
00368 {
00369 struct outgoing *o = NULL;
00370 FILE *f;
00371 int res = 0;
00372
00373 if (!(o = ast_calloc(1, sizeof(*o)))) {
00374 ast_log(LOG_WARNING, "Out of memory ;(\n");
00375 return -1;
00376 }
00377
00378 init_outgoing(o);
00379
00380
00381 if (!(f = fopen(fn, "r+"))) {
00382 remove_from_queue(o, "Failed");
00383 free_outgoing(o);
00384 ast_log(LOG_WARNING, "Unable to open %s: %s, deleting\n", fn, strerror(errno));
00385 return -1;
00386 }
00387
00388
00389 if (apply_outgoing(o, fn, f)) {
00390 remove_from_queue(o, "Failed");
00391 free_outgoing(o);
00392 ast_log(LOG_WARNING, "Invalid file contents in %s, deleting\n", fn);
00393 fclose(f);
00394 return -1;
00395 }
00396
00397 #if 0
00398 printf("Filename: %s, Retries: %d, max: %d\n", fn, o->retries, o->maxretries);
00399 #endif
00400 fclose(f);
00401 if (o->retries <= o->maxretries) {
00402 now += o->retrytime;
00403 if (o->callingpid && (o->callingpid == ast_mainpid)) {
00404 safe_append(o, time(NULL), "DelayedRetry");
00405 ast_log(LOG_DEBUG, "Delaying retry since we're currently running '%s'\n", o->fn);
00406 free_outgoing(o);
00407 } else {
00408
00409 o->retries++;
00410
00411
00412 if (o->callingpid)
00413 safe_append(o, time(NULL), "AbortRetry");
00414
00415 safe_append(o, now, "StartRetry");
00416 launch_service(o);
00417 }
00418 res = now;
00419 } else {
00420 ast_log(LOG_EVENT, "Queued call to %s/%s expired without completion after %d attempt%s\n", o->tech, o->dest, o->retries - 1, ((o->retries - 1) != 1) ? "s" : "");
00421 remove_from_queue(o, "Expired");
00422 free_outgoing(o);
00423 }
00424
00425 return res;
00426 }
00427
00428 static void *scan_thread(void *unused)
00429 {
00430 struct stat st;
00431 DIR *dir;
00432 struct dirent *de;
00433 char fn[256];
00434 int res;
00435 time_t last = 0, next = 0, now;
00436 struct timespec ts = { .tv_sec = 1 };
00437
00438 while (!ast_fully_booted) {
00439 nanosleep(&ts, NULL);
00440 }
00441
00442 for(;;) {
00443
00444 nanosleep(&ts, NULL);
00445 time(&now);
00446
00447 if (stat(qdir, &st)) {
00448 ast_log(LOG_WARNING, "Unable to stat %s\n", qdir);
00449 continue;
00450 }
00451
00452
00453 if ((st.st_mtime == last) && (next && (next > now)))
00454 continue;
00455
00456 #if 0
00457 printf("atime: %ld, mtime: %ld, ctime: %ld\n", st.st_atime, st.st_mtime, st.st_ctime);
00458 printf("Ooh, something changed / timeout\n");
00459 #endif
00460 next = 0;
00461 last = st.st_mtime;
00462
00463 if (!(dir = opendir(qdir))) {
00464 ast_log(LOG_WARNING, "Unable to open directory %s: %s\n", qdir, strerror(errno));
00465 continue;
00466 }
00467
00468 while ((de = readdir(dir))) {
00469 snprintf(fn, sizeof(fn), "%s/%s", qdir, de->d_name);
00470 if (stat(fn, &st)) {
00471 ast_log(LOG_WARNING, "Unable to stat %s: %s\n", fn, strerror(errno));
00472 continue;
00473 }
00474 if (!S_ISREG(st.st_mode))
00475 continue;
00476 if (st.st_mtime <= now) {
00477 res = scan_service(fn, now, st.st_atime);
00478 if (res > 0) {
00479
00480 if (!next || (res < next)) {
00481 next = res;
00482 }
00483 } else if (res) {
00484 ast_log(LOG_WARNING, "Failed to scan service '%s'\n", fn);
00485 } else if (!next) {
00486
00487 next = st.st_mtime;
00488 }
00489 } else {
00490
00491 if (!next || (st.st_mtime < next))
00492 next = st.st_mtime;
00493 }
00494 }
00495 closedir(dir);
00496 }
00497 return NULL;
00498 }
00499
00500 static int unload_module(void)
00501 {
00502 return -1;
00503 }
00504
00505 static int load_module(void)
00506 {
00507 pthread_t thread;
00508 int ret;
00509 snprintf(qdir, sizeof(qdir), "%s/%s", ast_config_AST_SPOOL_DIR, "outgoing");
00510 if (ast_mkdir(qdir, 0777)) {
00511 ast_log(LOG_WARNING, "Unable to create queue directory %s -- outgoing spool disabled\n", qdir);
00512 return AST_MODULE_LOAD_DECLINE;
00513 }
00514 snprintf(qdonedir, sizeof(qdir), "%s/%s", ast_config_AST_SPOOL_DIR, "outgoing_done");
00515
00516 if ((ret = ast_pthread_create_detached_background(&thread, NULL, scan_thread, NULL))) {
00517 ast_log(LOG_WARNING, "Unable to create thread :( (returned error: %d)\n", ret);
00518 return AST_MODULE_LOAD_FAILURE;
00519 }
00520
00521 return AST_MODULE_LOAD_SUCCESS;
00522 }
00523
00524 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Outgoing Spool Support");