/* * Mbuni - Open Source MMS Gateway * * MMS Relay, implements message routing * * Copyright (C) 2003 - 2005, Digital Solutions Ltd. - http://www.dsmagic.com * * Paul Bagyenda * * This program is free software, distributed under the terms of * the GNU General Public License, with a few exceptions granted (see LICENSE) */ #include #include #include #include "mms_queue.h" #include "mms_uaprof.h" #include "mms_util.h" #define NMAX 256 static char mobile_qdir[NMAX]; static char mm4_qdir[NMAX]; static char sendmail_cmd[NMAX]; /* Set the queue directory for messages going to mobile. */ static int mms_setmobile_queuedir(char *mqdir); /* Set the queue directory for messages going to remote proxies. */ static int mms_setmm4_queuedir(char *mqdir); /* Send command for sending mail. It will be called as with * headers and message on stdin. * * The following % formatting characters are allowed: * f - from address * t - recipient * s - subject * m - message id * */ static int mms_setsendmail_cmd(char *sendmail); /* Queue this message for delivery to mobile terminal. */ static int mms_sendtomobile(Octstr *from, Octstr *to, Octstr *subject, Octstr *fromproxy, Octstr *msgid, time_t expires, MmsMsg *m, int dlr, Octstr **error); /* Send this message via an intermediate proxy (MM4 interface). * The caller must modify the MmsMsg sender and recipient address if necessary. */ static int mms_sendtoproxy(Octstr *from, Octstr *to, Octstr *subject, Octstr *proxy, Octstr *msgid, time_t expires, MmsMsg *m, int dlr, Octstr **error); /* Send errors */ #define MMS_SEND_OK 0 #define MMS_SEND_ERROR_TRANSIENT -1 #define MMS_SEND_ERROR_FATAL -2 #define NMAX 256 static char qdir[NMAX]; static Cfg *cfg; static MmsBoxSettings *settings; static List *proxyrelays; static List *cdr_list; /* List for cdr as used by cdr consumer thread. */ static int rstop = 0; /* Set to 1 to stop queue runner. */ static int sendMsg(MmsEnvelope *e) { int i, n; MmsMsg *msg = NULL; if (!e->bill.billed) { /* Attempt to bill. */ List *l = list_create(); double amt; for (i = 0, n = list_len(e->to); i < n; i++) { MmsEnvelopeTo *to = list_get(e->to, i); list_append(l, to->rcpt); } amt = settings->mms_billfuncs->mms_billmsg(e->from, l, e->msize, settings->mms_bill_module_data); list_destroy(l, NULL); info(0, "Global Queue MMS Bill: From %s, to_count=%ld, msgid=%s, msgsize=%ld: returned=%.2f", octstr_get_cstr(e->from), list_len(e->to), e->msgId ? octstr_get_cstr(e->msgId) : "", e->msize, amt); if (amt == -1) { /* Delete message. */ for (i = 0, n = list_len(e->to); i < n; i++) { MmsEnvelopeTo *to = list_get(e->to, i); to->process = 0; } } else if (amt >= 0) { e->bill.billed = 1; e->bill.amt = amt; } if (amt >= -1) if (mms_queue_update(e) == 1) /* Write queue just in case we crash. */ e = NULL; if (e == NULL || !e->bill.billed) goto done2; /* If queue is gone, or we didn't manage to bill, go away */ } msg = mms_queue_getdata(e); #if 0 if (msg) mms_msgdump(msg,1); #endif for (i = 0, n = list_len(e->to); i < n; i++) { Octstr *err = NULL; int res = MMS_SEND_OK, m; MmsEnvelopeTo *to = list_get(e->to, i); time_t tnow = time(NULL); if (!to || !to->process) /* Already processed. */ continue; if (e->expiryt != 0 && /* Handle message expiry. */ e->expiryt < tnow) { err = octstr_format("MMSC error: Message expired while sending to %S!", to->rcpt); res = MMS_SEND_ERROR_FATAL; goto done; } else if (e->attempts >= settings->maxsendattempts) { err = octstr_format("MMSC error: Failed to deliver to %S after %ld attempts!", to->rcpt, e->attempts); res = MMS_SEND_ERROR_FATAL; goto done; } /* first check if it is an email address */ if (octstr_search_char(to->rcpt, '@', 0) > 0) { res = mms_sendtoemail(e->from, to->rcpt, e->subject, e->msgId, msg, e->dlr, &err, sendmail_cmd, settings->hostname, 1, 1, octstr_get_cstr(settings->mms_email_txt), octstr_get_cstr(settings->mms_email_html), 1); mms_log2("Sent", e->from, to->rcpt, -1, e->msgId, NULL, NULL, "MM3", NULL,NULL); } else if (e->viaproxy && octstr_len(e->viaproxy) > 0) /* If proxy to send through is already set, use it. */ res = mms_sendtoproxy(e->from, to->rcpt, e->subject, e->viaproxy, e->msgId, e->expiryt, msg, e->dlr, &err); else { int j = octstr_case_search(to->rcpt, octstr_imm("/TYPE=PLMN"), 0); int k = octstr_case_search(to->rcpt, octstr_imm("/TYPE=IPv"), 0); int len = octstr_len(to->rcpt); Octstr *phonenum = NULL; Octstr *mmsc; int sent = 0; if (j > 0 && j - 1 + sizeof "/TYPE=PLMN" == len) /* A proper number. */ phonenum = octstr_copy(to->rcpt, 0, j); else if (k > 0 && k + sizeof "/TYPE=IPv" == len) { res = mms_sendtomobile(e->from, to->rcpt, e->subject, e->fromproxy, e->msgId, e->expiryt, msg, e->dlr, &err); sent = 1; goto done; } else { /* We don't handle other types for now. */ err = octstr_format("MMSC error: Unsupported recipient type %S", to->rcpt); res = MMS_SEND_ERROR_FATAL; goto done; } /* Normalise the number, then match against local prefixes. */ normalize_number(octstr_get_cstr(settings->unified_prefix), &phonenum); if ((mmsc = settings->mms_resolvefuncs->mms_resolve(phonenum, settings->mms_resolver_module_data, settings, proxyrelays))) { info(0, "mmsc for \"%s\" resolved to: \"%s\"", octstr_get_cstr(phonenum), octstr_get_cstr(mmsc)); if (octstr_compare(mmsc, settings->hostname) == 0) { res = mms_sendtomobile(e->from, to->rcpt, e->subject, e->fromproxy, e->msgId, e->expiryt, msg, e->dlr, &err); sent = 1; } else if (proxyrelays && list_len(proxyrelays) > 0) /* Step through proxies. */ for (j = 0, m = list_len(proxyrelays); jhost, mmsc)) { res = mms_sendtoproxy(e->from, to->rcpt, e->subject, mp->host, e->msgId, e->expiryt, msg, e->dlr, &err); sent = 1; break; } } } if (!sent) { res = MMS_SEND_ERROR_FATAL; err = octstr_format("MMSC error: Don't know how to deliver to %S !", to->rcpt); } if (phonenum) octstr_destroy(phonenum); } done: if (res == MMS_SEND_OK) to->process = 0; else { /* If there was a report request, queue it. */ if (e->dlr) { MmsMsg *m = mms_deliveryreport(e->msgId, to->rcpt, tnow, (e->expiryt != 0 && e->expiryt < tnow) ? octstr_imm("Expired") : octstr_imm("Rejected")); List *l = list_create(); list_append(l, octstr_duplicate(e->from)); /* Add to queue, switch via proxy to be from proxy. */ mms_queue_add(settings->system_user, l, e->msgId, err, NULL, e->fromproxy, tnow, tnow+settings->default_msgexpiry, m, NULL, 0, qdir); list_destroy(l, NULL); mms_destroy(m); } if (res == MMS_SEND_ERROR_FATAL) to->process = 0; /* No more attempts. */ } /* Write to log */ info(0, "%s Global Queue MMS Send: From %s, to %s, msgsize=%ld: err=%s", SEND_ERROR_STR(res), octstr_get_cstr(e->from), octstr_get_cstr(to->rcpt), e->msize, err ? octstr_get_cstr(err) : "(null)"); if (res == MMS_SEND_OK) { /* Do CDR writing. */ MmsCdrStruct *cdr = gw_malloc(sizeof *cdr); cdr->module_data = settings->mms_bill_module_data; cdr->sdate = e->created; strncpy(cdr->from, octstr_get_cstr(e->from), sizeof cdr->from); strncpy(cdr->to, octstr_get_cstr(to->rcpt), sizeof cdr->to); strncpy(cdr->msgid, e->msgId ? octstr_get_cstr(e->msgId) : "", sizeof cdr->msgid); cdr->msg_size = e->msize; list_produce(cdr_list, cdr); /* Put it on list so sending thread sends it. */ } e->lasttry = tnow; e->attempts++; e->sendt = e->lasttry + settings->send_back_off * e->attempts; if (mms_queue_update(e) == 1) { e = NULL; break; /* Queue entry gone. */ } } done2: if (msg) mms_destroy(msg); if (e) mms_queue_free_env(e); return 1; /* Always deletes the queue entry. */ } static void quit_now(int notused) { rstop = 1; } static void cdr_thread(void *unused) { MmsCdrStruct *cdr; while ((cdr = list_consume(cdr_list)) != NULL) { settings->mms_billfuncs->mms_logcdr(cdr); /* We should probably write to log here... */ gw_free(cdr); } } int main(int argc, char *argv[]) { int cfidx; Octstr *fname; CfgGroup *grp; Octstr *log, *alog; long loglevel; mms_lib_init(); srandom(time(NULL)); cfidx = get_and_set_debugs(argc, argv, NULL); if (argv[cfidx] == NULL) fname = octstr_imm("mmsc.conf"); else fname = octstr_create(argv[cfidx]); cfg = cfg_create(fname); if (cfg_read(cfg) == -1) panic(0, "Couldn't read configuration from '%s'.", octstr_get_cstr(fname)); octstr_destroy(fname); info(0, "----------------------------------------"); info(0, " MMSC Global queue runner version %s starting", MMSC_VERSION); grp = cfg_get_single_group(cfg, octstr_imm("core")); log = cfg_get(grp, octstr_imm("log-file")); if (log != NULL) { if (cfg_get_integer(&loglevel, grp, octstr_imm("log-level")) == -1) loglevel = 0; log_open(octstr_get_cstr(log), loglevel, GW_NON_EXCL); octstr_destroy(log); } /* Get access log and open it. */ alog = cfg_get(grp, octstr_imm("access-log")); if (alog) { alog_open(octstr_get_cstr(alog), 1, 1); octstr_destroy(alog); } /* Load proxy relays. */ proxyrelays = mms_proxy_relays(cfg); /* Load settings. */ settings = mms_load_mmsbox_settings(cfg); if (!settings) panic(0, "No global MMSC configuration!"); mms_start_profile_engine(octstr_get_cstr(settings->ua_profile_cache_dir)); mms_setmobile_queuedir(octstr_get_cstr(settings->mm1_queuedir)); mms_setmm4_queuedir(octstr_get_cstr(settings->mm4_queuedir)); mms_setsendmail_cmd(octstr_get_cstr(settings->sendmail)); strncpy(qdir, octstr_get_cstr(settings->global_queuedir), sizeof qdir); /* Start the thread for CDR */ cdr_list = list_create(); list_add_producer(cdr_list); gwthread_create(cdr_thread, NULL); signal(SIGHUP, quit_now); signal(SIGTERM, quit_now); signal(SIGPIPE,SIG_IGN); /* Ignore pipe errors. They kill us sometimes for nothing*/ mms_queue_run(qdir, sendMsg, settings->queue_interval, settings->maxthreads, &rstop); mms_stop_profile_engine(); /* Stop profile stuff. */ sleep(2); list_remove_producer(cdr_list); /* Stop CDR thread. */ sleep(2); /* Wait for them to die. */ return 0; } static int mms_setmobile_queuedir(char *mqdir) { strncpy(mobile_qdir, mqdir, sizeof mobile_qdir); return 0; } static int mms_setmm4_queuedir(char *mqdir) { strncpy(mm4_qdir, mqdir, sizeof mm4_qdir); return 0; } static int mms_setsendmail_cmd(char *sendmail) { strncpy(sendmail_cmd, sendmail, -1 + sizeof sendmail_cmd); return 0; } /* * Queue this message for delivery to mobile terminal. * A queue thread will handle sending of notifications to phone. * When a deliver is received, another thread will remove the queue entry. */ int mms_sendtomobile(Octstr *from, Octstr *to, Octstr *subject, Octstr *fromproxy, Octstr *msgid, time_t expires, MmsMsg *m, int dlr, Octstr **error) { Octstr *ret, *x; List *l = list_create(); char tokenstr[128]; list_append(l, to); /* We generate a special token that will be added to message ID to make * stealing messages a bit harder. */ snprintf(tokenstr, -1 + sizeof tokenstr, "wx%ld", random() % 100); x = octstr_create(tokenstr); ret = mms_queue_add(from, l, msgid, subject, fromproxy, NULL, 0, expires, m, x, dlr, mobile_qdir); octstr_destroy(x); list_destroy(l, NULL); octstr_destroy(ret); if (ret == NULL) return MMS_SEND_ERROR_TRANSIENT; else return MMS_SEND_OK; } /* Send this message via an intermediate proxy (MM4 interface). * The way it works: We email the message to the proxy but we also keep a local copy in the queue * so we can handle delivery receipts and such. */ static int mms_sendtoproxy(Octstr *from, Octstr *to, Octstr *subject, Octstr *proxy, Octstr *msgid, time_t expires, MmsMsg *msg, int dlr, Octstr **error) { Octstr *pto; int x; if (!to || octstr_search_char(to, '@', 0) >= 0) { *error = octstr_format("Bad recipient address sent to MM4 interface, addresss is %S!", to); return MMS_SEND_ERROR_FATAL; } if (mms_messagetype(msg) == MMS_MSGTYPE_SEND_REQ) { /* Only queue these ones for future response. */ List *l = list_create(); Octstr *ret; list_append(l, to); ret = mms_queue_add(from, l, msgid, subject, NULL, proxy, 0, expires, msg,NULL, dlr, mm4_qdir); list_destroy(l, NULL); if (ret == NULL) { *error = octstr_format("MM4: Failed to queue message to %S for future tracking. ", to); return MMS_SEND_ERROR_TRANSIENT; } octstr_destroy(ret); } pto = octstr_format("%S@%S", to, proxy); x = mms_sendtoemail(from, pto, subject, msgid, msg, 0, error, sendmail_cmd, settings->hostname, 0, 0,NULL,NULL,0); mms_log2("Sent", from, to, -1, msgid, NULL, proxy, "MM4", NULL,NULL); octstr_destroy(pto); return x; }