/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * gw/dlr.c * * Implementation of handling delivery reports (DLRs) * * Andreas Fink , 18.08.2001 * Stipe Tolj , 22.03.2002 * Alexander Malysh 2003 * * Changes: * 2001-12-17: andreas@fink.org: * implemented use of mutex to avoid two mysql calls to run at the same time * 2002-03-22: stolj@wapme.de: * added more abstraction to fit for several other storage types * 2002-08-04: stolj@wapme.de: * added simple database library (sdb) support * 2002-11-14: stolj@wapme.de: * added re-routing info for DLRs to route via bearerbox to the same smsbox * instance. This is required if you use state conditioned smsboxes or smppboxes * via one bearerbox. Previously bearerbox was simple ignoring to which smsbox * connection a msg is passed. Now we can route the messages inside bearerbox. */ #include #include #include #include #include #include #include "gwlib/gwlib.h" #include "sms.h" #include "dlr.h" #include "dlr_p.h" /* Our callback functions */ static struct dlr_storage *handles = NULL; /* * Function to allocate a new struct dlr_entry entry * and intialize it to zero */ struct dlr_entry *dlr_entry_create(void) { struct dlr_entry *dlr; dlr = gw_malloc(sizeof(*dlr)); gw_assert(dlr != NULL); /* set all values to NULL */ memset(dlr, 0, sizeof(*dlr)); return dlr; } /* * Duplicate dlr entry */ struct dlr_entry *dlr_entry_duplicate(const struct dlr_entry *dlr) { struct dlr_entry *ret; if (dlr == NULL) return NULL; ret = dlr_entry_create(); ret->smsc = octstr_duplicate(dlr->smsc); ret->timestamp = octstr_duplicate(dlr->timestamp); ret->source = octstr_duplicate(dlr->source); ret->destination = octstr_duplicate(dlr->destination); ret->service = octstr_duplicate(dlr->service); ret->url = octstr_duplicate(dlr->url); ret->boxc_id = octstr_duplicate(dlr->boxc_id); ret->mask = dlr->mask; return ret; } /* * Function to destroy the struct dlr_entry entry */ void dlr_entry_destroy(struct dlr_entry *dlr) { /* sanity check */ if (dlr == NULL) return; #define O_DELETE(a) { if (a) octstr_destroy(a); a = NULL; } O_DELETE(dlr->smsc); O_DELETE(dlr->timestamp); O_DELETE(dlr->source); O_DELETE(dlr->destination); O_DELETE(dlr->service); O_DELETE(dlr->url); O_DELETE(dlr->boxc_id); #undef O_DELETE dlr->mask = 0; gw_free(dlr); } /* * Load all configuration directives that are common for all database * types that use the 'dlr-db' group to define which attributes are * used in the table */ struct dlr_db_fields *dlr_db_fields_create(CfgGroup *grp) { struct dlr_db_fields *ret = NULL; ret = gw_malloc(sizeof(*ret)); gw_assert(ret != NULL); memset(ret, 0, sizeof(*ret)); if (!(ret->table = cfg_get(grp, octstr_imm("table")))) panic(0, "DLR: DB: directive 'table' is not specified!"); if (!(ret->field_smsc = cfg_get(grp, octstr_imm("field-smsc")))) panic(0, "DLR: DB: directive 'field-smsc' is not specified!"); if (!(ret->field_ts = cfg_get(grp, octstr_imm("field-timestamp")))) panic(0, "DLR: DB: directive 'field-timestamp' is not specified!"); if (!(ret->field_src = cfg_get(grp, octstr_imm("field-source")))) panic(0, "DLR: DB: directive 'field-source' is not specified!"); if (!(ret->field_dst = cfg_get(grp, octstr_imm("field-destination")))) panic(0, "DLR: DB: directive 'field-destination' is not specified!"); if (!(ret->field_serv = cfg_get(grp, octstr_imm("field-service")))) panic(0, "DLR: DB: directive 'field-service' is not specified!"); if (!(ret->field_url = cfg_get(grp, octstr_imm("field-url")))) panic(0, "DLR: DB: directive 'field-url' is not specified!"); if (!(ret->field_mask = cfg_get(grp, octstr_imm("field-mask")))) panic(0, "DLR: DB: directive 'field-mask' is not specified!"); if (!(ret->field_status = cfg_get(grp, octstr_imm("field-status")))) panic(0, "DLR: DB: directive 'field-status' is not specified!"); if (!(ret->field_boxc = cfg_get(grp, octstr_imm("field-boxc-id")))) panic(0, "DLR: DB: directive 'field-boxc-id' is not specified!"); return ret; } void dlr_db_fields_destroy(struct dlr_db_fields *fields) { /* sanity check */ if (fields == NULL) return; #define O_DELETE(a) { if (a) octstr_destroy(a); a = NULL; } O_DELETE(fields->table); O_DELETE(fields->field_smsc); O_DELETE(fields->field_ts); O_DELETE(fields->field_src); O_DELETE(fields->field_dst); O_DELETE(fields->field_serv); O_DELETE(fields->field_url); O_DELETE(fields->field_mask); O_DELETE(fields->field_status); O_DELETE(fields->field_boxc); #undef O_DELETE gw_free(fields); } /* * Initialize specifically dlr storage. If defined storage is unknown * then panic. */ void dlr_init(Cfg* cfg) { CfgGroup *grp; Octstr *dlr_type; /* check which DLR storage type we are using */ grp = cfg_get_single_group(cfg, octstr_imm("core")); if(grp == NULL) panic(0, "DLR: can't find group core"); dlr_type = cfg_get(grp, octstr_imm("dlr-storage")); /* * assume we are using internal memory in case no directive * has been specified, warn the user anyway */ if (dlr_type == NULL) { dlr_type = octstr_imm("internal"); warning(0, "DLR: using default 'internal' for storage type."); } /* call the sub-init routine */ if (octstr_compare(dlr_type, octstr_imm("mysql")) == 0) { handles = dlr_init_mysql(cfg); } else if (octstr_compare(dlr_type, octstr_imm("sdb")) == 0) { handles = dlr_init_sdb(cfg); } else if (octstr_compare(dlr_type, octstr_imm("oracle")) == 0) { handles = dlr_init_oracle(cfg); } else if (octstr_compare(dlr_type, octstr_imm("internal")) == 0) { handles = dlr_init_mem(cfg); } else if (octstr_compare(dlr_type, octstr_imm("pgsql")) == 0) { handles = dlr_init_pgsql(cfg); } /* * add aditional types here */ if (handles == NULL) { panic(0, "DLR: storage type '%s' is not supported!", octstr_get_cstr(dlr_type)); } /* check needed function pointers */ if (handles->dlr_add == NULL || handles->dlr_get == NULL || handles->dlr_remove == NULL) panic(0, "DLR: storage type '%s' don't implement needed functions", octstr_get_cstr(dlr_type)); /* get info from storage */ info(0, "DLR using storage type: %s", handles->type); /* cleanup */ octstr_destroy(dlr_type); } /* * Shutdown dlr storage */ void dlr_shutdown() { if (handles != NULL && handles->dlr_shutdown != NULL) handles->dlr_shutdown(); } /* * Return count waiting delivery entries or -1 if error occurs */ long dlr_messages(void) { if (handles != NULL && handles->dlr_messages != NULL) return handles->dlr_messages(); return -1; } /* * Return type of used dlr storage */ const char* dlr_type(void) { if (handles != NULL && handles->type != NULL) return handles->type; return "unknown"; } /* * Add new dlr entry into dlr storage */ void dlr_add(const Octstr *smsc, const Octstr *ts, const Msg *msg) { struct dlr_entry *dlr = NULL; if(octstr_len(smsc) == 0) { warning(0, "DLR[%s]: Can't add a dlr without smsc-id", dlr_type()); return; } /* sanity check */ if (handles == NULL || handles->dlr_add == NULL || msg == NULL) return; /* check if delivery receipt requested */ if (!DLR_IS_ENABLED(msg->sms.dlr_mask)) return; /* allocate new struct dlr_entry struct */ dlr = dlr_entry_create(); gw_assert(dlr != NULL); /* now copy all values, we are interested in */ dlr->smsc = (smsc ? octstr_duplicate(smsc) : octstr_create("")); dlr->timestamp = (ts ? octstr_duplicate(ts) : octstr_create("")); dlr->source = (msg->sms.sender ? octstr_duplicate(msg->sms.sender) : octstr_create("")); dlr->destination = (msg->sms.receiver ? octstr_duplicate(msg->sms.receiver) : octstr_create("")); dlr->service = (msg->sms.service ? octstr_duplicate(msg->sms.service) : octstr_create("")); dlr->url = (msg->sms.dlr_url ? octstr_duplicate(msg->sms.dlr_url) : octstr_create("")); dlr->boxc_id = (msg->sms.boxc_id ? octstr_duplicate(msg->sms.boxc_id) : octstr_create("")); dlr->mask = msg->sms.dlr_mask; debug("dlr.dlr", 0, "DLR[%s]: Adding DLR smsc=%s, ts=%s, src=%s, dst=%s, mask=%d, boxc=%s", dlr_type(), octstr_get_cstr(dlr->smsc), octstr_get_cstr(dlr->timestamp), octstr_get_cstr(dlr->source), octstr_get_cstr(dlr->destination), dlr->mask, octstr_get_cstr(dlr->boxc_id)); /* call registered function */ handles->dlr_add(dlr); } /* * Return Msg* if dlr entry found in DB, otherwise NULL. * NOTE: If typ is end status (e.g. DELIVERED) then dlr entry * will be removed from DB. */ Msg *dlr_find(const Octstr *smsc, const Octstr *ts, const Octstr *dst, int typ) { Msg *msg = NULL; struct dlr_entry *dlr = NULL; if(octstr_len(smsc) == 0) { warning(0, "DLR[%s]: Can't find a dlr without smsc-id", dlr_type()); return NULL; } /* check if we have handler registered */ if (handles == NULL || handles->dlr_get == NULL) return NULL; debug("dlr.dlr", 0, "DLR[%s]: Looking for DLR smsc=%s, ts=%s, dst=%s, type=%d", dlr_type(), octstr_get_cstr(smsc), octstr_get_cstr(ts), octstr_get_cstr(dst), typ); dlr = handles->dlr_get(smsc, ts, dst); if (dlr == NULL) { warning(0, "DLR[%s]: DLR for DST<%s> not found.", dlr_type(), octstr_get_cstr(dst)); return NULL; } #define O_SET(x, val) if (octstr_len(val) > 0) { x = val; val = NULL; } if ((typ & dlr->mask) > 0) { /* its an entry we are interested in */ msg = msg_create(sms); msg->sms.sms_type = report_mo; msg->sms.dlr_mask = typ; O_SET(msg->sms.service, dlr->service); O_SET(msg->sms.smsc_id, dlr->smsc); O_SET(msg->sms.receiver, dlr->destination); O_SET(msg->sms.sender, dlr->source); /* if dlr_url was present, recode it here again */ O_SET(msg->sms.dlr_url, dlr->url); /* * insert original message to the data segment * later in the smsc module */ msg->sms.msgdata = NULL; /* * If a boxc_id is available, then instruct bearerbox to * route this msg back to originating smsbox */ O_SET(msg->sms.boxc_id, dlr->boxc_id); time(&msg->sms.time); debug("dlr.dlr", 0, "DLR[%s]: created DLR message for URL <%s>", dlr_type(), (msg->sms.dlr_url?octstr_get_cstr(msg->sms.dlr_url):"")); } else { debug("dlr.dlr", 0, "DLR[%s]: Ignoring DLR message because of mask type=%d dlr->mask=%d", dlr_type(), typ, dlr->mask); /* ok that was a status report but we where not interested in having it */ msg = NULL; } #undef O_SET /* check for end status and if so remove from storage */ if ((typ & DLR_BUFFERED) && ((dlr->mask & DLR_SUCCESS) || (dlr->mask & DLR_FAIL))) { info(0, "DLR[%s]: DLR not destroyed, still waiting for other delivery report", dlr_type()); /* update dlr entry status if function defined */ if (handles != NULL && handles->dlr_update != NULL) handles->dlr_update(smsc, ts, dst, typ); } else { if (handles != NULL && handles->dlr_remove != NULL) { /* it's not good for internal storage, but better for all others */ handles->dlr_remove(smsc, ts, dst); } else { warning(0, "DLR[%s]: Storage don't have remove operation defined", dlr_type()); } } /* destroy struct dlr_entry */ dlr_entry_destroy(dlr); return msg; } void dlr_flush(void) { info(0, "Flushing all %ld queued DLR messages in %s storage", dlr_messages(), dlr_type()); if (handles != NULL && handles->dlr_flush != NULL) handles->dlr_flush(); } Msg* create_dlr_from_msg(const Octstr *smsc, const Msg *msg, const Octstr *reply, long stat) { Msg *dlrmsg; if (msg == NULL) return NULL; /* generate DLR */ debug("dlr.dlr", 0,"SMSC[%s]: creating DLR message", (smsc ? octstr_get_cstr(smsc) : "UNKNOWN")); dlrmsg = msg_create(sms); gw_assert(dlrmsg != NULL); dlrmsg->sms.service = octstr_duplicate(msg->sms.service); dlrmsg->sms.dlr_mask = stat; dlrmsg->sms.sms_type = report_mo; dlrmsg->sms.smsc_id = octstr_duplicate(smsc ? smsc : msg->sms.smsc_id); dlrmsg->sms.sender = octstr_duplicate(msg->sms.sender); dlrmsg->sms.receiver = octstr_duplicate(msg->sms.receiver); dlrmsg->sms.dlr_url = octstr_duplicate(msg->sms.dlr_url); dlrmsg->sms.msgdata = octstr_duplicate(reply); dlrmsg->sms.boxc_id = octstr_duplicate(msg->sms.boxc_id); time(&dlrmsg->sms.time); debug("dlr.dlr", 0,"SMSC[%s]: DLR = %s", (smsc ? octstr_get_cstr(smsc) : "UNKNOWN"), (dlrmsg->sms.dlr_url ? octstr_get_cstr(dlrmsg->sms.dlr_url) : "")); return dlrmsg; }