1
0
Fork 0
mbuni/mbuni/mmsbox/mmsbox.c

1325 lines
40 KiB
C

/*
* Mbuni - Open Source MMS Gateway
*
* MMSBOX: MMS Content engine
*
* Copyright (C) 2003 - 2005, Digital Solutions Ltd. - http://www.dsmagic.com
*
* Paul Bagyenda <bagyenda@dsmagic.com>
*
* This program is free software, distributed under the terms of
* the GNU General Public License, with a few exceptions granted (see LICENSE)
*/
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include "mms_queue.h"
#include "mms_uaprof.h"
#include "mmsbox.h"
/* XXX warning, do not octstr_destroy strings in HTTPCGIVar struct. They are destroyed by the http module! */
int rstop = 0;
static void quit_now(int notused)
{
int i, n;
MmscGrp *mmc;
rstop = 1;
/* Close all MMSC http ports, kill all MMSC threads, kill sendmms port... */
if (sendmms_port.port > 0)
http_close_port(sendmms_port.port);
for (i = 0, n = gwlist_len(mmscs); i < n; i++)
if ((mmc = gwlist_get(mmscs, i)) != NULL &&
mmc->incoming.port > 0)
http_close_port(mmc->incoming.port);
}
/* Finds text part, returns copy. */
static MIMEEntity *find_textpart(MIMEEntity *m)
{
Octstr *ctype = NULL, *params = NULL;
MIMEEntity *res = NULL;
List *headers;
int i, n;
if (!m) return NULL;
headers = _x_mime_entity_headers(m);
get_content_type(headers, &ctype, &params);
http_destroy_headers(headers);
if (ctype && octstr_str_compare(ctype, "text/plain") == 0) {
res = mime_entity_duplicate(m);
goto done;
}
if ((n = mime_entity_num_parts(m)) > 0) {
for (i = 0; i < n; i++) {
MIMEEntity *x = mime_entity_get_part(m, i);
res = find_textpart(x);
mime_entity_destroy(x);
if (res != NULL)
goto done2;
}
}
done:
if (res) { /* We got it! Convert charset if needed. */
List *params_h = get_value_parameters(params);
Octstr *charset = http_header_value(params_h, octstr_imm("charset"));
Octstr *body = mime_entity_body(res);
if (charset == NULL ||
octstr_str_compare(charset, "unknown") == 0) {
if (charset) octstr_destroy(charset);
charset = octstr_imm(DEFAULT_CHARSET);
}
if (octstr_case_compare(charset, octstr_imm(DEFAULT_CHARSET)) != 0) {
charset_convert(body, DEFAULT_CHARSET, octstr_get_cstr(charset)); /* XXX error ignored? */
mime_entity_set_body(res, body);
}
octstr_destroy(body);
http_destroy_headers(params_h);
octstr_destroy(charset);
}
done2:
if (ctype)
octstr_destroy(ctype);
if (params)
octstr_destroy(params);
return res;
}
/* Gets the keyword, if any, from the text part of the message.
* converts charset as needed.
*/
static Octstr *get_keyword(MIMEEntity *me)
{
MIMEEntity *t = find_textpart(me);
Octstr *txt = t ? mime_entity_body(t) : NULL;
List *l = t ? octstr_split_words(txt) : NULL;
Octstr *keyword = l ? gwlist_get(l, 0) : NULL;
if (keyword)
keyword = octstr_duplicate(keyword);
if (l)
gwlist_destroy(l, (gwlist_item_destructor_t *)octstr_destroy);
if (txt)
octstr_destroy(txt);
if (t)
mime_entity_destroy(t);
return keyword;
}
static int _x_octstr_comp(Octstr *x, Octstr *y)
{
return (octstr_case_compare(x,y) == 0);
}
static MmsService *get_service(Octstr *keyword, Octstr *mmc_id)
{
int i, n;
MmsService *catch_all = NULL;
for (i = 0, n = gwlist_len(mms_services); i < n; i++) {
MmsService *ms = gwlist_get(mms_services,i);
/* Check that mmc_id is allowed:
* denied list is not null and we are on it, or allowed list is not null and we
* are *not* on it.
*/
if (ms->denied_mmscs &&
gwlist_search(ms->denied_mmscs, mmc_id, (gwlist_item_matches_t *)octstr_compare) != NULL)
continue;
if (ms->allowed_mmscs &&
gwlist_search(ms->allowed_mmscs, mmc_id, (gwlist_item_matches_t *)octstr_compare) == NULL)
continue;
if (keyword == NULL ||
gwlist_search(ms->keywords, keyword,
(gwlist_item_matches_t *)_x_octstr_comp) != NULL)
return ms;
if (ms->isdefault) /* We also find the catch-all for this sender. */
catch_all = ms;
}
return catch_all;
}
static void add_all_matching_parts(MIMEEntity *plist, MmsServiceUrlParam *pm,
MIMEEntity *me, MmsMsg *msg, int lev, int count)
{
int i, n;
List *headers = NULL;
Octstr *data = NULL, *ctype = NULL, *xctype = NULL, *params = NULL;
Octstr *s;
headers = _x_mime_entity_headers(me);
if (pm->type == WHOLE_BINARY && lev == 0) {
data = mms_tobinary(msg);
ctype = octstr_imm("application/vnd.wap.mms-message");
} else if (pm->type == NO_PART && lev == 0) {
data = octstr_create(""); /* We'll add value below. */
goto done;
}
if ((n = mime_entity_num_parts(me)) > 0) { /* Recurse over multi-parts. */
for (i = 0; i < n; i++) {
MIMEEntity *x = mime_entity_get_part(me,i);
add_all_matching_parts(plist, pm, x, msg, lev+1,i);
mime_entity_destroy(x);
}
goto done;
}
get_content_type(headers, &xctype, &params);
#define BEGINSWITH(s, prefix) (octstr_case_search(s, octstr_imm(prefix),0) == 0)
#define TYPE_MATCH(typ, prefix) ((pm->type) == (typ) && \
BEGINSWITH(xctype, prefix))
if (xctype)
if (TYPE_MATCH(IMAGE_PART,"image/") ||
TYPE_MATCH(AUDIO_PART,"audio/") ||
TYPE_MATCH(VIDEO_PART,"video/") ||
TYPE_MATCH(TEXT_PART,"text/") ||
TYPE_MATCH(SMIL_PART,"application/smil") ||
pm->type == ANY_PART || /* Skip top-level for 'any' parts. */
(pm->type == OTHER_PART &&
(!BEGINSWITH(xctype, "text/") &&
!BEGINSWITH(xctype, "video/") &&
!BEGINSWITH(xctype, "image/") &&
!BEGINSWITH(xctype, "application/smil")))) {
ctype = http_header_value(headers, octstr_imm("Content-Type"));
data = mime_entity_body(me);
}
done:
if (data) {
MIMEEntity *p = mime_entity_create();
Octstr *cd = octstr_format("form-data; name=\"%S\"", pm->name);
List *xh;
if (ctype) {
/* If Content-Location header or name (content-type) parameter given, pass it as filename. */
Octstr *c = NULL, *q = NULL;
Octstr *cloc = http_header_value(headers,
octstr_imm("Content-Location"));
split_header_value(ctype, &c, &q);
if (q || cloc) {
List *ph = q ? get_value_parameters(q) : http_create_empty_headers();
Octstr *v = cloc ? cloc : http_header_value(ph, octstr_imm("name"));
if (!v) /* make up a fake name if none is given */
v = octstr_format("part-%d-%d", lev, count);
octstr_format_append(cd, "; filename=\"%S\"", v);
http_header_remove_all(ph, "name");
if (v != cloc)
octstr_destroy(v);
octstr_destroy(ctype);
v = make_value_parameters(ph);
if (v && octstr_len(v) > 0)
ctype = octstr_format("%S; %S", c, v);
else
ctype = octstr_duplicate(c);
if (v)
octstr_destroy(v);
http_destroy_headers(ph);
if (q)
octstr_destroy(q);
}
if (c)
octstr_destroy(c);
if (cloc)
octstr_destroy(cloc);
}
xh = http_create_empty_headers();
http_header_add(xh, "Content-Disposition", octstr_get_cstr(cd));
if (ctype) /* This header must come after the above it seems. */
http_header_add(xh, "Content-Type", octstr_get_cstr(ctype));
mime_replace_headers(p, xh);
http_destroy_headers(xh);
s = octstr_duplicate(data); /* data for the parameter */
if (pm->value) /* add value part as needed. */
octstr_append(s, pm->value);
mime_entity_set_body(p, s);
octstr_destroy(s);
#if 0
base64_mimeparts(p);
#endif
mime_entity_add_part(plist, p); /* add it to list so far. */
mime_entity_destroy(p);
octstr_destroy(cd);
}
if (xctype)
octstr_destroy(xctype);
if (params)
octstr_destroy(params);
if (ctype)
octstr_destroy(ctype);
if (data)
octstr_destroy(data);
if (headers)
http_destroy_headers(headers);
}
enum _xurltype {FILE_TYPE, URL_TYPE};
static Octstr *url_path_prefix(Octstr *url, int type);
static Octstr *filename2content_type(char *fname);
static int make_and_queue_msg(Octstr *data, Octstr *ctype, List *reply_headers,
Octstr *base_url, int type, MmsEnvelope *e,
Octstr *svc_name, Octstr *faked_sender, Octstr *service_code,
int accept_x_headers,
List *passthro_headers,
Octstr **err);
static int fetch_serviceurl(MmsEnvelope *e,
MmsService *ms, MmsMsg *m,
MIMEEntity *msg,
Octstr **err)
{
List *rh, *rph = NULL;
Octstr *body = NULL, *rb = NULL;
Octstr *ctype = NULL, *params = NULL;
int i, n, method, typ = FILE_TYPE;
FILE *fp = NULL;
int res = -1;
switch (ms->type) {
case TRANS_TYPE_GET_URL:
case TRANS_TYPE_POST_URL:
rh = http_create_empty_headers();
http_header_add(rh, "User-Agent", MM_NAME "/" VERSION);
/* Put in some useful headers. */
if (e->msgId)
http_header_add(rh, "X-Mbuni-Message-ID", octstr_get_cstr(e->msgId));
if (e->fromproxy)
http_header_add(rh, "X-Mbuni-MMSC-ID", octstr_get_cstr(e->fromproxy));
if (e->from)
http_header_add(rh, "X-Mbuni-From", octstr_get_cstr(e->from));
if (e->subject)
http_header_add(rh, "X-Mbuni-Subject", octstr_get_cstr(e->subject));
for (i = 0, n = gwlist_len(e->to); i < n; i++) {
MmsEnvelopeTo *r = gwlist_get(e->to, i);
if (r && r->rcpt)
http_header_add(rh, "X-Mbuni-To", octstr_get_cstr(r->rcpt));
}
if (ms->type == TRANS_TYPE_POST_URL) { /* Put in the parameters. */
MIMEEntity *x = mime_entity_create();
http_header_add(rh, "Content-Type", "multipart/form-data");
mime_replace_headers(x, rh);
http_destroy_headers(rh);
for (i = 0, n = gwlist_len(ms->params); i < n; i++) {
MmsServiceUrlParam *p = gwlist_get(ms->params, i);
add_all_matching_parts(x, p, msg, m, 0, i);
}
body = mime_entity_body(x);
rh = _x_mime_entity_headers(x);
mime_entity_destroy(x);
method = HTTP_METHOD_POST;
} else
method = HTTP_METHOD_GET;
typ = URL_TYPE;
if (mmsbox_url_fetch_content(method, ms->url, rh, body, &rph, &rb) == HTTP_OK)
get_content_type(rph, &ctype, &params);
else
*err = octstr_format("MMSBox: Failed to fetch content for Service %S, url %S!",
ms->name, ms->url);
http_destroy_headers(rh);
if (body)
octstr_destroy(body);
break;
case TRANS_TYPE_FILE:
if ((fp = fopen(octstr_get_cstr(ms->url), "r")) != NULL) {
rb = octstr_read_pipe(fp);
fclose(fp);
ctype = filename2content_type(octstr_get_cstr(ms->url));
}
if (!rb)
*err = octstr_format("MMSBox: Failed to open file %S for service %S!",
ms->url, ms->name);
typ = FILE_TYPE;
break;
case TRANS_TYPE_EXEC:
if ((fp = popen(octstr_get_cstr(ms->url), "r+")) != NULL) {
Octstr *s = mime_entity_to_octstr(msg);
if (s) { /* Send the MMS to the exec program */
octstr_print(fp, s);
fflush(fp);
octstr_destroy(s);
}
rb = octstr_read_pipe(fp);
ctype = octstr_imm("application/smil");
pclose(fp);
}
if (!rb)
*err = octstr_format("MMSBox: Failed to fetch content for Service %S, exec path = %S!",
ms->name, ms->url);
typ = FILE_TYPE;
break;
case TRANS_TYPE_TEXT:
rb = octstr_duplicate(ms->url);
ctype = octstr_imm("text/plain");
typ = URL_TYPE;
break;
default:
*err = octstr_format(0, "MMSBOX: Unknown Service type for service %S!", ms->name);
break;
}
/* If we have the content, make the message and write it to out-going.
* if we have no content, or we have been told to suppress the reply, then we suppress
*/
if (!ctype || !rb ||
(octstr_len(rb) == 0 && ms->omitempty) ||
ms->noreply) {
info(0, "MMSBox.service_request: Suppressed reply for service %s, "
"suppress-reply=%s, omit-empty=%s, reply-len=%ld, content-type=%s",
octstr_get_cstr(ms->name), ms->noreply ? "true" : "false",
ms->omitempty ? "true" : "false",
rb ? octstr_len(rb) : 0,
ctype ? octstr_get_cstr(ctype) : "");
goto done;
} else {
Octstr *base_url = url_path_prefix(ms->url, typ);
res = make_and_queue_msg(rb, ctype, rph, base_url,
typ, e, ms->name, ms->faked_sender, ms->service_code,
ms->accept_x_headers, ms->passthro_headers,
err);
if (base_url)
octstr_destroy(base_url);
}
done:
if (ctype)
octstr_destroy(ctype);
if (rb)
octstr_destroy(rb);
if (rph)
http_destroy_headers(rph);
if (params)
octstr_destroy(params);
return res;
}
static int mmsbox_service_dispatch(MmsEnvelope *e)
{
MmsMsg *msg = NULL;
MIMEEntity *me = NULL;
int i, n, res = 0;
time_t tnow = time(NULL);
Octstr *err = NULL, *keyword = NULL;
MmsService *ms;
gw_assert(e->msgtype == MMS_MSGTYPE_SEND_REQ || e->msgtype == MMS_MSGTYPE_RETRIEVE_CONF);
if ((msg = mms_queue_getdata(e)) == NULL) {
err = octstr_format("Failed to read message for queue entry %s!",
e->xqfname);
res = -1;
goto done;
} else if (e->expiryt != 0 && /* Handle message expiry. */
e->expiryt < tnow) {
err = octstr_format("Queue entry [msgid=%S,mmc_id=%S] "
"expired while sending to service!", e->msgId, e->fromproxy);
res = -1;
goto done;
} else if (e->attempts >= mmsbox_maxsendattempts) {
err = octstr_format("Failed to deliver [msgid=%S,mmc_id=%S] "
"after %ld attempts. (max attempts allowed is %ld)!",
e->msgId, e->fromproxy, e->attempts,
mmsbox_maxsendattempts);
res = -1;
goto done;
}
me = mms_tomime(msg, 0);
keyword = get_keyword(me);
ms = get_service(keyword, e->fromproxy);
if (!ms) {
err = octstr_format("No Service to handle %S (keyword %S)!",
e->msgId, keyword ? octstr_imm("") : keyword);
res = -1;
goto done;
}
e->_x = ms; /* Remember it for later. */
res = fetch_serviceurl(e, ms, msg, me, &err);
done:
if (err) {
error(0, "MMSBox error: %s", octstr_get_cstr(err));
octstr_destroy(err);
}
if (res == -1 || res == 0) /* Fatal error, or success delete queue entry. */
for (i = 0, n = gwlist_len(e->to); i < n; i++) {
MmsEnvelopeTo *r = gwlist_get(e->to,i);
if (r)
r->process = 0;
}
else { /* Not succeeded so we need to wait a bit and try later. */
e->lasttry = time(NULL);
e->attempts++; /* Update count of number of delivery attempts. */
e->sendt = e->lasttry + mmsbox_send_back_off * e->attempts;
}
if (mms_queue_update(e) != 1)
mms_queue_free_env(e);
if (keyword)
octstr_destroy(keyword);
if (msg)
mms_destroy(msg);
if (me)
mime_entity_destroy(me);
return 1;
}
static void sendmms_func(void *unused);
int main(int argc, char *argv[])
{
Octstr *fname;
int cfidx;
mCfg *cfg;
long qthread = 0, sthread = 0;
mms_lib_init();
srandom(time(NULL));
cfidx = get_and_set_debugs(argc, argv, NULL);
if (argv[cfidx] == NULL)
fname = octstr_imm("mbuni.conf");
else
fname = octstr_create(argv[cfidx]);
cfg = mms_cfg_read(fname);
if (cfg == NULL)
panic(0, "Couldn't read configuration from '%s'.", octstr_get_cstr(fname));
octstr_destroy(fname);
info(0, "----------------------------------------");
info(0, " " MM_NAME " MMSBox version %s starting", MMSC_VERSION);
mms_load_mmsbox_settings(cfg,(gwthread_func_t *)mmsc_receive_func);
mms_cfg_destroy(cfg);
signal(SIGHUP, quit_now);
signal(SIGTERM, quit_now);
signal(SIGPIPE,SIG_IGN); /* Ignore pipe errors. They kill us sometimes for nothing*/
/* Start sendmms port */
if (sendmms_port.port > 0)
sthread = gwthread_create((gwthread_func_t *)sendmms_func, NULL);
/* Start out-going queue thread. */
qthread = gwthread_create((gwthread_func_t *)mmsbox_outgoing_queue_runner, &rstop);
mms_queue_run(octstr_get_cstr(incoming_qdir),
mmsbox_service_dispatch,
queue_interval, maxthreads, &rstop);
/* Wait for the sender thread, then quit. */
gwthread_join(qthread); /* Wait for it to die... */
if (sendmms_port.port > 0)
gwthread_join(sthread);
sleep(2);
mms_lib_shutdown();
return 0;
}
int mmsbox_url_fetch_content(int method, Octstr *url, List *request_headers,
Octstr *body, List **reply_headers, Octstr **reply_body)
{
int status = 0;
Octstr *furl = NULL;
if (octstr_search(url, octstr_imm("data:"), 0) == 0) {
int i = octstr_search_char(url, ',',0);
Octstr *ctype = (i >= 0) ? octstr_copy(url, 5, i-5) : octstr_create("text/plain; charset=us-ascii");
Octstr *data = (i >= 0) ? octstr_copy(url, i+1, octstr_len(url)) : octstr_duplicate(url);
Octstr *n = NULL, *h = NULL;
if (octstr_len(ctype) == 0)
octstr_append_cstr(ctype, "text/plain; charset=us-ascii");
split_header_value(ctype, &n, &h);
if (h) {
List *ph = get_value_parameters(h);
Octstr *v = NULL;
if (ph && (v = http_header_value(ph, octstr_imm("base64"))) != NULL) { /* has base64 item */
Octstr *p = NULL;
octstr_base64_to_binary(data);
http_header_remove_all(ph, "base64");
octstr_destroy(ctype);
if (gwlist_len(ph) > 0) {
p = make_value_parameters(ph);
ctype = octstr_format("%S; %S",
n,p);
octstr_destroy(p);
} else
ctype = octstr_format("%S", n);
}
if (ph)
http_destroy_headers(ph);
octstr_destroy(v);
octstr_destroy(h);
}
if (n)
octstr_destroy(n);
*reply_body = data;
*reply_headers = http_create_empty_headers();
http_header_add(*reply_headers, "Content-Type", octstr_get_cstr(ctype));
octstr_destroy(ctype);
status = HTTP_OK;
} else {
HTTPCaller *c = http_caller_create();
http_start_request(c, method, url, request_headers, body, 1, NULL, NULL);
if (http_receive_result_real(c, &status, &furl, reply_headers, reply_body,1) == NULL)
status = -1;
http_caller_destroy(c);
}
if (furl)
octstr_destroy(furl);
return status;
}
/* Mapping file extensions to content types. */
static struct {
char *ctype, *file_ext;
} exts[] = {
{"text/plain", "txt"},
{"image/jpeg", "jpg"},
{"image/jpeg", "jpeg"},
{"image/png", "png"},
{"image/tiff", "tiff"},
{"image/gif", "gif"},
{"image/bmp", "bmp"},
{"image/vnd.wap.wbmp", "wbmp"},
{"image/x-bmp", "bmp"},
{"image/x-wmf", "bmp"},
{"image/vnd.wap.wpng", "png"},
{"image/x-up-wpng", "png"},
{"audio/mpeg", "mp3"},
{"audio/wav", "wav"},
{"audio/x-wav", "wav"},
{"audio/basic", "au"},
{"audio/amr", "amr"},
{"audio/x-amr", "amr"},
{"audio/amr-wb", "amr"},
{"audio/midi", "mid"},
{"audio/sp-midi", "mid"},
{"application/smil", "smil"},
{NULL, NULL}
};
static Octstr *filename2content_type(char *fname)
{
char *p = strrchr(fname, '.');
int i;
if (p)
for (i = 0; exts[i].file_ext; i++)
if (strcasecmp(p+1, exts[i].file_ext) == 0)
return octstr_imm(exts[i].ctype);
return octstr_imm("application/octet-stream");
}
/* Return the prefix part of a url or file. */
static Octstr *url_path_prefix(Octstr *url, int type)
{
int i, j, len = octstr_len(url);
char *p = octstr_get_cstr(url);
/* Set lower/upper limit of search. */
if (type == URL_TYPE) { /* then skip first slashes. */
char *x;
i = octstr_search(url, octstr_imm("://"),0);
if (i > 0)
i += 3;
else
i = 0;
x = rindex(p, '#'); /* look for fragment if any. */
if (x)
j = x - p - 1;
else
j = len - 1;
} else {
i = 0;
j = len - 1;
}
/* Now search backwards for the last '/'.
* if you don't find one, set to end of string.
*/
for (;j > i; j--)
if (p[j] == '/')
break;
if (j <= i)
j = len;
return octstr_copy(url, 0, j);
}
/* Get's just the host:port part, leaving out UrI */
static Octstr *get_toplevel_url(Octstr *url)
{
int i, len = octstr_len(url);
char *p = octstr_get_cstr(url);
i = octstr_search(url, octstr_imm("://"),0);
if (i > 0)
i += 3;
else
i = 0;
for ( ; i < len; i++)
if (p[i] == '/')
break;
return octstr_copy(url, 0, i);
}
/* little dirty method to see if file begins with url scheme. */
static int has_url_scheme(char *url)
{
char *p = strstr(url, "://");
char *q = strstr(url, "data:"); /* data url scheme. */
if (q && q == url)
return 1;
if (!p)
return 0;
for (p -= 1; p >= url; p--)
if (!isalpha(*p))
return 0;
return 1;
}
static int add_msg_part(MIMEEntity *res, xmlNodePtr node, Octstr *base_url,
Octstr *top_url,
int type, Octstr *svc_name, Dict *url_map)
{
Octstr *curl = NULL, *ctype = NULL, *body = NULL;
char *src = NULL;
int isurl, slash_prefix;
static int cntr; /* For generating cids */
Octstr *cid = NULL;
/* For each node in the smil doc, if it has an src attribute, then:
* - if our type of base_url is FILE *and the src attribute does not look
* like a url, then file the file referenced, load it into the message and go
* - if our type is URL and the url scheme is http/https (or has no scheme)
* then fetch it and put into message.
*/
if (!node || node->type != XML_ELEMENT_NODE ||
(src = (char *)xmlGetProp(node, (unsigned char *)"src")) == NULL)
return 0; /* Nothing to do. */
if (src[0] == '\\') { /* User can escape url to prevent substitution. */
xmlSetProp(node, (xmlChar *)"src", (xmlChar *)(src + 1));
goto done;
}
isurl = has_url_scheme(src);
slash_prefix = (src[0] == '/');
if (isurl && strstr(src, "http") != src) /* Only http and https allowed! */
goto done;
if (isurl)
curl = octstr_create(src);
else if (slash_prefix) {
if (type == URL_TYPE)
curl = octstr_format("%S%s",
top_url, src);
else
curl = octstr_create(src);
} else
curl = octstr_format("%S/%s",base_url, src);
if ((cid = dict_get(url_map, curl)) != NULL) { /* We've seen it before. */
xmlSetProp(node, (xmlChar *)"src", (xmlChar *)octstr_get_cstr(cid));
/* Don't delete cid! */
goto done;
}
isurl |= (type == URL_TYPE); /* From now on, this flag tells us whether we are fetching a url.*/
if (isurl) {
List *rh = http_create_empty_headers(), *rph = NULL;
http_header_add(rh, "User-Agent", MM_NAME "/" VERSION);
if (mmsbox_url_fetch_content(HTTP_METHOD_GET, curl, rh, NULL, &rph, &body) == HTTP_OK)
ctype = http_header_value(rph, octstr_imm("Content-Type"));
else
error(0, "MMSBOX: Failed to load url %s within SMIL content from service %s!",
octstr_get_cstr(curl),
svc_name ? octstr_get_cstr(svc_name) : "unknown");
if (rph)
http_destroy_headers(rph);
http_destroy_headers(rh);
} else {
body = octstr_read_file(octstr_get_cstr(curl));
ctype = filename2content_type(src);
}
if (ctype && body) { /* If we got it, put it in. */
Octstr *attr = octstr_format("cid:%06d", ++cntr);
char *p = octstr_get_cstr(attr) + 4;
Octstr *cid_header_val = octstr_format("<%s>", p);
MIMEEntity *x = mime_entity_create();
List *headers = http_create_empty_headers();
http_header_add(headers, "Content-Type", octstr_get_cstr(ctype));
http_header_add(headers, "Content-ID", octstr_get_cstr(cid_header_val));
mime_replace_headers(x, headers);
mime_entity_set_body(x, body);
mime_entity_add_part(res, x);
mime_entity_destroy(x);
dict_put_once(url_map, curl, octstr_duplicate(attr)); /* Store the cid. */
xmlSetProp(node, (xmlChar *)"src", (xmlChar *)octstr_get_cstr(attr));
octstr_destroy(attr);
octstr_destroy(cid_header_val);
http_destroy_headers(headers);
}
done:
if (curl)
octstr_destroy(curl);
if (ctype)
octstr_destroy(ctype);
if (body)
octstr_destroy(body);
xmlFree(src);
return 0;
}
/* Traverse the tree doing the above. */
static void add_msg_parts(MIMEEntity *res, xmlNodePtr node, Octstr *base_url,
Octstr *top_url,
int type, Octstr *svc_name, Dict *url_map)
{
xmlNodePtr n;
/* Do all the children recursively, then come back and do parent. */
for (n = node; n; n = n->next)
if (n->type != XML_COMMENT_NODE) {
add_msg_part(res, n, base_url, top_url, type, svc_name, url_map);
add_msg_parts(res, n->xmlChildrenNode, base_url, top_url, type, svc_name, url_map);
}
}
/* Given content, make a message. We'll also use this for send-mms-user! */
static int make_and_queue_msg(Octstr *data, Octstr *ctype, List *reply_headers,
Octstr *base_url, int type, MmsEnvelope *e,
Octstr *svc_name, Octstr *faked_sender, Octstr *service_code,
int accept_x_headers, List *passthro_headers,
Octstr **err)
{
Octstr *from = NULL, *subject = NULL, *turl = get_toplevel_url(base_url);
Octstr *dlr_url = NULL, *rr_url = NULL, *mmc = NULL, *xservice_code = NULL;
MmsMsg *m = NULL;
MIMEEntity *me = mime_entity_create();
List *hdrs = NULL, *xheaders = NULL;
time_t expiryt = time(NULL) + DEFAULT_EXPIRE;
Octstr *x;
List *xto = gwlist_create();
int i, n, res = -1;
gw_assert(svc_name);
/* Get the from address. */
if (faked_sender)
from = octstr_duplicate(faked_sender);
else if (accept_x_headers && reply_headers &&
(from = http_header_value(reply_headers, octstr_imm("X-Mbuni-From"))) != NULL)
(void)0; /* all done above. */
else {
/* get first recipient, set that as sender. */
MmsEnvelopeTo *r = (e) ? gwlist_get(e->to, 0) : NULL;
if (r)
from = octstr_duplicate(r->rcpt);
else
from = octstr_imm("anon@anon");
}
/* Get service code. */
if (service_code)
xservice_code = octstr_duplicate(service_code);
else if (accept_x_headers && reply_headers)
xservice_code = http_header_value(reply_headers, octstr_imm("X-Mbuni-ServiceCode"));
if (from)
_mms_fixup_address(from);
/* start with the easy one... */
if (octstr_case_compare(ctype, octstr_imm("application/vnd.wap.mms-message")) == 0)
m = mms_frombinary(data, from);
else if (octstr_case_compare(ctype, octstr_imm("application/smil")) == 0) {
xmlDocPtr smil;
xmlChar *buf = NULL;
int bsize = 0;
Dict *url_map = dict_create(97, (void (*)(void *))octstr_destroy);
List *xh = http_create_empty_headers();
/* This is the hard bit: Fetch each external reference in smil, add it to message! */
http_header_add(xh, "Content-Type", "multipart/related; "
"type=\"application/smil\"; start=\"<presentation>\"");
mime_replace_headers(me,xh);
http_destroy_headers(xh);
/* Parse the smil as XML. */
smil = xmlParseMemory(octstr_get_cstr(data), octstr_len(data));
if (!smil || !smil->xmlChildrenNode) {
*err = octstr_format("MMSBox: Error parsing SMIL response from service[%s], "
" msgid %s!", octstr_get_cstr(svc_name),
(e && e->msgId) ? octstr_get_cstr(e->msgId) : "(none)");
dict_destroy(url_map);
goto done;
}
add_msg_parts(me, smil->xmlChildrenNode, base_url, turl, type, svc_name, url_map);
dict_destroy(url_map);
/* SMIL has been modified, convert it to text, put it in. */
xmlDocDumpFormatMemory(smil, &buf, &bsize, 1);
xmlFreeDoc(smil);
if (buf) {
MIMEEntity *sm = mime_entity_create();
List *xh = http_create_empty_headers();
Octstr *s;
http_header_add(xh, "Content-Type", "application/smil");
http_header_add(xh, "Content-ID", "<presentation>"); /* identify it as start element. */
s = octstr_create_from_data((char *)buf, bsize);
mime_replace_headers(sm, xh);
mime_entity_set_body(sm, s);
mime_entity_add_part(me, sm);
mime_entity_destroy(sm);
http_destroy_headers(xh);
octstr_destroy(s);
xmlFree(buf);
} else {
*err = octstr_format("MMSBox: Error writing converted SMIL "
"for response from service[%s], "
" msgid %s!",
octstr_get_cstr(svc_name),
(e && e->msgId) ? octstr_get_cstr(e->msgId) : "(none)");
goto done;
}
} else { /* all others, make the message as-is and hope for the best! */
List *xh = http_create_empty_headers();
http_header_add(xh, "Content-Type", octstr_get_cstr(ctype));
mime_replace_headers(me, xh);
http_destroy_headers(xh);
mime_entity_set_body(me, data);
}
/* Get headers needed, if we are allowed to do so. */
if (accept_x_headers && reply_headers) {
Octstr *x = NULL;
List *l = NULL;
subject = http_header_value(reply_headers, octstr_imm("X-Mbuni-Subject"));
if ((x = http_header_value(reply_headers, octstr_imm("X-Mbuni-Expiry"))) != NULL)
expiryt = date_parse_http(x);
if ((l = http_header_find_all(reply_headers, "X-Mbuni-To")) != NULL) {
int i, n;
for (i = 0, n = gwlist_len(l); i<n; i++) {
Octstr *h = NULL, *v = NULL;
List *hv = NULL;
int j;
http_header_get(l, i, &h, &v);
hv = http_header_split_value(v);
for (j = 0; j < gwlist_len(hv); j++) {
Octstr *v = gwlist_get(hv, j);
/* Fix the address. */
_mms_fixup_address(v);
gwlist_append(xto, v);
}
octstr_destroy(v);
octstr_destroy(h);
gwlist_destroy(hv, NULL); /* Don't kill strings since we added them to xto above! */
}
http_destroy_headers(l);
}
dlr_url = http_header_value(reply_headers, octstr_imm("X-Mbuni-DLR-Url"));
rr_url = http_header_value(reply_headers, octstr_imm("X-Mbuni-RR-Url"));
mmc = http_header_value(reply_headers, octstr_imm("X-Mbuni-MMSC"));
}
if (gwlist_len(xto) == 0 && e && e->from)
gwlist_append(xto, octstr_duplicate(e->from));
if (!subject && e && e->subject)
subject = octstr_duplicate(e->subject);
if (!mmc && e)
mmc = e->fromproxy;
/* Add some nice headers. */
xheaders = _x_mime_entity_headers(me);
http_header_add(xheaders, "From", octstr_get_cstr(from));
for (i = 0, n = gwlist_len(xto); i < n; i++) {
Octstr *v;
v = gwlist_get(xto, i);
http_header_add(xheaders, "To", octstr_get_cstr(v));
}
if (dlr_url)
http_header_add(xheaders, "X-Mms-Delivery-Report", "Yes");
if (rr_url)
http_header_add(xheaders, "X-Mms-Read-Report", "Yes");
if (subject)
http_header_add(xheaders, "Subject", octstr_get_cstr(subject));
if (expiryt > 0) {
Octstr *x = date_format_http(expiryt);
http_header_add(xheaders, "X-Mms-Expiry", octstr_get_cstr(x));
octstr_destroy(x);
}
mime_replace_headers(me, xheaders);
http_destroy_headers(xheaders);
if (me && !m) /* Set m if it hasn't been set yet. */
m = mms_frommime(me);
if (!m) {
*err = octstr_format("MMSBox: Failed to convert Mms Message from service %s!",
octstr_get_cstr(svc_name));
goto done;
}
if (passthro_headers && reply_headers) { /* if user wants passthro headers, get them and add them. */
int n = gwlist_len(reply_headers);
int i;
hdrs = http_create_empty_headers();
for (i = 0; i < n; i++) {
Octstr *h = NULL, *v = NULL;
int j;
http_header_get(reply_headers, i, &h, &v);
if (h == NULL || v == NULL)
goto loop;
/* Check for this header in
* pass thro list.
*/
for (j = 0; j < gwlist_len(passthro_headers); j++)
if (octstr_case_compare(h, gwlist_get(passthro_headers, j)) == 0) {
http_header_add(hdrs, octstr_get_cstr(h), octstr_get_cstr(v));
break;
}
loop:
if (h)
octstr_destroy(h);
if (v)
octstr_destroy(v);
}
}
/* Write to queue. */
x = mms_queue_add(from, xto, subject,
e ? e->fromproxy : NULL,
mmc,
time(NULL), expiryt, m, NULL,
xservice_code, /* Send service code as vasp id XXX - not very nice, but */
svc_name,
dlr_url, rr_url,
hdrs,
(dlr_url != NULL),
octstr_get_cstr(outgoing_qdir),
octstr_imm(MM_NAME));
info(0, "MMSBox: Queued message from service [%s] to outgoing: %s",
octstr_get_cstr(svc_name), octstr_get_cstr(x));
octstr_destroy(x);
res = 0;
done:
if (dlr_url)
octstr_destroy(dlr_url);
if (rr_url)
octstr_destroy(rr_url);
if (from)
octstr_destroy(from);
if (subject)
octstr_destroy(subject);
if (me)
mime_entity_destroy(me);
if (m)
mms_destroy(m);
if (xto)
gwlist_destroy(xto, (gwlist_item_destructor_t *)octstr_destroy);
if (hdrs)
http_destroy_headers(hdrs);
if (xservice_code)
octstr_destroy(xservice_code);
return res;
}
static SendMmsUser *auth_user(Octstr *user, Octstr *pass)
{
int i, n;
SendMmsUser *u = NULL;
if (!user || !pass)
return NULL;
for (i = 0, n = gwlist_len(sendmms_users); i < n; i++)
if ((u = gwlist_get(sendmms_users, i)) != NULL &&
octstr_compare(u->user, user) == 0 &&
octstr_compare(u->pass, pass) == 0)
return u;
return NULL;
}
static void sendmms_func(void *unused)
{
HTTPClient *client = NULL;
Octstr *ip = NULL, *url = NULL;
Octstr *body = NULL;
List *cgivars = NULL, *h = NULL;
while (!rstop &&
(client = http_accept_request(sendmms_port.port,
&ip, &url, &h, &body, &cgivars)) != NULL) {
SendMmsUser *u = NULL;
List *hh = http_create_empty_headers();
Octstr *username;
Octstr *password;
Octstr *err = NULL;
List *cgivar_ctypes = NULL;
int tparse = parse_cgivars(h, body, &cgivars, &cgivar_ctypes);
username = http_cgi_variable(cgivars, "username");
password = http_cgi_variable(cgivars, "password");
if ((u = auth_user(username, password)) != NULL &&
is_allowed_ip(sendmms_port.allow_ip, sendmms_port.deny_ip, ip)) {
Octstr *data, *ctype = NULL, *mmc, *to, *from, *dlr_url;
Octstr *rr_url, *subject = NULL;
List *lto = NULL, *rh = http_create_empty_headers();
Octstr *rb = NULL, *base_url;
int i, n, res = 0;
Octstr *vasid = http_cgi_variable(cgivars, "vasid");
Octstr *service_code = http_cgi_variable(cgivars, "servicecode");
if ((base_url = http_cgi_variable(cgivars, "base-url")) == NULL)
base_url = octstr_imm("http://localhost");
if ((data = http_cgi_variable(cgivars, "text")) != NULL) { /* text. */
Octstr *charset = http_cgi_variable(cgivars, "charset");
ctype = octstr_format("text/plain");
if (charset)
octstr_format_append(ctype, "; charset=%S", charset);
} else if ((data = http_cgi_variable(cgivars, "smil")) != NULL) /* smil. */
ctype = octstr_imm("application/smil");
else if ((data = http_cgi_variable(cgivars, "content")) != NULL) { /* any content. */
Octstr *_xctype = NULL; /* ... because cgi var stuff is destroyed elsewhere, we must dup it !! */
/* If the user sent us a content element, then they should have
* sent us its type either as part of the HTTP POST (multipart/form-data)
* or as CGI VAR called content_type
*/
if ((_xctype = http_cgi_variable(cgivars, "content_type")) == NULL)
if (cgivar_ctypes)
_xctype = http_cgi_variable(cgivar_ctypes, "content");
if (_xctype)
ctype = octstr_duplicate(_xctype);
} else if (body && tparse != 0) { /* if all above fails,
use send-mms msg body if any. */
data = octstr_duplicate(body);
ctype = http_header_value(h, octstr_imm("Content-Type"));
} else
rb = octstr_imm("Missing content");
dlr_url = http_cgi_variable(cgivars, "dlr-url");
rr_url = http_cgi_variable(cgivars, "rr-url");
mmc = http_cgi_variable(cgivars, "mmsc");
subject = http_cgi_variable(cgivars, "subject");
if ((to = http_cgi_variable(cgivars, "to")) == NULL)
rb = octstr_imm("Missing recipient!");
else
lto = octstr_split_words(to);
/* Build the fake reply headers. */
if (ctype)
http_header_add(rh, "Content-Type", octstr_get_cstr(ctype));
if ((from = http_cgi_variable(cgivars, "from")) != NULL) {
from = octstr_duplicate(from);
_mms_fixup_address(from);
http_header_add(rh, "X-Mbuni-From", octstr_get_cstr(from));
octstr_destroy(from);
} else if (!u->faked_sender)
rb = octstr_imm("Missing Sender address");
if (lto) {
for (i = 0, n = gwlist_len(lto); i < n; i++) {
Octstr *x = gwlist_get(lto, i);
http_header_add(rh, "X-Mbuni-To", octstr_get_cstr(x));
}
gwlist_destroy(lto, (gwlist_item_destructor_t *)octstr_destroy);
}
if (dlr_url)
http_header_add(rh, "X-Mbuni-DLR-Url", octstr_get_cstr(dlr_url));
if (rr_url)
http_header_add(rh, "X-Mbuni-RR-Url", octstr_get_cstr(rr_url));
if (mmc)
http_header_add(rh, "X-Mbuni-MMSC", octstr_get_cstr(mmc));
if (subject)
http_header_add(rh, "X-Mbuni-Subject", octstr_get_cstr(subject));
/* Requests to make_and_queue below can block, but for now we don't care. */
if (ctype && data && !rb) { /* only send if no error. */
res = make_and_queue_msg(data, ctype, rh, base_url, URL_TYPE, NULL,
vasid ? vasid : octstr_imm("sendmms-user"),
u->faked_sender, service_code,
1, NULL, &err);
if (res < 0)
rb = octstr_imm("Error in message conversion");
} else if (!rb)
rb = octstr_imm("Failed to send message");
http_send_reply(client, HTTP_BAD_REQUEST, hh,
rb ? rb : octstr_imm("Sent"));
info(0, "MMSBox.mmssend: u=%s, %s",
u ? octstr_get_cstr(u->user) : "none",
(res == 0) ? "Sent" : "Not Sent");
if (rh)
http_destroy_headers(rh);
if (ctype)
octstr_destroy(ctype);
if (rb)
octstr_destroy(rb);
} else {
http_send_reply(client, HTTP_UNAUTHORIZED, hh,
octstr_imm("Authentication failed"));
err = octstr_format("MMSBox: SendMMS, Authentication failed, "
"username=%s, password=%s!",
username ? octstr_get_cstr(username) : "(null)",
password ? octstr_get_cstr(password) : "(null)");
}
if (err) {
error(0, "%s", octstr_get_cstr(err));
octstr_destroy(err);
}
/* Free the ip and other stuff here. */
octstr_destroy(ip);
if (body)
octstr_destroy(body);
if (url)
octstr_destroy(url);
if (cgivars)
http_destroy_cgiargs(cgivars);
if (cgivar_ctypes)
http_destroy_cgiargs(cgivar_ctypes);
if (h)
http_destroy_headers(h);
if (hh)
http_destroy_headers(hh);
}
}