1
0
Fork 0
mbuni/mbuni/mmlib/mms_util.c

833 lines
21 KiB
C

/*
* Mbuni - Open Source MMS Gateway
*
* Misc. functions
*
* 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 <sys/file.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>
#include <dlfcn.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "mms_util.h"
#include "mms_queue.h"
#include "mms_uaprof.h"
Octstr *_mms_cfg_getx(CfgGroup *grp, Octstr *item)
{
Octstr *v = cfg_get(grp, item);
return v ? v : octstr_create("");
}
int mms_load_core_settings(CfgGroup *cgrp)
{
Octstr *log, *alog;
Octstr *http_proxy_host;
long loglevel;
/* Set the log file. */
log = cfg_get(cgrp, octstr_imm("log-file"));
if (log != NULL) {
if (cfg_get_integer(&loglevel, cgrp, 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(cgrp, octstr_imm("access-log"));
if (alog) {
alog_open(octstr_get_cstr(alog), 1, 1);
octstr_destroy(alog);
}
/* look for http proxy. If set, use it. */
if ((http_proxy_host = cfg_get(cgrp, octstr_imm("http-proxy-host"))) != NULL) {
Octstr *username = cfg_get(cgrp,
octstr_imm("http-proxy-username"));
Octstr *password = cfg_get(cgrp,
octstr_imm("http-proxy-password"));
List *exceptions = cfg_get_list(cgrp,
octstr_imm("http-proxy-exceptions"));
long http_proxy_port = -1;
cfg_get_integer(&http_proxy_port, cgrp, octstr_imm("http-proxy-port"));
if (http_proxy_port > 0)
http_use_proxy(http_proxy_host, http_proxy_port,
exceptions, username, password);
octstr_destroy(http_proxy_host);
octstr_destroy(username);
octstr_destroy(password);
list_destroy(exceptions, octstr_destroy_item);
}
#ifdef HAVE_LIBSSL
conn_config_ssl(cgrp);
#endif
return 0;
}
Octstr *mms_find_sender_ip(List *request_hdrs, Octstr *ip_header, Octstr *ip, int *isv6)
{
Octstr *xip;
/* Look in the headers, if none is defined, return actual IP */
Octstr *client_ip = http_header_value(request_hdrs, ip_header);
char *s;
xip = client_ip ? client_ip : octstr_duplicate(ip);
s = octstr_get_cstr(xip);
/* Crude test for ipv6 */
*isv6 = (index(s, ':') >= 0);
return xip;
}
int mms_decodefetchurl(Octstr *fetch_url,
Octstr **qf, Octstr **token, int *loc)
{
Octstr *xfurl = octstr_duplicate(fetch_url);
int i, j, n;
char *s, *p;
for (i = 0, n = 0, s = octstr_get_cstr(xfurl);
i < octstr_len(xfurl); i++)
if (s[i] == '/')
n++;
if (n < 2) /* We need at least two slashes. */
octstr_append_char(xfurl, '/');
i = 0;
n = octstr_len(xfurl);
s = octstr_get_cstr(xfurl);
p = strrchr(s, '/'); /* Find last slash. */
if (p)
i = (p - s) - 1;
else
i = n-1;
if (i < 0)
i = 0;
while (i>0 && s[i] != '/')
i--; /* Go back, find first slash */
if (i>=0 && s[i] == '/')
i++;
/* Now we have qf, find its end. */
j = i;
while (j<n && s[j] != '/')
j++; /* Skip to next slash. */
*qf = octstr_copy(fetch_url, i, j-i);
if (j<n)
*token = octstr_copy(fetch_url, j + 1, n - (j+1));
else
*token = octstr_create("");
octstr_destroy(xfurl);
/* Now get loc out of qf. */
*loc = MMS_LOC_MQUEUE;
i = octstr_search_char(*qf, '@', 0);
if (i >= 0) {
long l;
int j = octstr_parse_long(&l, *qf, i+1, 10);
if (j > 0)
*loc = l;
octstr_delete(*qf, i, octstr_len(*qf));
}
return 0;
}
Octstr *mms_maketransid(char *qf, Octstr *mmscname)
{
Octstr *res;
Octstr *x;
static int ct;
if (!qf)
x = octstr_format("msg.%ld.x%d.%d.%d",
time(NULL), ++ct, getpid(), random()%10000);
else
x = octstr_create(qf);
res = octstr_format("%S@%S", x, mmscname);
octstr_destroy(x);
return res;
}
extern Octstr *mms_getqf_fromtransid(Octstr *transid)
{
int i = octstr_search_char(transid, '@', 0);
return (i >= 0) ? octstr_copy(transid, 0, i) : octstr_duplicate(transid);
}
Octstr *mms_isodate(time_t t)
{
Octstr *current_time;
struct tm now;
now = gw_gmtime(t);
current_time = octstr_format("%04d-%02d-%02dT%02d:%02d:%02dZ",
now.tm_year + 1900, now.tm_mon + 1,
now.tm_mday, now.tm_hour, now.tm_min,
now.tm_sec);
return current_time;
}
void mms_lib_init(void)
{
gwlib_init();
mms_strings_init();
}
static void strip_quotes(Octstr *s)
{
int l = s ? octstr_len(s) : 0;
if (l == 0)
return;
if (octstr_get_char(s, 0) == '"') {
octstr_delete(s, 0, 1);
l--;
}
if (octstr_get_char(s, l-1) == '"')
octstr_delete(s, l-1, 1);
}
List *get_value_parameters(Octstr *params)
{
int i,n, k = 0;
List *h = http_create_empty_headers();
Octstr *xparams = octstr_duplicate(params);
octstr_format_append(xparams, ";"); /* So parsing is easier. (aka cheap hack) */
for (i = 0, n = octstr_len(xparams); i < n; i++) {
int c = octstr_get_char(xparams, i);
if (c == ';') {
int j = octstr_search_char(xparams, '=', k);
Octstr *name, *value;
if (j > 0 && j < i) {
name = octstr_copy(xparams, k, j - k);
value = octstr_copy(xparams, j+1,i-j-1);
octstr_strip_blanks(name);
octstr_strip_blanks(value);
strip_quotes(value);
if (octstr_len(name) > 0)
http_header_add(h,
octstr_get_cstr(name),
octstr_get_cstr(value));
octstr_destroy(name);
octstr_destroy(value);
}
k = i + 1;
} else if (c == '"')
i += http_header_quoted_string_len(xparams, i) - 1;
}
octstr_destroy(xparams);
return h;
}
int split_header_value(Octstr *value, Octstr **base_value, Octstr **params)
{
int i, n;
for (i = 0, n = octstr_len(value); i < n; i++) {
int c = octstr_get_char(value, i);
if (c == ';')
break;
else if (c == '"')
i += http_header_quoted_string_len(value, i) - 1;
}
*base_value = octstr_duplicate(value);
if (i < n) {
*params = octstr_copy(value, i+1, octstr_len(value));
octstr_delete(*base_value, i, octstr_len(*base_value));
} else
*params = octstr_create("");
return 0;
}
int get_content_type(List *hdrs, Octstr **type, Octstr **params)
{
Octstr *v;
v = http_header_find_first(hdrs, "Content-Type");
*params =NULL;
if (!v) {
*type = octstr_create("application/octet-stream");
*params = octstr_create("");
return -1;
}
split_header_value(v, type, params);
octstr_destroy(v);
return 0;
}
static int needs_quotes(Octstr *s)
{
int i, n;
if (!s)
return 0;
for (i = 0, n = octstr_len(s); i<n; i++) {
int ch = octstr_get_char(s,i);
if (isspace(ch) || ispunct(ch))
return 1;
}
return 0;
}
Octstr *make_value_parameters(List *params)
{
Octstr *s = octstr_create(""), *name, *value;
int i, n;
for (i = 0, n = params ? list_len(params) : 0; i<n; i++) {
int space;
http_header_get(params, i, &name, &value);
space = needs_quotes(value);
octstr_format_append(s, "%s%S=%s%S%s",
(i==0) ? "" : "; ",
name,
(space) ? "\"" : "",
value,
(space) ? "\"" : "");
octstr_destroy(name);
octstr_destroy(value);
}
return s;
}
/* Take each header with a comma separated set of values (for To,Cc,Bcc),
* re-create as a series of header/value pairs.
* Remove all non-conformant headers (e.g. old unix-style from
*/
void unpack_mimeheaders(MIMEEntity *mm)
{
int i, n;
List *h = http_create_empty_headers();
for (i = 0, n = list_len(mm->headers); i<n; i++) {
Octstr *header = NULL, *value = NULL;
List *l = NULL;
int j, m;
int skip;
http_header_get(mm->headers, i, &header, &value);
if (header == NULL ||
octstr_str_compare(header, "X-Unknown") == 0 ||
octstr_search_chars(header, octstr_imm(" \n\t"), 0) >= 0) /* Don't allow space in the name. */
goto loop;
if (octstr_case_compare(header, octstr_imm("Cc")) == 0 ||
octstr_case_compare(header, octstr_imm("To")) == 0 ||
octstr_case_compare(header, octstr_imm("Bcc")) == 0)
skip = 0;
else
skip = 1;
/* XXX This may not be safe. Need to skip over quotes. */
if (!skip && octstr_search_char(value, ',', 0) > 0 &&
(l = http_header_split_value(value)) != NULL &&
list_len(l) > 1)
for (j = 0, m = list_len(l); j<m; j++)
http_header_add(h, octstr_get_cstr(header),
octstr_get_cstr(list_get(l, j)));
else
http_header_add(h, octstr_get_cstr(header),
octstr_get_cstr(value));
if (l) list_destroy(l, (list_item_destructor_t *)octstr_destroy);
loop:
if (header) octstr_destroy(header);
if (value) octstr_destroy(value);
}
http_destroy_headers(mm->headers);
mm->headers = h;
}
/* Undo base64 content coding for mime entities that need it. */
void unbase64_mimeparts(MIMEEntity *m)
{
int i, n;
if (m->multiparts && list_len(m->multiparts) > 0)
for (i = 0, n = list_len(m->multiparts); i<n; i++)
unbase64_mimeparts(list_get(m->multiparts, i));
else { /* A non-multipart message .*/
Octstr *ctype = http_header_value(m->headers, octstr_imm("Content-Type"));
Octstr *te = http_header_value(m->headers, octstr_imm("Content-Transfer-Encoding"));
if (ctype && te &&
octstr_case_compare(te,octstr_imm("base64")) == 0)
octstr_base64_to_binary(m->body);
http_header_remove_all(m->headers, "Content-Transfer-Encoding"); /* Remove it in all cases (?).*/
/* XXX may be we should deal with other transfer encodings here as well... */
if (ctype)
octstr_destroy(ctype);
if (te)
octstr_destroy(te);
}
}
int _mms_gw_isprint(int c)
{
return isprint(c) || isspace(c);
}
/* Change content coding for mime entities that need it. */
void base64_mimeparts(MIMEEntity *m)
{
int i, n;
if (m->multiparts && list_len(m->multiparts) > 0)
for (i = 0, n = list_len(m->multiparts); i<n; i++)
base64_mimeparts(list_get(m->multiparts, i));
else { /* A non-multipart message .*/
Octstr *ctype = http_header_value(m->headers, octstr_imm("Content-Type"));
Octstr *te = http_header_value(m->headers, octstr_imm("Content-Transfer-Encoding"));
if (ctype && !te
#if 1
&&
(m->body && octstr_check_range(m->body, 0, octstr_len(m->body), _mms_gw_isprint) == 0)
#endif
) {
octstr_binary_to_base64(m->body);
http_header_add(m->headers, "Content-Transfer-Encoding", "base64");
}
if (ctype)
octstr_destroy(ctype);
if (te)
octstr_destroy(te);
}
}
void notify_prov_server(char *cmd, char *from, char *event, char *arg)
{
Octstr *s;
if (cmd == NULL || cmd[0] == '\0')
return;
s = octstr_format("%s '%s' '%s' '%s'", cmd, event, from, arg);
if (s) {
system(octstr_get_cstr(s));
octstr_destroy(s);
}
}
int mms_ind_send(Octstr *prov_cmd, Octstr *to)
{
Octstr *s;
int res = 1;
if (prov_cmd == NULL ||
octstr_len(prov_cmd) == 0)
return 1;
s = octstr_format("%S %S", prov_cmd, to);
if (s) {
int x = system(octstr_get_cstr(s));
int y = WEXITSTATUS(x);
if (x < 0) {
error(0, "Checking MMS Ind.Send: Failed to run command %s!",
octstr_get_cstr(s));
res = 1;
} else if (y != 0 && y != 1)
res = -1;
else
res = y;
octstr_destroy(s);
} else
warning(0, "Checking MMS Ind.Send: Failed call to compose command [%s] ",
octstr_get_cstr(prov_cmd));
return res;
}
static void addmmscname(Octstr *s, Octstr *myhostname)
{
int j;
int len = octstr_len(s);
if (octstr_search_char(s, '@', 0) >= 0)
return; /* Nothing to do. */
j = octstr_case_search(s, octstr_imm("/TYPE=PLMN"), 0);
if (j > 0 && j - 1 + sizeof "/TYPE=PLMN" == len) { /* A proper number. */
octstr_delete(s, j, -1 + sizeof "/TYPE=PLMN"); /* XXX We strip off /TYPE=PLMN, should we ? */
octstr_format_append(s, "@%S", myhostname);
}
}
static int send2email(Octstr *to, Octstr *from, Octstr *subject,
Octstr *msgid,
MIMEEntity *m, int append_hostname, Octstr **error,
char *sendmail_cmd, Octstr *myhostname)
{
Octstr *s;
FILE *f;
int ret = MMS_SEND_OK, i;
Octstr *cmd = octstr_create("");
if (append_hostname) { /* Add our hostname to all phone numbers. */
int i, n;
List *l = http_create_empty_headers();
Octstr *xfrom = http_header_value(m->headers, octstr_imm("From"));
List *lto = http_header_find_all(m->headers, "To");
List *lcc = http_header_find_all(m->headers, "Cc");
if (xfrom) {
addmmscname(xfrom, myhostname);
http_header_add(l, "From", octstr_get_cstr(xfrom));
octstr_destroy(xfrom);
}
http_header_remove_all(m->headers, "From");
for (i = 0, n = list_len(lto); i < n; i++) {
Octstr *name, *value;
http_header_get(lto, i, &name, &value);
if (!value || !name ||
octstr_case_compare(name, octstr_imm("To")) != 0)
goto loop;
addmmscname(value, myhostname);
http_header_add(l, "To", octstr_get_cstr(value));
loop:
if (value) octstr_destroy(value);
if (name) octstr_destroy(name);
}
http_destroy_headers(lto);
http_header_remove_all(m->headers, "To");
for (i = 0, n = list_len(lcc); i < n; i++) {
Octstr *name, *value;
http_header_get(lcc, i, &name, &value);
if (!value || !name ||
octstr_case_compare(name, octstr_imm("Cc")) != 0)
goto loop2;
addmmscname(value, myhostname);
http_header_add(l, "Cc", octstr_get_cstr(value));
loop2:
if (value) octstr_destroy(value);
if (name) octstr_destroy(name);
}
http_destroy_headers(lcc);
http_header_remove_all(m->headers, "Cc");
http_append_headers(m->headers, l); /* combine old with new. */
http_destroy_headers(l);
}
/* Pack headers, get string rep of mime entity. */
http_header_pack(m->headers);
s = mime_entity_to_octstr(m);
/*
* Make the command: Transpose % formatting characters:
* f - from address
* t - recipient
* s - subject
* m - message id
*/
i = 0;
for (;;) {
while (sendmail_cmd[i]) {
char c = sendmail_cmd[i];
if (c == '%' && sendmail_cmd[i + 1])
break;
octstr_append_char(cmd, c);
i++;
}
if (!sendmail_cmd[i])
break;
switch(sendmail_cmd[i+1]) {
case 't':
octstr_append(cmd, to);
break;
case 'f':
if (append_hostname) {
Octstr *xfrom = octstr_duplicate(from);
addmmscname(xfrom, myhostname);
octstr_append(cmd, xfrom);
octstr_destroy(xfrom);
} else
octstr_append(cmd, from);
break;
case 's':
octstr_append(cmd, subject);
break;
case 'm':
octstr_append(cmd, msgid);
break;
case '%':
octstr_format_append(cmd, "%%");
break;
default:
octstr_format_append(cmd, "%%%c", sendmail_cmd[i+1]);
break;
}
i += 2;
}
debug("mms.sendtoemail", 0, "preparing to execute %s to send to email: ", octstr_get_cstr(cmd));
if ((f = popen(octstr_get_cstr(cmd), "w")) == NULL) {
*error = octstr_format("popen failed for %S: %d: %s",
cmd, errno, strerror(errno));
ret = MMS_SEND_ERROR_TRANSIENT;
goto done;
}
if (octstr_print(f, s) < 0) {
*error = octstr_format("send email failed in octstr_print %d: %s",
errno, strerror(errno));
pclose(f);
ret = MMS_SEND_ERROR_TRANSIENT;
goto done;
}
if ((ret = pclose(f)) != 0) {
*error = octstr_format("Send email command returned non-zero %d: errno=%s",
ret, strerror(errno));
ret = MMS_SEND_ERROR_TRANSIENT;
} else
ret = MMS_SEND_OK;
done:
octstr_destroy(cmd);
octstr_destroy(s);
return ret;
}
/* Send this message to email recipient. */
int mms_sendtoemail(Octstr *from, Octstr *to,
Octstr *subject, Octstr *msgid,
MmsMsg *msg, int dlr, Octstr **error, char *sendmail_cmd,
Octstr *myhostname,
int trans_msg,
int trans_smil, char *txt, char *html,
int append_hostname)
{
MIMEEntity *m = NULL;
List *newhdrs = http_create_empty_headers();
int ret;
if (!to ||
octstr_search_char(to, '@', 0) < 0) {
*error = octstr_format("Invalid email address %S!", to);
return MMS_SEND_ERROR_FATAL;
}
if (!trans_msg)
m = mms_tomime(msg,0);
else
if ((ret = mms_format_special(msg, trans_smil, txt, html, &m)) < 0 ||
m == NULL) {
warning(0, "MMS: send2email failed to format message (msg=%s,ret=%d)",
m ? "OK" : "Not transformed",ret);
return -ret;
}
base64_mimeparts(m); /* make sure parts are base64 formatted. */
/* Before we send it, we insert some email friendly headers if they are missing. */
http_header_add(newhdrs, "Subject", subject ? octstr_get_cstr(subject) : "MMS Message");
http_header_add(newhdrs, "From", octstr_get_cstr(from));
http_header_add(newhdrs, "To", octstr_get_cstr(to));
http_header_add(newhdrs, "Message-ID", msgid ? octstr_get_cstr(msgid) : "");
http_header_combine(newhdrs, m->headers);
http_destroy_headers(m->headers);
m->headers = newhdrs;
ret = send2email(to, from, subject, msgid, m, append_hostname, error, sendmail_cmd, myhostname);
mime_entity_destroy(m);
return ret;
}
void mms_log2(char *logmsg, Octstr *from, Octstr *to,
int msize, Octstr *msgid,
Octstr *acct,
Octstr *viaproxy,
char *interface, Octstr *ua, Octstr *mmboxloc)
{
List *l;
if (to) {
l = list_create();
list_append(l, to);
} else
l = NULL;
mms_log(logmsg, from,l,msize,msgid,acct,viaproxy,interface,ua,mmboxloc);
if (l)
list_destroy(l, NULL);
}
void mms_log(char *logmsg, Octstr *from, List *to,
int msize, Octstr *msgid,
Octstr *acct,
Octstr *viaproxy,
char *interface, Octstr *ua, Octstr *mmboxloc)
{
Octstr *xto = octstr_create("");
int i, n = to ? list_len(to) : 0;
for (i = 0; i < n; i++)
octstr_format_append(xto,
"%s%S",
(i == 0) ? "" : ", ",
list_get(to,i));
alog("%s MMS [INT:%s] [ACT:%s] [MMSC:%s] [from:%s] [to:%s] [msgid:%s] [size=%d] [UA:%s] [MMBox:%s]",
logmsg, interface,
acct ? octstr_get_cstr(acct) : "",
viaproxy ? octstr_get_cstr(viaproxy) : "",
from ? octstr_get_cstr(from) : "",
octstr_get_cstr(xto),
msgid ? octstr_get_cstr(msgid) : "",
msize,
ua ? octstr_get_cstr(ua) : "",
mmboxloc ? octstr_get_cstr(mmboxloc) : "");
octstr_destroy(xto);
}
static int lockfile(int fd, int shouldblock)
{
int n, stop;
unsigned flg = shouldblock ? 0 : LOCK_NB;
do {
n = flock(fd, LOCK_EX|flg);
if (n < 0) {
if (errno == EINTR)
stop = 0;
else
stop = 1;
} else
stop = 1;
} while (!stop);
return (n == 0) ? 0 : errno;
}
static int check_lock(int fd, char *fname)
{
struct stat fs = {0}, ds = {0};
/* You might grab a lock on a file, but the file
* might be changed just before you grabbed the lock. Detect that and fail..
*/
if (fstat(fd, &ds) < 0 ||
stat(fname, &fs) < 0 ||
ds.st_nlink != fs.st_nlink ||
memcmp(&ds.st_dev,&fs.st_dev, sizeof ds.st_dev) != 0 ||
memcmp(&ds.st_ino,&fs.st_ino, sizeof ds.st_ino) != 0 ||
ds.st_uid != fs.st_uid ||
ds.st_gid != fs.st_gid ||
ds.st_size != fs.st_size)
return -1;
else
return 0;
}
int mm_lockfile(int fd, char *fname, int shouldblock)
{
int ret = lockfile(fd,shouldblock);
if (ret != 0 ||
(ret = check_lock(fd,fname)) != 0)
return ret;
return 0;
}
MIMEEntity *mime_entity_duplicate(MIMEEntity *m)
{
MIMEEntity *mx = gw_malloc(sizeof *mx);
mx->headers = http_header_duplicate(m->headers);
if (m->multiparts && list_len(m->multiparts) > 0) {
int i, n;
mx->multiparts = list_create();
for (i = 0, n = list_len(m->multiparts); i < n; i++) {
MIMEEntity *x = mime_entity_duplicate(list_get(m->multiparts, i));
list_append(mx->multiparts, x);
}
mx->body = NULL;
} else {
mx->body = m->body ? octstr_duplicate(m->body) : NULL;
mx->multiparts = NULL;
}
mx->start = NULL;
return mx;
}