489 lines
14 KiB
C
489 lines
14 KiB
C
|
/*
|
||
|
* The global queue runner, dispatches messages to email, mobile and to other proxies.
|
||
|
*/
|
||
|
|
||
|
#include <signal.h>
|
||
|
#include <ctype.h>
|
||
|
#include <unistd.h>
|
||
|
#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 <sendmail> <to> 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);
|
||
|
|
||
|
} 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); j<m; j++) {
|
||
|
MmsProxyRelay *mp = list_get(proxyrelays, j);
|
||
|
|
||
|
if (!octstr_compare(mp->host, 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);
|
||
|
|
||
|
octstr_destroy(pto);
|
||
|
|
||
|
return x;
|
||
|
}
|