/* * Mbuni - Open Source MMS Gateway * * Misc. functions * * 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 #include #include #include #include #include #include #include #include "mms_util.h" #include "mms_queue.h" #include "mms_uaprof.h" Octstr *_mms_cfg_getx(mCfgGrp *grp, Octstr *item) { Octstr *v = mms_cfg_get(grp, item); return v ? v : octstr_create(""); } int mms_load_core_settings(mCfgGrp *cgrp) { Octstr *log, *alog; Octstr *http_proxy_host; long loglevel; if (cgrp == NULL) panic(0,"Missing required group `core' in config file!"); /* Set the log file. */ log = mms_cfg_get(cgrp, octstr_imm("log-file")); if (log != NULL) { if (mms_cfg_get_int(cgrp, octstr_imm("log-level"), &loglevel) == -1) loglevel = 0; log_open(octstr_get_cstr(log), loglevel, GW_NON_EXCL); octstr_destroy(log); } /* Get access log and open it. */ alog = mms_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 = mms_cfg_get(cgrp, octstr_imm("http-proxy-host"))) != NULL) { Octstr *username = mms_cfg_get(cgrp, octstr_imm("http-proxy-username")); Octstr *password = mms_cfg_get(cgrp, octstr_imm("http-proxy-password")); List *exceptions = mms_cfg_get_list(cgrp, octstr_imm("http-proxy-exceptions")); Octstr *except_regex = mms_cfg_get(cgrp, octstr_imm("http-proxy-exceptions-regex")); long http_proxy_port = -1; mms_cfg_get_int(cgrp, octstr_imm("http-proxy-port"), &http_proxy_port); if (http_proxy_port > 0) http_use_proxy(http_proxy_host, http_proxy_port, exceptions, username, password, except_regex); octstr_destroy(http_proxy_host); octstr_destroy(username); octstr_destroy(password); octstr_destroy(except_regex); gwlist_destroy(exceptions, octstr_destroy_item); } #ifdef HAVE_LIBSSL /* We expect that gwlib_init() has been called already, so only need * to setup cert files. * -- adapted from gwlib/conn.c */ { Octstr *ssl_client_certkey_file = NULL; Octstr *ssl_server_cert_file = NULL; Octstr *ssl_server_key_file = NULL; Octstr *ssl_trusted_ca_file = NULL; /* * check if SSL is desired for HTTP servers and then * load SSL client and SSL server public certificates * and private keys */ ssl_client_certkey_file = mms_cfg_get(cgrp, octstr_imm("ssl-client-certkey-file")); if (ssl_client_certkey_file != NULL) use_global_client_certkey_file(ssl_client_certkey_file); ssl_server_cert_file = mms_cfg_get(cgrp, octstr_imm("ssl-server-cert-file")); ssl_server_key_file = mms_cfg_get(cgrp, octstr_imm("ssl-server-key-file")); if (ssl_server_cert_file != NULL && ssl_server_key_file != NULL) use_global_server_certkey_file(ssl_server_cert_file, ssl_server_key_file); ssl_trusted_ca_file = mms_cfg_get(cgrp, octstr_imm("ssl-trusted-ca-file")); use_global_trusted_ca_file(ssl_trusted_ca_file); octstr_destroy(ssl_client_certkey_file); octstr_destroy(ssl_server_cert_file); octstr_destroy(ssl_server_key_file); octstr_destroy(ssl_trusted_ca_file); } #endif return 0; } Octstr *mms_maketransid(char *qf, Octstr *mmscname) { Octstr *res; Octstr *x, *y = NULL; static int ct; if (!qf) x = octstr_format("msg.%ld.x%d.%d.%d", (long)time(NULL) % 10000, (++ct % 1000), getpid()%100, random()%100); else x = octstr_create(qf); res = octstr_format("%S@%S", mmscname, x); octstr_destroy(x); octstr_destroy(y); return res; } extern Octstr *mms_getqf_fromtransid(Octstr *transid) { int i; if (transid == NULL) return NULL; i = octstr_search_char(transid, '@', 0); return (i >= 0) ? octstr_copy(transid, i+1, octstr_len(transid)) : 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(); } void mms_lib_shutdown(void) { mms_strings_shutdown(); gwlib_shutdown(); } 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') return 1; } return 0; } Octstr *make_value_parameters(List *params) { Octstr *s = octstr_create(""), *name, *value; int i, n; for (i = 0, n = params ? gwlist_len(params) : 0; i= 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 && gwlist_len(l) > 1) for (j = 0, m = gwlist_len(l); j 0) for (i = 0; i 0) for (i = 0; i= 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(""); List *headers = mime_entity_headers(m); /* we don't want the mime version header removed. */ if (append_hostname) { /* Add our hostname to all phone numbers. */ int i, n; List *l = http_create_empty_headers(); Octstr *xfrom = http_header_value(headers, octstr_imm("From")); List *lto = http_header_find_all(headers, "To"); List *lcc = http_header_find_all(headers, "Cc"); if (xfrom) { addmmscname(xfrom, myhostname); http_header_add(l, "From", octstr_get_cstr(xfrom)); octstr_destroy(xfrom); } http_header_remove_all(headers, "From"); for (i = 0, n = gwlist_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(headers, "To"); for (i = 0, n = gwlist_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(headers, "Cc"); http_append_headers(headers, l); /* combine old with new. */ http_destroy_headers(l); } /* Pack headers, get string rep of mime entity. */ http_header_pack(headers); mime_replace_headers(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 (;;) { Octstr *tmp; 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': tmp = octstr_duplicate(to); escape_shell_chars(tmp); octstr_append(cmd, tmp); octstr_destroy(tmp); break; case 'f': if (append_hostname) { Octstr *xfrom = octstr_duplicate(from); addmmscname(xfrom, myhostname); escape_shell_chars(xfrom); octstr_append(cmd, xfrom); octstr_destroy(xfrom); } else { tmp = octstr_duplicate(from); escape_shell_chars(tmp); octstr_append(cmd, tmp); octstr_destroy(tmp); } break; case 's': tmp = octstr_duplicate(subject); escape_shell_chars(tmp); octstr_append(cmd, subject); octstr_destroy(tmp); break; case 'm': tmp = octstr_duplicate(msgid); escape_shell_chars(tmp); octstr_append(cmd, msgid); octstr_destroy(tmp); 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: http_destroy_headers(headers); 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 *headers = 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_add(newhdrs, "MIME-Version", "1.0"); headers = _x_mime_entity_headers(m); http_header_combine(headers, newhdrs); mime_replace_headers(m, headers); http_destroy_headers(headers); http_destroy_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 = gwlist_create(); gwlist_append(l, to); } else l = NULL; mms_log(logmsg, from,l,msize,msgid,acct,viaproxy,interface,ua,mmboxloc); if (l) gwlist_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 ? gwlist_len(to) : 0; for (i = 0; i < n; i++) octstr_format_append(xto, "%s%S", (i == 0) ? "" : ", ", gwlist_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; } void mms_collect_envdata_from_msgheaders(List *mh, List **xto, Octstr **subject, Octstr **otransid, time_t *expiryt, time_t *deliveryt, long default_msgexpiry) { Octstr *s; List *l = http_header_find_all(mh, "To"); if (l) { int i, n; for (i = 0, n = gwlist_len(l); i= 1 && octstr_get_cstr(s)[0] == '+') i++; for ( cs = octstr_get_cstr(s); i0) return; i = octstr_search(address, octstr_imm("/TYPE="), 0); if (i > 0) return; if (isphonenum(address)) octstr_append(address, octstr_imm("/TYPE=PLMN")); else octstr_append(address, octstr_imm("@unknown")); } /* compare, reversed result! */ static int comp_fn(void *item, void *pattern) { return (octstr_case_compare(item, pattern) == 0) ? 1 : 0; } int is_allowed_host(Octstr *host, Octstr *host_list) { List *l; int ret; gw_assert(host_list); gw_assert(host); l = octstr_split(host_list, octstr_imm(";")); ret = (gwlist_search(l, host, comp_fn) != NULL) ? 1 : 0; gwlist_destroy(l, (void *)octstr_destroy); return ret; } #define SHELLCHARS "'|\"()[]{}$&!?*><%`\n \t\\" void escape_shell_chars(Octstr *str) { Octstr *tmp; int i, n; octstr_strip_blanks(str); tmp = octstr_duplicate(str); octstr_delete(str, 0, octstr_len(str)); for (i = 0, n = octstr_len(tmp); i < n; i++) { int ch = octstr_get_char(tmp,i); if (strchr(SHELLCHARS, ch) != NULL) octstr_append_char(str, '\\'); octstr_append_char(str, ch); } octstr_destroy(tmp); } int parse_cgivars(List *request_headers, Octstr *request_body, List **cgivars, List **cgivar_ctypes) { Octstr *ctype = NULL, *charset = NULL; int ret = 0; if (request_body == NULL || octstr_len(request_body) == 0 || cgivars == NULL) return 0; /* Nothing to do, this is a normal GET request. */ http_header_get_content_type(request_headers, &ctype, &charset); if (*cgivars == NULL) *cgivars = gwlist_create(); if (*cgivar_ctypes == NULL) *cgivar_ctypes = gwlist_create(); if (!ctype) { warning(0, "MMS: Parse CGI Vars: Missing Content Type!"); ret = -1; goto done; } if (octstr_case_compare(ctype, octstr_imm("application/x-www-form-urlencoded")) == 0) { /* This is a normal POST form */ List *l = octstr_split(request_body, octstr_imm("&")); Octstr *v; while ((v = gwlist_extract_first(l)) != NULL) { List *r = octstr_split(v, octstr_imm("=")); if (gwlist_len(r) == 0) warning(0, "MMS: Parse CGI Vars: Missing CGI var name/value in POST data: %s", octstr_get_cstr(request_body)); else { HTTPCGIVar *x = gw_malloc(sizeof *x); x->name = gwlist_extract_first(r); x->value = gwlist_extract_first(r); if (!x->value) x->value = octstr_imm(""); octstr_strip_blanks(x->name); octstr_strip_blanks(x->value); octstr_url_decode(x->name); octstr_url_decode(x->value); gwlist_append(*cgivars, x); } octstr_destroy(v); gwlist_destroy(r, octstr_destroy_item); } gwlist_destroy(l, NULL); } else if (octstr_case_compare(ctype, octstr_imm("multipart/form-data")) == 0) { /* multi-part form data */ MIMEEntity *m = mime_http_to_entity(request_headers, request_body); int i, n; if (!m) { warning(0, "MMS: Parse CGI Vars: Failed to parse multipart/form-data body: %s", octstr_get_cstr(request_body)); ret = -1; goto done; } /* Go through body parts, pick out what we need. */ for (i = 0, n = mime_entity_num_parts(m); i < n; i++) { MIMEEntity *mp = mime_entity_get_part(m, i); List *headers = _x_mime_entity_headers(mp); Octstr *body = mime_entity_body(mp); Octstr *ct = http_header_value(headers, octstr_imm("Content-Type")); Octstr *cd = http_header_value(headers, octstr_imm("Content-Disposition")); Octstr *name = http_get_header_parameter(cd, octstr_imm("name")); if (name) { HTTPCGIVar *x = gw_malloc(sizeof *x); /* Strip quotes */ if (octstr_get_char(name, 0) == '"') { octstr_delete(name, 0, 1); octstr_truncate(name, octstr_len(name) - 1); } x->name = octstr_duplicate(name); x->value = octstr_duplicate(body); gwlist_append(*cgivars, x); if (ct) { /* If the content type is set, use it. */ x = gw_malloc(sizeof *x); x->name = octstr_duplicate(name); x->value = octstr_duplicate(ct); gwlist_append(*cgivar_ctypes, x); } octstr_destroy(name); } if (ct) octstr_destroy(ct); if (cd) octstr_destroy(cd); octstr_destroy(body); http_destroy_headers(headers); mime_entity_destroy(mp); } mime_entity_destroy(m); } else /* else it is nothing that we know about, so simply go away... */ ret = -1; done: if (ctype) octstr_destroy(ctype); if (charset) octstr_destroy(charset); return ret; } /* We need this because of boundary element adding bug in gwlib/mime.c */ List *_x_mime_entity_headers(MIMEEntity *m) { List *h = mime_entity_headers(m); http_header_remove_all(h, "MIME-Version"); /* also remove boundary element -- it was added erroneously */ if (mime_entity_num_parts(m) == 0) strip_boundary_element(h,NULL); mime_replace_headers(m, h); return h; } /* get content-ID header, fix: WAP decoder may leave " at beginning */ Octstr *_x_get_content_id(List *headers) { Octstr *cid = http_header_value(headers, octstr_imm("Content-ID")); if (cid) if (octstr_get_char(cid, 0) == '"' && octstr_get_char(cid, octstr_len(cid) - 1) != '"') octstr_delete(cid, 0,1); return cid; } /* Utility: Take a header list, remove any boundary parameter from the content-type * element. We don't want this in the WSP packed content. */ void strip_boundary_element(List *headers, char *s) { Octstr *ctype = NULL, *params = NULL; Octstr *value; gw_assert(headers); get_content_type(headers, &ctype, ¶ms); if (s) {/* we are replacing the content type as well as stripping */ if (ctype) octstr_destroy(ctype); ctype = octstr_create(s); } if (params) { List *h = get_value_parameters(params); Octstr *ps; http_header_remove_all(h,"boundary"); /* We don't need the boundary param if it is there. */ ps = make_value_parameters(h); value = octstr_format("%S%s%S", ctype, (ps && octstr_len(ps) > 0) ? "; " : "", ps); octstr_destroy(ps); http_destroy_headers(h); } else value = ctype; http_header_remove_all(headers, "Content-Type"); http_header_add(headers, "Content-Type", octstr_get_cstr(value)); if (ctype != value) octstr_destroy(ctype); octstr_destroy(value); }