瀏覽代碼

core: relocated some folders from root and utils to misc

- utils keeps only the applications related to kamailio c code
- the other are now in misc/tools
- utils/misc/vim moved to misc/extra/
- obsolete and scripts folders moved to misc/
Daniel-Constantin Mierla 8 年之前
父節點
當前提交
c99c491004
共有 100 個文件被更改,包括 25561 次插入0 次删除
  1. 2 0
      acc_db/.cvsignore
  2. 18 0
      acc_db/Makefile
  3. 1040 0
      acc_db/acc_db.c
  4. 1171 0
      acc_db/acc_db.xml
  5. 16 0
      acc_radius/Makefile
  6. 1093 0
      acc_radius/acc_radius.c
  7. 2 0
      acc_syslog/.cvsignore
  8. 16 0
      acc_syslog/Makefile
  9. 383 0
      acc_syslog/README
  10. 943 0
      acc_syslog/acc_syslog.c
  11. 762 0
      acc_syslog/acc_syslog.xml
  12. 203 0
      acc_syslog/attrs.h
  13. 4 0
      acc_syslog/doc/Makefile
  14. 206 0
      acc_syslog/doc/acc_syslog.xml
  15. 120 0
      acc_syslog/doc/functions.xml
  16. 334 0
      acc_syslog/doc/params.xml
  17. 17 0
      auth_radius/Makefile
  18. 147 0
      auth_radius/README
  19. 313 0
      auth_radius/authorize.c
  20. 52 0
      auth_radius/authorize.h
  21. 204 0
      auth_radius/authrad_mod.c
  22. 50 0
      auth_radius/authrad_mod.h
  23. 4 0
      auth_radius/doc/Makefile
  24. 74 0
      auth_radius/doc/auth_radius.xml
  25. 104 0
      auth_radius/doc/functions.xml
  26. 70 0
      auth_radius/doc/params.xml
  27. 283 0
      auth_radius/sterman.c
  28. 56 0
      auth_radius/sterman.h
  29. 18 0
      avp_radius/Makefile
  30. 85 0
      avp_radius/README
  31. 413 0
      avp_radius/avp_radius.c
  32. 4 0
      avp_radius/doc/Makefile
  33. 47 0
      avp_radius/doc/avp_radius.xml
  34. 47 0
      avp_radius/doc/functions.xml
  35. 46 0
      avp_radius/doc/params.xml
  36. 26 0
      bdb/Makefile
  37. 197 0
      bdb/README
  38. 105 0
      bdb/bdb.c
  39. 68 0
      bdb/bdb.h
  40. 869 0
      bdb/bdb_api.c
  41. 56 0
      bdb/bdb_api.h
  42. 323 0
      bdb/bdb_base.c
  43. 115 0
      bdb/bdb_res.h
  44. 91 0
      bdb/bdb_rval.c
  45. 280 0
      bdb/bdb_sval.c
  46. 223 0
      bdb/bdb_uval.c
  47. 436 0
      bdb/bdb_val.c
  48. 81 0
      bdb/bdb_vals.h
  49. 4 0
      bdb/doc/Makefile
  50. 330 0
      bdb/doc/bdb.xml
  51. 11 0
      bdb/version.dump
  52. 225 0
      cpl-c/CPL_tree.h
  53. 21 0
      cpl-c/Makefile
  54. 250 0
      cpl-c/README
  55. 266 0
      cpl-c/cpl-06.dtd
  56. 949 0
      cpl-c/cpl.c
  57. 206 0
      cpl-c/cpl_db.c
  58. 63 0
      cpl-c/cpl_db.h
  59. 69 0
      cpl-c/cpl_env.h
  60. 234 0
      cpl-c/cpl_loader.c
  61. 45 0
      cpl-c/cpl_loader.h
  62. 108 0
      cpl-c/cpl_log.c
  63. 62 0
      cpl-c/cpl_log.h
  64. 281 0
      cpl-c/cpl_nonsig.c
  65. 73 0
      cpl-c/cpl_nonsig.h
  66. 1605 0
      cpl-c/cpl_parser.c
  67. 37 0
      cpl-c/cpl_parser.h
  68. 543 0
      cpl-c/cpl_proxy.h
  69. 233 0
      cpl-c/cpl_rpc.c
  70. 36 0
      cpl-c/cpl_rpc.h
  71. 1086 0
      cpl-c/cpl_run.c
  72. 101 0
      cpl-c/cpl_run.h
  73. 129 0
      cpl-c/cpl_sig.c
  74. 39 0
      cpl-c/cpl_sig.h
  75. 1111 0
      cpl-c/cpl_switches.h
  76. 1237 0
      cpl-c/cpl_time.c
  77. 181 0
      cpl-c/cpl_time.h
  78. 63 0
      cpl-c/cpl_utils.h
  79. 4 0
      cpl-c/doc/Makefile
  80. 97 0
      cpl-c/doc/cpl-c.xml
  81. 135 0
      cpl-c/doc/functions.xml
  82. 186 0
      cpl-c/doc/params.xml
  83. 3 0
      cpl-c/init.mysql
  84. 183 0
      cpl-c/loc_set.h
  85. 23 0
      cpl-c/ser-cpl.cfg
  86. 81 0
      cpl-c/sub_list.c
  87. 42 0
      cpl-c/sub_list.h
  88. 20 0
      dbtext/Makefile
  89. 282 0
      dbtext/README
  90. 425 0
      dbtext/dbt_api.c
  91. 86 0
      dbtext/dbt_api.h
  92. 607 0
      dbtext/dbt_base.c
  93. 620 0
      dbtext/dbt_file.c
  94. 548 0
      dbtext/dbt_lib.c
  95. 168 0
      dbtext/dbt_lib.h
  96. 576 0
      dbtext/dbt_res.c
  97. 81 0
      dbtext/dbt_res.h
  98. 471 0
      dbtext/dbt_tb.c
  99. 62 0
      dbtext/dbt_util.c
  100. 46 0
      dbtext/dbt_util.h

+ 2 - 0
acc_db/.cvsignore

@@ -0,0 +1,2 @@
+acc_db.7
+

+ 18 - 0
acc_db/Makefile

@@ -0,0 +1,18 @@
+# $Id$
+#
+# acc_syslog - Accounting into syslog
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+
+auto_gen=
+NAME=acc_db.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 1040 - 0
acc_db/acc_db.c

@@ -0,0 +1,1040 @@
+/*
+ * Accounting module
+ *
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2005 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../modules/tm/t_hooks.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../modules/tm/h_table.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/digest/digest.h"
+#include "../../usr_avp.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../usr_avp.h"
+#include "../../lib/srdb2/db.h"
+#include "../../trim.h"
+#include "../../id.h"
+#include "../acc_syslog/attrs.h"
+
+/*
+ * TODO:
+ * - Quote attribute values properly
+ */
+
+/*
+ * a: attr
+ * c: sip_callid
+ * d: to_tag
+ * f: sip_from
+ * g: flags
+ * i: inbound_ruri
+ * m: sip_method
+ * n: sip_cseq
+ * o: outbound_ruri
+ * p: source_ip
+ * r: from_tag
+ * t: sip_to
+ * u: digest_username
+ * x: request_timestamp
+ * D: to_did
+ * F: from_uri
+ * I: from_uid
+ * M: from_did
+ * P: source_port
+ * R: digest_realm
+ * S: sip_status
+ * T: to_uri
+ * U: to_uid
+ * X: response_timestamp
+ */
+
+#define ALL_LOG_FMT "acdfgimnoprstuxDFIMPRSTUX"
+#define ALL_LOG_FMT_LEN (sizeof(ALL_LOG_FMT) - 1)
+
+/* Default column names */
+#define A_ATTRS        "attrs"
+#define A_CALLID       "sip_callid"
+#define A_TOTAG        "to_tag"
+#define A_FROM         "sip_from"
+#define A_FLAGS        "flags"
+#define A_IURI         "in_ruri"
+#define A_METHOD       "sip_method"
+#define A_CSEQ         "sip_cseq"
+#define A_OURI         "out_ruri"
+#define A_FROMTAG      "from_tag"
+#define A_TO           "sip_to"
+#define A_DIGUSER      "digest_username"
+#define A_REQTIMESTAMP "request_timestamp"
+#define A_TODID        "to_did"
+#define A_FROMURI      "from_uri"
+#define A_FROMUID      "from_uid"
+#define A_FROMDID      "from_did"
+#define A_DIGREALM     "digest_realm"
+#define A_STATUS       "sip_status"
+#define A_TOURI        "to_uri"
+#define A_TOUID        "to_uid"
+#define A_RESTIMESTAMP "response_timestamp"
+#define A_SRCIP        "src_ip"
+#define A_SRCPORT      "src_port"
+#define A_SERVER_ID    "server_id"
+
+MODULE_VERSION
+
+struct tm_binds tmb;
+
+static int mod_init(void);
+static void mod_destroy(void);
+static int child_init(int rank);
+static int fix_log_flag( modparam_t type, void* val);
+static int fix_log_missed_flag( modparam_t type, void* val);
+
+static int early_media = 0;         /* Enable/disable early media (183) accounting */
+static int failed_transactions = 0; /* Enable/disable accounting of failed (>= 300) transactions */
+static int report_cancels = 0;      /* Enable/disable CANCEL reporting */
+static int report_ack = 0;          /* Enable/disable end-to-end ACK reports */
+static int log_flag = 0;            /* Flag that marks transactions to be accounted */
+static int log_missed_flag = 0;     /* Transaction having this flag set will be accounted in missed calls when fails */
+static char* log_fmt = ALL_LOG_FMT; /* Formating string that controls what information will be collected and accounted */
+
+#define DEFAULT_ACC_TABLE "acc"
+#define DEFAULT_MC_TABLE "missed_calls"
+
+static str db_url = STR_STATIC_INIT(DEFAULT_DB_URL);
+static str acc_table = STR_STATIC_INIT(DEFAULT_ACC_TABLE);
+static str mc_table = STR_STATIC_INIT(DEFAULT_MC_TABLE);
+
+static str attrs_col = STR_STATIC_INIT(A_ATTRS);
+static str callid_col = STR_STATIC_INIT(A_CALLID);
+static str totag_col = STR_STATIC_INIT(A_TOTAG);
+static str from_col = STR_STATIC_INIT(A_FROM);
+static str flags_col = STR_STATIC_INIT(A_FLAGS);
+static str iuri_col = STR_STATIC_INIT(A_IURI);
+static str method_col = STR_STATIC_INIT(A_METHOD);
+static str cseq_col = STR_STATIC_INIT(A_CSEQ);
+static str ouri_col = STR_STATIC_INIT(A_OURI);
+static str fromtag_col = STR_STATIC_INIT(A_FROMTAG);
+static str to_col = STR_STATIC_INIT(A_TO);
+static str diguser_col = STR_STATIC_INIT(A_DIGUSER);
+static str reqtimestamp_col = STR_STATIC_INIT(A_REQTIMESTAMP);
+static str todid_col = STR_STATIC_INIT(A_TODID);
+static str fromuri_col = STR_STATIC_INIT(A_FROMURI);
+static str fromuid_col = STR_STATIC_INIT(A_FROMUID);
+static str fromdid_col = STR_STATIC_INIT(A_FROMDID);
+static str digrealm_col = STR_STATIC_INIT(A_DIGREALM);
+static str status_col = STR_STATIC_INIT(A_STATUS);
+static str touri_col = STR_STATIC_INIT(A_TOURI);
+static str touid_col = STR_STATIC_INIT(A_TOUID);
+static str restimestamp_col = STR_STATIC_INIT(A_RESTIMESTAMP);
+static str src_ip_col = STR_STATIC_INIT(A_SRCIP);
+static str src_port_col = STR_STATIC_INIT(A_SRCPORT);
+static str server_id_col = STR_STATIC_INIT(A_SERVER_ID);
+
+static db_ctx_t* acc_db = NULL;
+static db_fld_t fld[sizeof(ALL_LOG_FMT) - 1];
+static db_cmd_t* write_mc = NULL, *write_acc = NULL;
+
+
+/* Attribute-value pairs */
+static char* attrs = "";
+avp_ident_t* avps;
+int avps_n;
+
+static int acc_db_request0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_db_missed0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_db_request1(struct sip_msg *rq, char *p1, char *p2);
+static int acc_db_missed1(struct sip_msg *rq, char *p1, char *p2);
+
+static cmd_export_t cmds[] = {
+	{"acc_db_log",    acc_db_request0, 0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_db_missed", acc_db_missed0,  0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_db_log",    acc_db_request1, 1, fixup_var_int_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_db_missed", acc_db_missed1,  1, fixup_var_int_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+static param_export_t params[] = {
+	{"early_media",		PARAM_INT, &early_media },
+	{"failed_transactions",	PARAM_INT, &failed_transactions },
+	{"report_ack",		PARAM_INT, &report_ack },
+	{"report_cancels",	PARAM_INT, &report_cancels },
+	{"log_flag",		PARAM_INT, &log_flag },
+	{"log_flag",		PARAM_STRING|PARAM_USE_FUNC, fix_log_flag},
+	{"log_missed_flag",	PARAM_INT, &log_missed_flag },
+	{"log_missed_flag",	PARAM_STRING|PARAM_USE_FUNC, fix_log_missed_flag},
+	{"log_fmt",		PARAM_STRING, &log_fmt },
+	{"attrs",               PARAM_STRING, &attrs },
+	{"db_url",              PARAM_STR, &db_url },
+	{"acc_table",           PARAM_STR, &acc_table },
+	{"mc_table",            PARAM_STR, &mc_table },
+	{"attrs_column",        PARAM_STR, &attrs_col },
+	{"callid_column",       PARAM_STR, &callid_col },
+	{"totag_column",        PARAM_STR, &totag_col },
+	{"from_column",         PARAM_STR, &from_col },
+	{"flags_column",        PARAM_STR, &flags_col },
+	{"iuri_column",         PARAM_STR, &iuri_col },
+	{"method_column",       PARAM_STR, &method_col },
+	{"cseq_column",         PARAM_STR, &cseq_col },
+	{"ouri_column",         PARAM_STR, &ouri_col },
+	{"fromtag_column",      PARAM_STR, &fromtag_col },
+	{"to_column",           PARAM_STR, &to_col },
+	{"diguser_column",      PARAM_STR, &diguser_col },
+	{"reqtimestamp_column", PARAM_STR, &reqtimestamp_col },
+	{"todid_column",        PARAM_STR, &todid_col },
+	{"fromuri_column",      PARAM_STR, &fromuri_col },
+	{"fromuid_column",      PARAM_STR, &fromuid_col },
+	{"fromdid_column",      PARAM_STR, &fromdid_col },
+	{"digrealm_column",     PARAM_STR, &digrealm_col },
+	{"status_column",       PARAM_STR, &status_col },
+	{"touri_column",        PARAM_STR, &touri_col },
+	{"touid_column",        PARAM_STR, &touid_col },
+	{"restimestamp_column", PARAM_STR, &restimestamp_col },
+	{"src_ip_column",       PARAM_STR, &src_ip_col },
+	{"src_port_column",     PARAM_STR, &src_port_col },
+	{"server_id_column",    PARAM_STR, &server_id_col},
+	{0, 0, 0}
+};
+
+
+struct module_exports exports= {
+	"acc_db",
+	cmds,        /* exported functions */
+	0,           /* RPC methods */
+	params,      /* exported params */
+	mod_init,    /* initialization module */
+	0,	     /* response function */
+	mod_destroy, /* destroy function */
+	0,	     /* oncancel function */
+	child_init   /* per-child init function */
+};
+
+
+
+/* fixes log_flag param (resolves possible named flags) */
+static int fix_log_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_db", "log_flag", &log_flag);
+}
+
+
+
+/* fixes log_missed_flag param (resolves possible named flags) */
+static int fix_log_missed_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_db", "log_missed_flag", &log_missed_flag);
+}
+
+
+
+static inline int skip_cancel(struct sip_msg *msg)
+{
+        return (msg->REQ_METHOD == METHOD_CANCEL) && report_cancels == 0;
+}
+
+static int verify_fmt(char *fmt) {
+
+	if (!fmt) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string zero\n");
+		return -1;
+	}
+
+	if (!(*fmt)) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string empty\n");
+		return -1;
+	}
+
+	if (strlen(fmt) > ALL_LOG_FMT_LEN) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string too long\n");
+		return -1;
+	}
+
+	while(*fmt) {
+		if (!strchr(ALL_LOG_FMT, *fmt)) {
+			LOG(L_ERR, "ERROR:acc:verify_fmt: char in log_fmt invalid: %c\n", *fmt);
+			return -1;
+		}
+		fmt++;
+	}
+	return 1;
+}
+
+
+/*
+ * Return true if accounting is enabled and the
+ * transaction is marked for accounting
+ */
+static inline int is_acc_on(struct sip_msg *rq)
+{
+	return log_flag && isflagset(rq, log_flag) == 1;
+}
+
+
+/*
+ * Return true if missed_call accounting is enabled
+ * and the transaction has the flag set
+ */
+static inline int is_mc_on(struct sip_msg *rq)
+{
+	return log_missed_flag && isflagset(rq, log_missed_flag) == 1;
+}
+
+
+static inline void preparse_req(struct sip_msg *rq)
+{
+	     /* try to parse from for From-tag for accounted transactions;
+	      * don't be worried about parsing outcome -- if it failed,
+	      * we will report N/A. There is no need to parse digest credentials
+	      * here even if we account them, because the authentication function
+	      * will do it before us and if not then we will account n/a.
+	      */
+	parse_headers(rq, HDR_CALLID_F | HDR_FROM_F | HDR_TO_F | HDR_CSEQ_F, 0 );
+	parse_from_header(rq);
+}
+
+
+/* is this reply of interest for accounting ? */
+static inline int should_acc_reply(struct cell* t, int code)
+{
+	struct sip_msg *r;
+
+	r = t->uas.request;
+
+	     /* validation */
+	if (r == 0) {
+		LOG(L_ERR, "ERROR:acc:should_acc_reply: 0 request\n");
+		return 0;
+	}
+
+	     /* negative transactions reported otherwise only if explicitly
+	      * demanded */
+	if (!failed_transactions && code >= 300) return 0;
+	if (!is_acc_on(r)) return 0;
+	if (skip_cancel(r)) return 0;
+	if (code < 200 && ! (early_media && code == 183)) return 0;
+	return 1; /* seed is through, we will account this reply */
+}
+
+
+/* Extract username attribute from authorized credentials */
+static inline str* cred_user(struct sip_msg* rq)
+{
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred || !cred->digest.username.user.len)
+		return 0;
+	return &cred->digest.username.user;
+}
+
+
+/* Extract realm attribute from authorized credentials */
+static inline str* cred_realm(struct sip_msg* rq)
+{
+	str* realm;
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred) return 0;
+	realm = GET_REALM(&cred->digest);
+	if (!realm->len || !realm->s) {
+		return 0;
+	}
+	return realm;
+}
+
+
+/* Return To header field from the request in case of faked reply or
+ * missing To header field in the reply
+ */
+static inline struct hdr_field* valid_to(struct cell* t, struct sip_msg* reply)
+{
+	if (reply == FAKED_REPLY || !reply || !reply->to) {
+		return t->uas.request->to;
+	} else {
+		return reply->to;
+	}
+}
+
+
+static int init_data(char* fmt)
+{
+	int i = 0;
+
+	memset(fld, '\0', sizeof(fld));
+	while(*fmt) {
+		switch(*fmt) {
+		case 'a': /* attr */
+			fld[i].name = attrs_col.s;
+			fld[i].type = DB_STR;
+			break;
+
+		case 'c': /* callid */
+			fld[i].type = DB_STR;
+			fld[i].name = callid_col.s;
+			break;
+
+		case 'd': /* To tag */
+			fld[i].type = DB_STR;
+			fld[i].name = totag_col.s;
+			break;
+
+		case 'f': /* From */
+			fld[i].type = DB_STR;
+			fld[i].name = from_col.s;
+			break;
+
+		case 'g': /* flags */
+			fld[i].type = DB_INT;
+			fld[i].name = flags_col.s;
+			break;
+
+		case 'i': /* Inbound ruri */
+			fld[i].type = DB_STR;
+		    fld[i].name = iuri_col.s;
+			break;
+
+		case 'm': /* method */
+			fld[i].type = DB_STR;
+			fld[i].name = method_col.s;
+			break;
+
+		case 'n': /* sip_cseq */
+			fld[i].type = DB_INT;
+			fld[i].name = cseq_col.s;
+			break;
+
+		case 'o': /* outbound_ruri */
+			fld[i].type = DB_STR;
+			fld[i].name = ouri_col.s;
+			break;
+
+		case 'p': /* src_ip */
+			fld[i].type = DB_INT;
+			fld[i].name = src_ip_col.s;
+			break;
+
+		case 'r': /* from_tag */
+			fld[i].type = DB_STR;
+			fld[i].name = fromtag_col.s;
+			break;
+
+		case 't': /* sip_to */
+			fld[i].type = DB_STR;
+			fld[i].name = to_col.s;
+			break;
+
+		case 'u': /* digest_username */
+			fld[i].type = DB_STR;
+			fld[i].name = diguser_col.s;
+			break;
+
+		case 'x': /* request_timestamp */
+			fld[i].type = DB_DATETIME;
+			fld[i].name = reqtimestamp_col.s;
+			break;
+
+		case 'D': /* to_did */
+			fld[i].type = DB_STR;
+			fld[i].name = todid_col.s;
+			break;
+
+		case 'F': /* from_uri */
+			fld[i].type = DB_STR;
+			fld[i].name = fromuri_col.s;
+			break;
+
+		case 'I': /* from_uid */
+			fld[i].type = DB_STR;
+			fld[i].name = fromuid_col.s;
+			break;
+
+		case 'M': /* from_did */
+		    fld[i].type = DB_STR;
+			fld[i].name = fromdid_col.s;
+			break;
+
+		case 'P': /* src_port */
+			fld[i].type = DB_INT;
+			fld[i].name = src_port_col.s;
+			break;
+
+		case 'R': /* digest_realm */
+			fld[i].type = DB_STR;
+			fld[i].name = digrealm_col.s;
+			break;
+
+		case 'S': /* sip_status */
+			fld[i].type = DB_INT;
+			fld[i].name = status_col.s;
+			break;
+
+		case 'T': /* to_uri */
+			fld[i].type = DB_STR;
+			fld[i].name = touri_col.s;
+			break;
+
+		case 'U': /* to_uid */
+			fld[i].type = DB_STR;
+			fld[i].name = touid_col.s;
+			break;
+
+		case 'X': /* response_timestamp */
+			fld[i].type = DB_DATETIME;
+			fld[i].name = restimestamp_col.s;
+			break;
+
+		case 's': /* server_id */
+			fld[i].type = DB_INT;
+			fld[i].name = server_id_col.s;
+		}
+
+		fmt++;
+		i++;
+	}
+	fld[i].name = NULL;
+	return 0;
+}
+
+
+
+/* create an array of str's for accounting using a formatting string;
+ * this is the heart of the accounting module -- it prints whatever
+ * requested in a way, that can be used for syslog, radius,
+ * sql, whatsoever
+ * tm sip_msg_clones does not clone (shmmem-zed) parsed fields, other then Via1,2. Such fields clone now or use from rq_rp
+ */
+static int fmt2strar(char *fmt,             /* what would you like to account ? */
+					 struct sip_msg *rq,    /* accounted message */
+					 str* ouri,             /* Outbound Request-URI */
+					 struct hdr_field *to,
+					 unsigned int code,
+					 time_t req_time,
+					 db_fld_t* params)       /* Timestamp of the request */
+{
+	int cnt;
+	struct to_body* from, *pto;
+	str *cr, *at;
+	struct cseq_body *cseq;
+
+	cnt = 0;
+
+	     /* we don't care about parsing here; either the function
+	      * was called from script, in which case the wrapping function
+	      * is supposed to parse, or from reply processing in which case
+	      * TM should have preparsed from REQUEST_IN callback; what's not
+	      * here is replaced with NA
+	      */
+	while(*fmt) {
+		if (cnt == ALL_LOG_FMT_LEN) {
+			LOG(L_ERR, "ERROR:acc:fmt2strar: Formatting string is too long\n");
+			return 0;
+		}
+
+		params[cnt].flags &= ~DB_NULL;
+		switch(*fmt) {
+		case 'a': /* attr */
+			at = print_attrs(avps, avps_n, 0);
+			if (!at) {
+				params[cnt].flags |= DB_NULL;
+			} else {
+				params[cnt].v.lstr = *at;
+			}
+			break;
+
+		case 'c': /* sip_callid */
+			if (rq->callid && rq->callid->body.len) {
+				params[cnt].v.lstr = rq->callid->body;
+			} else {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 'd': /* to_tag */
+			if (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) {
+				params[cnt].v.lstr = pto->tag_value;
+			} else {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 'f': /* sip_from */
+			if (rq->from && rq->from->body.len) {
+				params[cnt].v.lstr = rq->from->body;
+			} else {
+				params[cnt].flags |= DB_NULL;				
+			}
+			break;
+
+		case 'g': /* flags */
+			params[cnt].v.int4 = rq->flags;
+			break;
+
+		case 'i': /* inbound_ruri */
+			params[cnt].v.lstr = rq->first_line.u.request.uri;
+			break;
+
+		case 'm': /* sip_method */
+			params[cnt].v.lstr = rq->first_line.u.request.method;
+			break;
+
+		case 'n': /* sip_cseq */
+			if (rq->cseq && (cseq = get_cseq(rq)) && cseq->number.len) {
+				str2int(&cseq->number, (unsigned int*)&params[cnt].v.int4);
+			} else {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 'o': /* outbound_ruri */
+			params[cnt].v.lstr = *ouri;
+			break;
+
+		case 'p':
+			params[cnt].v.int4 = rq->rcv.src_ip.u.addr32[0];
+			break;
+
+		case 'r': /* from_tag */
+			if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
+				params[cnt].v.lstr = from->tag_value;
+			} else {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 't': /* sip_to */
+			if (to && to->body.len) params[cnt].v.lstr = to->body;
+			else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'u': /* digest_username */
+			cr = cred_user(rq);
+			if (cr) params[cnt].v.lstr = *cr;
+			else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'x': /* request_timestamp */
+			params[cnt].v.time = req_time;
+			break;
+
+		case 'D': /* to_did */
+			params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'F': /* from_uri */
+			if (rq->from && (from = get_from(rq)) && from->uri.len) {
+				params[cnt].v.lstr = from->uri;
+			} else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'I': /* from_uid */
+			if (get_from_uid(&params[cnt].v.lstr, rq) < 0) {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 'M': /* from_did */
+			params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'P': /* source_port */
+			params[cnt].v.int4 = rq->rcv.src_port;
+			break;
+
+		case 'R': /* digest_realm */
+			cr = cred_realm(rq);
+			if (cr) params[cnt].v.lstr = *cr;
+			else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 's': /* server_id */
+			params[cnt].v.int4 = server_id;
+			break;
+
+		case 'S': /* sip_status */
+			if (code > 0) params[cnt].v.int4 = code;
+			else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'T': /* to_uri */
+			if (rq->to && (pto = get_to(rq)) && pto->uri.len) params[cnt].v.lstr = pto->uri;
+			else params[cnt].flags |= DB_NULL;
+			break;
+
+		case 'U': /* to_uid */
+			if (get_to_uid(&params[cnt].v.lstr, rq) < 0) {
+				params[cnt].flags |= DB_NULL;
+			}
+			break;
+
+		case 'X': /* response_timestamp */
+			params[cnt].v.time = time(0);
+			break;
+
+		default:
+			LOG(L_CRIT, "BUG:acc:fmt2strar: unknown char: %c\n", *fmt);
+			return 0;
+		} /* switch (*fmt) */
+
+		fmt++;
+		cnt++;
+	} /* while (*fmt) */
+
+	return cnt;
+}
+
+
+int log_request(struct sip_msg *rq, str* ouri, struct hdr_field *to, db_cmd_t* cmd, unsigned int code, time_t req_timestamp)
+{
+	int cnt;
+	if (skip_cancel(rq)) return 1;
+
+	cnt = fmt2strar(log_fmt, rq, ouri, to, code, req_timestamp, cmd->vals);
+	if (cnt == 0) {
+		LOG(L_ERR, "ERROR:acc:log_request: fmt2strar failed\n");
+		return -1;
+	}
+
+	if (!db_url.len) {
+		LOG(L_ERR, "ERROR:acc:log_request: can't log -- no db_url set\n");
+		return -1;
+	}
+
+	if (db_exec(NULL, cmd) < 0) {
+		ERR("Error while inserting to database\n");
+		return -1;
+	}
+
+	return 1;
+}
+
+
+static void log_reply(struct cell* t , struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+	str* ouri;
+	
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+	
+	log_request(t->uas.request, ouri, valid_to(t,reply), write_acc, code, req_time);
+}
+
+
+static void log_ack(struct cell* t , struct sip_msg *ack, time_t req_time)
+{
+	struct sip_msg *rq;
+	struct hdr_field *to;
+
+	rq = t->uas.request;
+	if (ack->to) to = ack->to;
+	else to = rq->to;
+
+	log_request(ack, GET_RURI(ack), to, write_acc, t->uas.status, req_time);
+}
+
+
+static void log_missed(struct cell* t, struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+        str* ouri;
+
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+	
+	log_request(t->uas.request, ouri, valid_to(t, reply), write_mc, code, req_time);
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_db_request0(struct sip_msg *rq, char* s1, char* s2)
+{
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, write_acc, 0, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_db_missed0(struct sip_msg *rq, char* s1, char* s2)
+{
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, write_mc, 0, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_db_request1(struct sip_msg *rq, char* p1, char* p2)
+{
+    int code;
+
+    if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
+	code = 0;
+    }
+    preparse_req(rq);
+    return log_request(rq, GET_RURI(rq), rq->to, write_acc, code, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_db_missed1(struct sip_msg *rq, char* p1, char* p2)
+{
+    int code;
+
+    if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
+	code = 0;
+    }
+    preparse_req(rq);
+    return log_request(rq, GET_RURI(rq), rq->to, write_mc, code, time(0));
+}
+
+
+static void ack_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (is_acc_on(t->uas.request)) {
+		preparse_req(ps->req);
+		log_ack(t, ps->req, (time_t)*(ps->param));
+	}
+}
+
+
+/* initiate a report if we previously enabled MC accounting for this t */
+static void failure_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	/* validation */
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:failure_handler: No uas.request, skipping local transaction\n");
+		return;
+	}
+
+	if (is_invite(t) && ps->code >= 300) {
+		if (is_mc_on(t->uas.request)) {
+			log_missed(t, ps->rpl, ps->code, (time_t)*(ps->param));
+			resetflag(t->uas.request, log_missed_flag);
+		}
+	}
+}
+
+
+/* initiate a report if we previously enabled accounting for this t */
+static void replyout_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:replyout_handler: No uas.request, local transaction, skipping\n");
+		return;
+	}
+
+	     /* acc_onreply is bound to TMCB_REPLY which may be called
+	      * from _reply, like when FR hits; we should not miss this
+	      * event for missed calls either
+	      */
+	failure_handler(t, type, ps);
+	if (!should_acc_reply(t, ps->code)) return;
+	if (is_acc_on(t->uas.request)) log_reply(t, ps->rpl, ps->code, (time_t)*(ps->param));
+}
+
+
+/* parse incoming replies before cloning */
+static void replyin_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	     /* validation */
+	if (t->uas.request == 0) {
+		LOG(L_ERR, "ERROR:acc:replyin_handler:replyin_handler: 0 request\n");
+		return;
+	}
+
+	     /* don't parse replies in which we are not interested */
+	     /* missed calls enabled ? */
+	if (((is_invite(t) && ps->code >= 300 && is_mc_on(t->uas.request))
+	     || should_acc_reply(t, ps->code))
+	    && (ps->rpl && ps->rpl != FAKED_REPLY)) {
+		parse_headers(ps->rpl, HDR_TO_F, 0);
+	}
+}
+
+
+/* prepare message and transaction context for later accounting */
+static void on_req(struct cell* t, int type, struct tmcb_params *ps)
+{
+	time_t req_time;
+	     /* Pass the timestamp of the request as a parameter to callbacks */
+	req_time = time(0);
+
+	if (is_acc_on(ps->req) || is_mc_on(ps->req)) {
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_OUT, replyout_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_OUT callback\n");
+			return;
+		}
+
+		if (report_ack) {
+			if (tmb.register_tmcb(0, t, TMCB_E2EACK_IN, ack_handler,
+									(void*)req_time, 0) <= 0) {
+				LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_E2EACK_IN callback\n");
+				return;
+			}
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_ON_FAILURE_RO, failure_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_ON_FAILURE_RO callback\n");
+			return;
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_IN, replyin_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_IN callback\n");
+			return;
+		}
+
+		     /* do some parsing in advance */
+		preparse_req(ps->req);
+		     /* also, if that is INVITE, disallow silent t-drop */
+		if (ps->req->REQ_METHOD == METHOD_INVITE) {
+			DBG("DEBUG: noisy_timer set for accounting\n");
+			t->flags |= T_NOISY_CTIMER_FLAG;
+		}
+	}
+}
+
+
+static int child_init(int rank)
+{
+	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0; /* do nothing for the main process */
+
+	if (db_url.s) {
+		acc_db = db_ctx("acc_db");
+		if (acc_db == NULL) {
+			ERR("Error while initializing database layer\n");
+			return -1;
+		}
+
+		if (db_add_db(acc_db, db_url.s) < 0) goto error;
+		if (db_connect(acc_db) < 0) goto error;
+
+		write_acc = db_cmd(DB_PUT, acc_db, acc_table.s, NULL, NULL, fld);
+		if (write_acc == NULL) {
+			ERR("Error while compiling database query\n");
+			goto error;
+		}
+
+		write_mc = db_cmd(DB_PUT, acc_db, mc_table.s, NULL, NULL, fld);
+		if (write_mc == NULL) {
+			ERR("Error while compiling database query\n");
+			goto error;
+		}
+
+		return 0;
+	} else {
+		LOG(L_CRIT, "BUG:acc:child_init: null db url\n");
+		return -1;
+	}
+ error:
+	if (write_acc) db_cmd_free(write_acc);
+	write_acc = NULL;
+	if (write_mc) db_cmd_free(write_mc);
+	write_mc = NULL;
+	if (acc_db) db_ctx_free(acc_db);
+	acc_db = NULL;
+	return -1;
+}
+
+
+static void mod_destroy(void)
+{
+	if (write_mc) db_cmd_free(write_mc);
+	if (write_acc) db_cmd_free(write_acc);
+	if (acc_db) {
+		db_disconnect(acc_db);
+		db_ctx_free(acc_db);
+	}
+}
+
+
+static int mod_init(void)
+{
+	load_tm_f load_tm;
+
+	     /* import the TM auto-loading function */
+	if ( !(load_tm = (load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
+		LOG(L_ERR, "ERROR:acc:mod_init: can't import load_tm\n");
+		return -1;
+	}
+	     /* let the auto-loading function load all TM stuff */
+	if (load_tm( &tmb )==-1) return -1;
+	if (verify_fmt(log_fmt)==-1) return -1;
+
+	     /* register callbacks*/
+	     /* listen for all incoming requests  */
+	if (tmb.register_tmcb( 0, 0, TMCB_REQUEST_IN, on_req, 0, 0) <= 0) {
+		LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN "
+		    "callback\n");
+		return -1;
+	}
+
+	init_data(log_fmt);
+
+	if (parse_attrs(&avps, &avps_n, attrs) < 0) {
+		ERR("Error while parsing 'attrs' module parameter\n");
+		return -1;
+	}
+
+	return 0;
+}

+ 1171 - 0
acc_db/acc_db.xml

@@ -0,0 +1,1171 @@
+<?xml version='1.0'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbookid/id/g/4.5/docbookx.dtd">
+
+<refentry xml:id="module.acc_db"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>acc_db</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>acc_db</refname>
+    <refpurpose>Transaction Accounting into a Database</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>acc_db</command> SER module stores information about
+      processed SIP transactions in a database. 
+    </para>
+    <para>
+      Unlike most other modules, <command>acc_db</command> is normally
+      not used by calling one of its functions. Instead, it is
+      invoked by setting a certain flag and then starting a transaction
+      by calling a function from the <serdoc:module>tm</serdoc:module>
+      module, usually <serdoc:func>t_relay</serdoc:func>.
+    </para>
+    <para>
+      There are two flags. Their names are determined by the module
+      parameters <serdoc:modparam module="acc_db">log_flag</serdoc:modparam>
+      and <serdoc:modparam module="acc_db">log_missed_flag</serdoc:modparam>
+      respectively. The former is intended to be used for all transactions
+      that are involved in a successfully established call. If the flag is
+      set, information on the transaction (an "accounting entry") is written
+      to the database table given by the parameter
+      <serdoc:modparam module="acc_db">acc_table</serdoc:modparam>.
+      The second flag is to be used for transactions of a failed call
+      attempt. Information on transactions for which this flag is set are
+      then written to the table given by the
+      <serdoc:modparam module="acc_db">mc_table</serdoc:modparam> (for
+      "missed calls table") parameter.
+    </para>
+    <para>
+      Neither flag is set by default. In order to activate the writing of
+      accounting entries, you have to explicitely choose flags by setting
+      the module parameters.
+    </para>
+    <para>
+      Normally, you can use the same flag for both parameters since
+      <command>acc_db</command>'s internal logic takes care of distinguishing
+      between the two tables. It only writes an accounting entry to the
+      <serdoc:modparam module="acc_db">acc_table</serdoc:modparam> if the
+      transaction ended with a final response sent upstream with a 2xx
+      status, ie., was successful. Likewise, the module only writes to
+      the <serdoc:modparam module="acc_db">mc_table</serdoc:modparam>
+      if the final response sent upstream had a status of 300 or up.
+    </para>
+    <para>
+      If you want to write all accounting entries into one table, you
+      either give the same table name two both
+      <serdoc:modparam module="acc_db">acc_table</serdoc:modparam> and
+      <serdoc:modparam module="acc_db">mc_table</serdoc:modparam> or
+      set the module parameter
+      <serdoc:modparam module="acc_db">failed_transactions</serdoc:modparam>
+      to <literal>yes</literal>. The latter causes the restriction on
+      <serdoc:modparam module="acc_db">acc_table</serdoc:modparam> to be
+      lifted and entries to be written regardless of the status code of
+      the final response.
+    </para>
+    <para>
+      Note that the <command>acc_db</command> module only writes accounting
+      entries for individual transactions. A call usually consists at least
+      of an INVITE and a BYE transaction for the start and end of a call.
+      Searching the accounting records for the call and translating them
+      into call detail records is not performed by the module.
+    </para>
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_db.functions">
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.acc_db_log">
+      <title>
+        <function>acc_db_log</function>
+        ([<symbol>status</symbol>])
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>acc_db_log()</function> forces
+        <command>acc_db</command> to write information taken from the
+        currently processed request into the database table determined
+        by the parameter
+        <serdoc:modparam module="acc_db">acc_table</serdoc:modparam>.
+      </para>
+      <para>
+        If the argument <symbol>status</symbol> is given, it contains the
+        status code that should be stored in the field
+        <varname>sip_status</varname>. If it is missing, the field will
+        be set to <literal>0</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.acc_db_missed">
+      <title>
+        <function>acc_db_missed</function>
+        ([<symbol>status</symbol>])
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>acc_db_missed()</function> forces
+        <command>acc_db</command> to write information taken from the
+        currently processed request into the database table determined
+        by the parameter
+        <serdoc:modparam module="acc_db">mc_table</serdoc:modparam>.
+      </para>
+      <para>
+        If the argument <symbol>status</symbol> is given, it contains the
+        status code that should be stored in the field
+        <varname>sip_status</varname>. If it is missing, the field will
+        be set to <literal>0</literal>.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_db.parameters">
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.acc_db.acc_table">
+      <title><parameter>acc_table</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>acc</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>acc_table</parameter> parameter sets the name
+        of the database table into which information for successfully
+        established calls should be written. Transaction belonging to
+        such a call are indicated by the flag named through the parameter
+        <serdoc:modparam module="acc_db">log_flag</serdoc:modparam> being
+        set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.attrs">
+      <title><parameter>attrs</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>attrs</parameter> parameter contains a comma 
+        separated list of those attributes whose values should be written
+        to the database. See the description of the field
+        <serdoc:field table="acc">attrs</serdoc:field> for information on
+        how these values are stored in the database.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.attrs_column">
+      <title><parameter>attrs_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"attrs"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>attrs_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">attrs</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.callid_column">
+      <title><parameter>callid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_callid"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>callid_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_callid</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.cseq_column">
+      <title><parameter>cseq_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_cseq"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>cseq_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_cseq</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.db_url">
+      <title><parameter>db_url</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>
+          mysql://ser:heslo@localhost/ser
+        </serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>db_url</parameter> parameter contains the URL used
+        to connect to the database. The scheme identifies the database module
+        in use. Check the reference for the database you intend to use
+        for more information.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.digreal_column">
+      <title><parameter>digrealm_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"digest_realm"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>digrealm_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">digest_realm</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.diguser_column">
+      <title><parameter>diguser_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"digest_username"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>diguser_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">digest_user</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.early_media">
+      <title><parameter>early_media</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>early_media</parameter> parameter determines,
+        whether the arrival of a reponse 183 (Session Progress) should
+        trigger the writing of information, too. If the parameter is
+        set to <literal>yes</literal>,
+        a 183 response will be treated as a successful response and
+        information stored in the
+        <serdoc:modparam module="acc_db">acc_table</serdoc:modparam> if
+        the <serdoc:modparam module="acc_db">log_flag</serdoc:modparam> is
+        set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.failed_transactions">
+      <title><parameter>failed_transactions</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>failed_transactions</parameter> parameter determines,
+        whether transaction with a final response indicating a failure
+        (ie., status codes of 300 and up) should be accounted too if the
+        <serdoc:modparam module="acc_db">log_flag</serdoc:modparam> is set.
+      </para>
+      <para>
+        The value of the <parameter>failed_transactions</parameter> parameter
+        has no influence on accounting if the
+        <serdoc:modparam module="acc_db">log_missed_flag</serdoc:modparam>
+        is set, in which case the transaction will be accounted for only
+        if the final response indicates a failure.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.flags_column">
+      <title><parameter>flags_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"flags"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>flags_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">flags</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.from_column">
+      <title><parameter>from_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_from"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>from_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_from</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.fromdid_column">
+      <title><parameter>fromdid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>from_did</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>fromdid_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">from_did</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.fromtag_column">
+      <title><parameter>fromtag_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"from_tag"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>fromtag_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">from_tag</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.fromuid_column">
+      <title><parameter>fromuid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"from_uid"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>fromuid_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">from_uid</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.fromuri_column">
+      <title><parameter>fromuri_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"from_uri"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>fromuri_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">from_uri</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.iuri_column">
+      <title><parameter>iuri_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"in_ruri"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>iuri_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">in_ruri</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.log_flag">
+      <title><parameter>log_flag</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>flag name</serdoc:paramtype>
+        <serdoc:paramdefault>none</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_flag</parameter> parameter sets the name of the
+        flag that decides whether information about a succeeeded
+        transaction (ie., one with a final response from the 2xx group) is
+        to be stored in the
+        <serdoc:modparam module="acc_db">acc_table</serdoc:modparam>.
+      </para>
+      <para>
+        By setting the
+        <serdoc:modparam module="acc_db">early_media</serdoc:modparam>
+        and
+        <serdoc:modparam module="acc_db">failed_transactions</serdoc:modparam>
+        to <literal>yes</literal> setting the <parameter>log_flag</parameter>
+        also triggers writing for 183 provisional responses and failed
+        transactions respectively.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.log_fmt">
+      <title><parameter>log_fmt</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"acdfgimnoprstuxDFIMPRSTUX"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_fmt</parameter> parameter determines, which
+        information is written to the database. Its value is a string.
+        The various fields described in the section
+        <serdoc:link linkend="module.acc_db.database">Database
+        Schema</serdoc:link> below are assigned a letter. If the letter
+        for the field is included in the string, the field will be written.
+      </para>
+      <para>
+        The mapping between letters and fields is as follows:
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><literal>a</literal></term>
+          <listitem><serdoc:field table="acc">attr</serdoc:field></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>c</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_callid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>d</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_tag</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>f</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_from</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>g</literal></term>
+          <listitem>
+            <serdoc:field table="acc">flags</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>i</literal></term>
+          <listitem>
+            <serdoc:field table="acc">in_ruri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>m</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_method</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>n</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_cseq</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>o</literal></term>
+          <listitem>
+            <serdoc:field table="acc">out_ruri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>p</literal></term>
+          <listitem>
+            <serdoc:field table="acc">src_ip</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>r</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_tag</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>t</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_to</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>u</literal></term>
+          <listitem>
+            <serdoc:field table="acc">digest_username</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>x</literal></term>
+          <listitem>
+            <serdoc:field table="acc">request_timestamp</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>D</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_did</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>F</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_uri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>I</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_uid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>M</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_did</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>P</literal></term>
+          <listitem>
+            <serdoc:field table="acc">src_port</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>R</literal></term>
+          <listitem>
+            <serdoc:field table="acc">digest_realm</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>S</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_status</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>T</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_uri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>U</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_uid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>X</literal></term>
+          <listitem>
+            <serdoc:field table="acc">response_timestamp</serdoc:field>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+      <para>
+        By default, all of the fields are active.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.log_missed_flag">
+      <title><parameter>log_missed_flag</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>flag name</serdoc:paramtype>
+        <serdoc:paramdefault>none</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_missed_flag</parameter> parameter determines the
+        flag which control whether information on a failed transaction
+        (ie., one with a final response with status code 300 or up) is to be
+        written to the
+        <serdoc:modparam module="acc_db">mc_table</serdoc:modparam> table.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.mc_table">
+      <title><parameter>mc_table</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>missed_calls</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>mc_table</parameter> parameter sets the name of the
+        database table where information on transaction for failed call
+        attempts should be stored.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.method_column">
+      <title><parameter>method_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_method"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>method_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_method</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.ouri_column">
+      <title><parameter>ouri_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"out_ruri"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>ouri_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">out_ruri</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.report_ack">
+      <title><parameter>report_ack</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>report_ack</parameter> parameter determines,
+        whether a separate accounting entry should be written for the
+        ACK following a 200 OK response.
+      </para>
+      <para>
+        Usually, having the entry for the first transaction is enough
+        and no additional entry is necessary. There is, however, a chance
+        that the ACK does not reach its destination and the call does
+        in fact not start. If you need to know about those cases, you can
+        enable the <parameter>report_ack</parameter> parameter and check
+        that there is an ACK for every INVITE.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.report_cancels">
+      <title><parameter>report_cancels</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>report_cancels</parameter> parameter determines,
+        whether accounting entries should be made for CANCEL transactions.
+      </para>
+      <para>
+        You can recognize a canceled transaction by its status 487 in the
+        <serdoc:field table="acc">sip_status</serdoc:field> field.
+        Because of this, there is usually no need for the extra entries
+        the CANCEL transaction itself may create.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.reqtimestamp_column">
+      <title><parameter>reqtimestamp_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"request_timestamp"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>reqtimestamp_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">request_timestamp</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.restimestamp_column">
+      <title><parameter>restimestamp_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"response_timestamp"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>restimestamp_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">response_timestamp</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.server_id_column">
+      <title><parameter>server_id_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"server_id"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>server_id_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">server_id_column</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.src_ip_column">
+      <title><parameter>src_ip_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"src_ip"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>src_ip_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">src_ip</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.src_port_column">
+      <title><parameter>src_port_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>src_port</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>src_port_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">src_port</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.status_column">
+      <title><parameter>status_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_status"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>status_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_status</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.to_column">
+      <title><parameter>to_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"sip_to"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>to_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">sip_to</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.todid_column">
+      <title><parameter>todid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"to_did"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>todid_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">to_did</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.totag_column">
+      <title><parameter>totag_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"to_tag"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>totag_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">to_tag</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.touid_column">
+      <title><parameter>touid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"to_uid"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>touid_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">to_uid</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_db.touri_column">
+      <title><parameter>touri_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"to_uri"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>touri_column</parameter> parameter contains
+        the name of the database column for the
+        <serdoc:field table="acc">to_uri</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <!--
+    <refsect2 xml:id="module.acc_db.">
+      <title><parameter></parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype></serdoc:paramtype>
+        <serdoc:paramdefault></serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <serdoc:todo />
+    </refsect2>
+    -->
+
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_db.database">
+    <title>Database Scheme</title>
+
+    <!-- XXX Sort alphabetically. -->
+
+    <refsect2 xml:id="table.acc.id">
+      <title><varname>id</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT AUTO_INCREMENT NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>id</varname> field is not directly written to by the
+        <command>acc_db</command>. Instead, as an auto-increment database
+        field, it is set by the database itself and gives every entry its
+        own unique numeric identifier.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.server_id">
+      <title><varname>server_id</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT NOT NULL DEFAULT '0'</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>server_id</varname> field will contain the server ID
+        of the SER instance that processed the transaction. This is useful
+        in a cluster of several SER machines. By giving each machine its
+        own
+        <serdoc:link linkend="core.parameter.server_id">server_id</serdoc:link>
+        you can later determine, which server the accounting entry
+        originated from.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.from_uid">
+      <title><varname>from_uid</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>from_uid</varname> field will contain the user ID
+        determined for the callee and stored in the
+        <varname>$fu.uid</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.to_uid">
+      <title><varname>to_uid</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>to_uid</varname> field will contain the user ID
+        determined for the caller and stored in the
+        <varname>$tu.uid</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.to_did">
+      <title><varname>to_did</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>to_did</varname> field will contain the domain ID
+        determined for the caller's domain and stored in the
+        <varname>$td.did</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.from_did">
+      <title><varname>from_did</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>from_did</varname> field will contain the domain ID
+        determined for the callee's domain and stored in the
+        <varname>$fd.did</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_from">
+      <title><varname>sip_from</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_from</varname> field will contain the content
+        of the From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_to">
+      <title><varname>sip_to</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_to</varname> field will contain the content of the
+        To header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_status">
+      <title><varname>sip_status</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_status</varname> field will contain the status code
+        of the final response or, if
+        <serdoc:modparam module="acc_db">early_media</serdoc:modparam> is set,
+        183 response transmitted upstream for the transaction.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_method">
+      <title><varname>sip_method</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(16)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_method</varname> field will contain the method
+        of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.in_ruri">
+      <title><varname>in_ruri</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>in_ruri</varname> field will contain the Request-URI
+        the request arrived with.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.out_ruri">
+      <title><varname>out_ruri</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>out_ruri</varname> field will contain the Request-URI
+        of the winning branch, ie., of that relayed request which caused
+        the final response that was sent upstram.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.from_uri">
+      <title><varname>from_uri</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>from_uri</varname> field will contain the URI of the
+        From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.to_uri">
+      <title><varname>to_uri</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>to_uri</varname> field will contain the URI of the
+        To header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_callid">
+      <title><varname>sip_callid</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_callid</varname> field will contain the content
+        of the Call-ID header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.sip_cseq">
+      <title><varname>sip_cseq</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>sip_cseq</varname> field will contain the sequence
+        number contained in the CSeq header field of the request. The
+        method in that header field (which should be identical to the
+        method of the request) can be found in the
+        <serdoc:field table="acc">sip_method</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.digest_username">
+      <title><varname>digest_username</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>digest_username</varname> field will contain the
+        username used in authenticating the request. If no
+        authentication was done or the authentication failed, the field
+        will be <literal>NULL</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.digest_realm">
+      <title><varname>digest_realm</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>digest_realm</varname> field will contain the realm
+        used in authenticating the request. If no authentication was done
+        or it failed, the field will be <literal>NULL</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.from_tag">
+      <title><varname>from_tag</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(128)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>from_tag</varname> field will contain the value of the
+        tag parameter of the From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.to_tag">
+      <title><varname>to_tag</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(128)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>to_tag</varname> field will contain the value of the
+        tag parameter of the response sent upstream or <literal>NULL</literal>
+        if the response was missing this parameter.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.src_ip">
+      <title><varname>src_ip</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT UNSIGNED</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>src_ip</varname> field will contain the source IP
+        address of the received request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.src_port">
+      <title><varname>src_port</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>SMALLINT UNSIGNED</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>src_port</varname> field will contain the source port
+        of the received request.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.request_timestamp">
+      <title><varname>request_timestamp</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>DATETIME NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>request_timestamp</varname> field will contain the
+        date and time when the request was received, ie., when the
+        transaction started.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.response_timestamp">
+      <title><varname>response_timestamp</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>DATETIME NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>response_timestamp</varname> field will contain the
+        date and time when the response was sent upstream, ie. when the
+        transaction ended.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.flags">
+      <title><varname>flags</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT UNSIGNED NOT NULL DEFAULT '0'</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>flags</varname> field will contain the combined
+        numerical value of the flags that where set for the transaction
+        when the entry was written. The value is determined by treating
+        the flags as a bit field with the flag's number as the number of
+        the corresponding bit.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.acc.attrs">
+      <title><varname>attrs</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>attrs</varname> field will contain the attribute and
+        their values that have been selected for storing with the accounting
+        entry.
+      </para>
+      <para>
+        The field will contain a comma separated list of entries. Each
+        entry will start with the name of the attribute, followed by a
+        colon, followed by its value enclosed in double quotation marks.
+        Which attributes will be in the list is controlled by the
+        <serdoc:modparam module="acc_db">attrs</serdoc:modparam> module
+        parameter.
+      </para>
+    </refsect2>
+
+    <!--
+    <refsect2 xml:id="table.acc.">
+      <title><varname></varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql></serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <serdoc:todo />
+    </refsect2>
+
+    -->
+  </refsect1>
+
+  <refsect1 role="manpage">
+    <title>See Also</title>
+    <simplelist type="inline">
+      <member><serdoc:sbin>ser</serdoc:sbin></member>
+      <member><serdoc:file>ser.cfg</serdoc:file></member>
+      <member><serdoc:module>acc_radius</serdoc:module></member>
+      <member><serdoc:module>acc_syslog</serdoc:module></member>
+      <member><serdoc:module>tm</serdoc:module></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+<!-- vim:sw=2 sta et sts=2 ai
+  -->

+ 16 - 0
acc_radius/Makefile

@@ -0,0 +1,16 @@
+# $Id$
+#
+# RADIUS Accounting module
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+include ../../Makefile.radius
+
+auto_gen=
+NAME=acc_radius.so
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 1093 - 0
acc_radius/acc_radius.c

@@ -0,0 +1,1093 @@
+/*
+ * Accounting module
+ *
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2005 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <radiusclient-ng.h>
+#include "../../rad_dict.h"
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../modules/tm/t_hooks.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../modules/tm/h_table.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/digest/digest.h"
+#include "../../usr_avp.h"
+#include "../../id.h"
+#include "../../modules/tm/tm_load.h"
+
+#include "../../parser/parse_rr.h"
+#include "../../trim.h"
+#include "../acc_syslog/attrs.h"
+
+/*
+ * FIXME:
+ * - Quote attribute values properly
+ * - Explicitly called accounting function will generate Acct-Status-Type
+ *   set to failed (rad_status does not work properly in this case), it
+ *   should generate Interim-Update
+ * - INVITEs with to tag should generate Interim-Update
+ * - Configurable option which allows to switch From/To based on the
+ *   value of ftag route parameter
+ */
+
+/*
+ * a: attr
+ * c: sip_callid
+ * d: to_tag
+ * f: sip_from
+ * g: flags
+ * i: inbound_ruri
+ * m: sip_method
+ * n: sip_cseq
+ * o: outbound_ruri
+ * p: source_ip
+ * r: from_tag
+ * s: server_id
+ * t: sip_to
+ * u: digest_username
+ * x: request_timestamp
+ * D: to_did
+ * F: from_uri
+ * I: from_uid
+ * M: from_did
+ * R: digest_realm
+ * P: source_port
+ * S: sip_status
+ * T: to_uri
+ * U: to_uid
+ * X: response_timestamp
+ */
+
+
+#define ALL_LOG_FMT "acdfgimnoprstuxDFIMPRSTUX"
+#define ALL_LOG_FMT_LEN (sizeof(ALL_LOG_FMT) - 1)
+
+MODULE_VERSION
+
+struct tm_binds tmb;
+
+static int mod_init( void );
+static int fix_log_flag( modparam_t type, void* val);
+static int fix_log_missed_flag( modparam_t type, void* val);
+
+static int early_media = 0;         /* Enable/disable early media (183) accounting */
+static int failed_transactions = 0; /* Enable/disable accounting of failed (>= 300) transactions */
+static int report_cancels = 0;      /* Enable/disable CANCEL reporting */
+static int report_ack = 0;          /* Enable/disable end-to-end ACK reports */
+static int log_flag = 0;            /* Flag that marks transactions to be accounted */
+static int log_missed_flag = 0;     /* Transaction having this flag set will be accounted in missed calls when fails */
+static char* log_fmt = ALL_LOG_FMT; /* Formating string that controls what information will be collected and accounted */
+
+/* Attribute-value pairs */
+static char* attrs_param = "";
+avp_ident_t* avps;
+int avps_n;
+
+static char *radius_config = "/usr/local/etc/radiusclient-ng/radiusclient.conf";
+static int service_type = -1;
+static int swap_dir = 0;
+
+static void *rh;
+static struct attr attrs[A_MAX];
+static struct val vals[V_MAX];
+
+static int acc_rad_request0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_rad_missed0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_rad_request1(struct sip_msg *rq, char *p1, char *p2);
+static int acc_rad_missed1(struct sip_msg *rq, char *p1, char *p2);
+
+static cmd_export_t cmds[] = {
+	{"acc_rad_log",    acc_rad_request0, 0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_rad_missed", acc_rad_missed0,  0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_rad_log",    acc_rad_request1, 1, fixup_var_int_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_rad_missed", acc_rad_missed1,  1, fixup_var_int_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+static param_export_t params[] = {
+	{"early_media",		PARAM_INT, &early_media         },
+	{"failed_transactions",	PARAM_INT, &failed_transactions },
+	{"report_ack",		PARAM_INT, &report_ack          },
+	{"report_cancels",	PARAM_INT, &report_cancels 	},
+	{"log_flag",		PARAM_INT, &log_flag         	},
+	{"log_flag",		PARAM_STRING|PARAM_USE_FUNC, fix_log_flag},
+	{"log_missed_flag",	PARAM_INT, &log_missed_flag	},
+	{"log_missed_flag",	PARAM_STRING|PARAM_USE_FUNC, fix_log_missed_flag},
+	{"log_fmt",		PARAM_STRING, &log_fmt          },
+	{"attrs",               PARAM_STRING, &attrs_param      },
+	{"radius_config",	PARAM_STRING, &radius_config	},
+	{"service_type", 	PARAM_INT, &service_type        },
+	{"swap_direction",      PARAM_INT, &swap_dir            },
+	{0, 0, 0}
+};
+
+
+struct module_exports exports= {
+	"acc_radius",
+	cmds,     /* exported functions */
+	0,        /* RPC methods */
+	params,   /* exported params */
+	mod_init, /* initialization module */
+	0,	  /* response function */
+	0,        /* destroy function */
+	0,	  /* oncancel function */
+	0         /* per-child init function */
+};
+
+
+
+/* fixes log_flag param (resolves possible named flags) */
+static int fix_log_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_radius", "log_flag", &log_flag);
+}
+
+
+
+/* fixes log_missed_flag param (resolves possible named flags) */
+static int fix_log_missed_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_radius", "log_missed_flag", &log_missed_flag);
+}
+
+
+
+static inline int skip_cancel(struct sip_msg *msg)
+{
+        return (msg->REQ_METHOD == METHOD_CANCEL) && report_cancels == 0;
+}
+
+static int check_ftag(struct sip_msg* msg, str* uri)
+{
+	param_hooks_t hooks;
+	param_t* params;
+	char* semi;
+	struct to_body* from;
+	str t;
+
+	t = *uri;
+	params = 0;
+	semi = q_memchr(t.s, ';', t.len);
+	if (!semi) {
+		DBG("No ftag parameter found\n");
+		return -1;
+	}
+
+	t.len -= semi - uri->s + 1;
+	t.s = semi + 1;
+	trim_leading(&t);
+
+	if (parse_params(&t, CLASS_URI, &hooks, &params) < 0) {
+		ERR("Error while parsing parameters\n");
+		return -1;
+	}
+
+	if (!hooks.uri.ftag) {
+		DBG("No ftag parameter found\n");
+		goto err;
+	}
+
+	from = get_from(msg);
+
+	if (!from || !from->tag_value.len || !from->tag_value.s) {
+		DBG("No from tag parameter found\n");
+		goto err;
+	}
+
+	if (from->tag_value.len == hooks.uri.ftag->body.len &&
+	    !strncmp(from->tag_value.s, hooks.uri.ftag->body.s, hooks.uri.ftag->body.len)) {
+		DBG("Route ftag and From tag are same\n");
+		free_params(params);
+		return 0;
+	} else {
+		DBG("Route ftag and From tag are NOT same\n");
+		free_params(params);
+		return 1;
+	}
+
+ err:
+	if (params) free_params(params);
+	return -1;
+}
+
+static int get_direction(struct sip_msg* msg)
+{
+	int ret;
+	if (parse_orig_ruri(msg) < 0) {
+		return -1;
+	}
+
+	if (!msg->parsed_orig_ruri_ok) {
+		ERR("Error while parsing original Request-URI\n");
+		return -1;
+	}
+
+	ret = check_self(&msg->parsed_orig_ruri.host,
+			 msg->parsed_orig_ruri.port_no ? msg->parsed_orig_ruri.port_no : SIP_PORT, 0);/* match all protos*/
+	if (ret < 0) return -1;
+	if (ret > 0) {
+		     /* Route is in ruri */
+		return check_ftag(msg, &msg->first_line.u.request.uri);
+	} else {
+		if (msg->route) {
+			if (parse_rr(msg->route) < 0) {
+				ERR("Error while parsing Route HF\n");
+				return -1;
+			}
+		        ret = check_ftag(msg, &((rr_t*)msg->route->parsed)->nameaddr.uri);
+			if (msg->route->parsed) free_rr((rr_t**)(void*)&msg->route->parsed);
+			return ret;
+		} else {
+			DBG("No Route headers found\n");
+			return -1;
+		}
+	}
+}
+
+
+int verify_fmt(char *fmt) {
+
+	if (!fmt) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string zero\n");
+		return -1;
+	}
+
+	if (!(*fmt)) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string empty\n");
+		return -1;
+	}
+
+	if (strlen(fmt) > ALL_LOG_FMT_LEN) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string too long\n");
+		return -1;
+	}
+
+	while(*fmt) {
+		if (!strchr(ALL_LOG_FMT, *fmt)) {
+			LOG(L_ERR, "ERROR:acc:verify_fmt: char in log_fmt invalid: %c\n", *fmt);
+			return -1;
+		}
+		fmt++;
+	}
+	return 1;
+}
+
+
+/*
+ * Return true if accounting is enabled and the
+ * transaction is marked for accounting
+ */
+static inline int is_acc_on(struct sip_msg *rq)
+{
+	return log_flag && isflagset(rq, log_flag) == 1;
+}
+
+
+/*
+ * Return true if missed_call accounting is enabled
+ * and the transaction has the flag set
+ */
+static inline int is_mc_on(struct sip_msg *rq)
+{
+	return log_missed_flag && isflagset(rq, log_missed_flag) == 1;
+}
+
+
+static inline void preparse_req(struct sip_msg *rq)
+{
+	     /* try to parse from for From-tag for accounted transactions;
+	      * don't be worried about parsing outcome -- if it failed,
+	      * we will report N/A. There is no need to parse digest credentials
+	      * here even if we account them, because the authentication function
+	      * will do it before us and if not then we will account n/a.
+	      */
+	parse_headers(rq, HDR_CALLID_F | HDR_FROM_F | HDR_TO_F | HDR_CSEQ_F | HDR_ROUTE_F, 0 );
+	parse_from_header(rq);
+}
+
+
+/* is this reply of interest for accounting ? */
+static inline int should_acc_reply(struct cell* t, int code)
+{
+	struct sip_msg *r;
+
+	r = t->uas.request;
+
+	     /* validation */
+	if (r == 0) {
+		LOG(L_ERR, "ERROR:acc:should_acc_reply: 0 request\n");
+		return 0;
+	}
+
+	     /* negative transactions reported otherwise only if explicitly
+	      * demanded */
+	if (!failed_transactions && code >= 300) return 0;
+	if (!is_acc_on(r)) return 0;
+	if (skip_cancel(r)) return 0;
+	if (code < 200 && ! (early_media && code == 183)) return 0;
+	return 1; /* seed is through, we will account this reply */
+}
+
+
+/* Extract username attribute from authorized credentials */
+static inline str* cred_user(struct sip_msg* rq)
+{
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred || !cred->digest.username.user.len)
+		return 0;
+	return &cred->digest.username.user;
+}
+
+
+/* Extract realm attribute from authorized credentials */
+static inline str* cred_realm(struct sip_msg* rq)
+{
+	str* realm;
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred) return 0;
+	realm = GET_REALM(&cred->digest);
+	if (!realm->len || !realm->s) {
+		return 0;
+	}
+	return realm;
+}
+
+
+/* Return To header field from the request in case of faked reply or
+ * missing To header field in the reply
+ */
+static inline struct hdr_field* valid_to(struct cell* t, struct sip_msg* reply)
+{
+	if (reply == FAKED_REPLY || !reply || !reply->to) {
+		return t->uas.request->to;
+	} else {
+		return reply->to;
+	}
+}
+
+
+/* create an array of str's for accounting using a formatting string;
+ * this is the heart of the accounting module -- it prints whatever
+ * requested in a way, that can be used for syslog, radius,
+ * sql, whatsoever
+ * tm sip_msg_clones does not clone (shmmem-zed) parsed fields, other then Via1,2. Such fields clone now or use from rq_rp
+ */
+static int fmt2rad(char *fmt,
+		   struct sip_msg *rq,
+		   str* ouri,
+		   struct hdr_field *to,
+		   unsigned int code,
+		   VALUE_PAIR** send,
+		   time_t req_time)       /* Timestamp of the request */
+{
+	static unsigned int cseq_num, src_port, src_ip;
+	static time_t rq_time, rs_time;
+	int cnt;
+	struct to_body* from, *pto;
+	str val, *cr, *at;
+	struct cseq_body *cseq;
+	struct attr* attr;
+	int dir;
+
+	cnt = 0;
+	dir = -2;
+
+	     /* we don't care about parsing here; either the function
+	      * was called from script, in which case the wrapping function
+	      * is supposed to parse, or from reply processing in which case
+	      * TM should have preparsed from REQUEST_IN callback.
+	      */
+	while(*fmt) {
+		if (cnt == ALL_LOG_FMT_LEN) {
+			LOG(L_ERR, "ERROR:acc:fmt2rad: Formatting string is too long\n");
+			return 0;
+		}
+
+		attr = 0;
+		switch(*fmt) {
+		case 'a': /* attr */
+			at = print_attrs(avps, avps_n, 0);
+			if (at) {
+				attr = &attrs[A_SER_ATTR];
+				val = *at;
+			}
+			break;
+
+		case 'c': /* sip_callid */
+			if (rq->callid && rq->callid->body.len) {
+				attr = &attrs[A_ACCT_SESSION_ID];
+				val = rq->callid->body;
+			}
+			break;
+
+		case 'd': /* to_tag */
+			if (swap_dir && dir == -2) dir = get_direction(rq);
+			if (dir <= 0) {
+				if (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) {
+					attr = &attrs[A_SIP_TO_TAG];
+					val = pto->tag_value;
+				}
+			} else {
+				if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
+					attr = &attrs[A_SIP_TO_TAG];
+					val = from->tag_value;
+				}
+			}
+			break;
+
+		case 'f': /* sip_from */
+			if (rq->from && rq->from->body.len) {
+				attr = &attrs[A_SER_FROM];
+				val = rq->from->body;
+			}
+			break;
+
+		case 'g': /* flags */
+			attr = &attrs[A_SER_FLAGS];
+			val.s = (char*)&rq->flags;
+			val.len = sizeof(unsigned int);
+			break;
+
+		case 'i': /* inbound_ruri */
+			attr = &attrs[A_SER_ORIGINAL_REQUEST_ID];
+			val = rq->first_line.u.request.uri;
+			break;
+
+		case 'm': /* sip_method */
+			attr = &attrs[A_SIP_METHOD];
+			val = rq->first_line.u.request.method;
+			break;
+
+		case 'n': /* sip_cseq */
+			if (rq->cseq && (cseq = get_cseq(rq)) && cseq->number.len) {
+				attr = &attrs[A_SIP_CSEQ];
+				str2int(&cseq->number, &cseq_num);
+				val.s = (char*)&cseq_num;
+				val.len = sizeof(unsigned int);
+			}
+			break;
+
+		case 'o': /* outbound_ruri */
+			attr = &attrs[A_SIP_TRANSLATED_REQUEST_ID];
+			val = *ouri;
+			break;
+
+		case 'p': /* Source IP address */
+			attr = &attrs[A_SIP_SOURCE_IP_ADDRESS];
+			src_ip = ntohl(rq->rcv.src_ip.u.addr32[0]);
+			val.s = (char*)&src_ip;
+			val.len = sizeof(src_ip);
+			break;
+
+		case 'r': /* from_tag */
+			if (swap_dir && dir == -2) dir = get_direction(rq);
+			if (dir <= 0) {
+				if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
+					attr = &attrs[A_SIP_FROM_TAG];
+					val = from->tag_value;
+				}
+			} else {
+				if (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) {
+					attr = &attrs[A_SIP_FROM_TAG];
+					val = pto->tag_value;
+				}
+			}
+			break;
+
+		case 's': /* server_id */
+			attr = &attrs[A_SER_SERVER_ID];
+			val.s = (char*)&server_id;
+			val.len = sizeof(int);
+			break;
+
+		case 't': /* sip_to */
+			if (to && to->body.len) {
+				attr = &attrs[A_SER_TO];
+				val = to->body;
+			}
+			break;
+
+		case 'u': /* digest_username */
+			cr = cred_user(rq);
+			if (cr) {
+				attr = &attrs[A_SER_DIGEST_USERNAME];
+				val = *cr;
+			}
+			break;
+
+		case 'x': /* request_timestamp */
+			attr = &attrs[A_SER_REQUEST_TIMESTAMP];
+			rq_time = req_time;
+			val.s = (char*)&rq_time;
+			val.len = sizeof(time_t);
+			break;
+
+		case 'D': /* to_did */
+			break;
+
+		case 'F': /* from_uri */
+			if (swap_dir && dir == -2) dir = get_direction(rq);
+			if (dir <= 0) {
+				if (rq->from && (from = get_from(rq)) && from->uri.len) {
+					attr = &attrs[A_CALLING_STATION_ID];
+					val = from->uri;
+				}
+			} else {
+				if (rq->to && (pto = get_to(rq)) && pto->uri.len) {
+					attr = &attrs[A_CALLING_STATION_ID];
+					val = pto->uri;
+				}
+			}
+			break;
+
+		case 'I': /* from_uid */
+			if (get_from_uid(&val, rq) < 0) {
+				attr = &attrs[A_SER_FROM_UID];
+			}
+			break;
+
+		case 'M': /* from_did */
+			break;
+
+		case 'P': /* Source port */
+			attr = &attrs[A_SIP_SOURCE_PORT];
+			src_port = rq->rcv.src_port;
+			val.s = (char*)&src_port;
+			val.len = sizeof(unsigned int);
+			break;
+
+		case 'R': /* digest_realm */
+			cr = cred_realm(rq);
+			if (cr) {
+				attr = &attrs[A_SER_DIGEST_REALM];
+				val = *cr;
+			}
+			break;
+
+		case 'S': /* sip_status */
+			attr = &attrs[A_SIP_RESPONSE_CODE];
+			val.s = (char*)&code;
+			val.len = sizeof(unsigned int);
+			break;
+
+		case 'T': /* to_uri */
+			if (swap_dir && dir == -2) dir = get_direction(rq);
+			if (dir <= 0) {
+				if (rq->to && (pto = get_to(rq)) && pto->uri.len) {
+					attr = &attrs[A_CALLED_STATION_ID];
+					val = pto->uri;
+				}
+			} else {
+				if (rq->from && (from = get_from(rq)) && from->uri.len) {
+					attr = &attrs[A_CALLED_STATION_ID];
+					val = from->uri;
+				}
+			}
+			break;
+
+		case 'U': /* to_uid */
+			if (get_from_uid(&val, rq) < 0) {
+				attr = &attrs[A_SER_TO_UID];
+			}
+			break;
+
+		case 'X': /* response_timestamp */
+			attr = &attrs[A_SER_RESPONSE_TIMESTAMP];
+			rs_time = time(0);
+			val.s = (char*)&rs_time;
+			val.len = sizeof(time_t);
+			break;
+
+		default:
+			LOG(L_CRIT, "BUG:acc:fmt2rad: unknown char: %c\n", *fmt);
+			return -1;
+		} /* switch (*fmt) */
+
+		if (attr) {
+			if (!rc_avpair_add(rh, send, ATTRID(attr->v), val.s, val.len, VENDOR(attr->v))) {
+				LOG(L_ERR, "ERROR:acc:fmt2rad: Failed to add attribute %s\n",
+				    attr->n);
+				return -1;
+			}
+		}
+
+		fmt++;
+		cnt++;
+	} /* while (*fmt) */
+
+	return 0;
+}
+
+
+/*
+ * Return the value of Acc-Status-Type attribute based on SIP method
+ * and response code
+ */
+static inline UINT4 rad_status(struct sip_msg *rq, unsigned int code)
+{
+	     /* Faked reply */
+	if (code == 0) {
+		return vals[V_FAILED].v;
+	}
+
+	     /* Successful call start */
+	if ((rq->REQ_METHOD == METHOD_INVITE || rq->REQ_METHOD == METHOD_ACK)
+	    && code >= 200 && code < 300) {
+		return vals[V_START].v;
+	}
+
+	     /* Successful call termination */
+	if ((rq->REQ_METHOD == METHOD_BYE || rq->REQ_METHOD == METHOD_CANCEL)) {
+		return vals[V_STOP].v;
+	}
+
+	     /* Successful transaction */
+	if (code >= 200 && code < 300) {
+		return vals[V_INTERIM_UPDATE].v;
+	}
+
+	     /* Otherwise it failed */
+	return vals[V_FAILED].v;
+}
+
+
+/*
+ * Add User-Name attribute
+ */
+static inline int add_user_name(struct sip_msg* rq, void* rh, VALUE_PAIR** send)
+{
+	struct sip_uri puri;
+	str* user, *realm;
+	str user_name;
+	struct to_body* from;
+
+	user = cred_user(rq);  /* try to take it from credentials */
+	realm = cred_realm(rq);
+
+	if (!user || !realm) {
+		if (rq->from && (from = get_from(rq)) && from->uri.len) {
+			if (parse_uri(from->uri.s, from->uri.len, &puri) < 0 ) {
+				LOG(L_ERR, "ERROR:acc:add_user_name: Bad From URI\n");
+				return -1;
+			}
+
+			user = &puri.user;
+			realm = &puri.host;
+		} else {
+			DBG("acc:add_user_name: Neither digest nor From found, mandatory attribute User-Name not added\n");
+			return -1;
+		}
+	}
+
+	user_name.len = user->len + 1 + realm->len;
+	user_name.s = pkg_malloc(user_name.len);
+	if (!user_name.s) {
+		LOG(L_ERR, "ERROR:acc:add_user_name: no memory\n");
+		return -1;
+	}
+	memcpy(user_name.s, user->s, user->len);
+	user_name.s[user->len] = '@';
+	memcpy(user_name.s + user->len + 1, realm->s, realm->len);
+
+	if (!rc_avpair_add(rh, send, ATTRID(attrs[A_USER_NAME].v),
+			   user_name.s, user_name.len, VENDOR(attrs[A_USER_NAME].v))) {
+		LOG(L_ERR, "ERROR:acc:add_user_name: Failed to add User-Name attribute\n");
+		pkg_free(user_name.s);
+		return -1;
+	}
+	pkg_free(user_name.s);
+	
+	return 0;
+}
+
+
+/* skip leading text and begin with first item's
+ * separator ", " which will be overwritten by the
+ * leading text later
+ *
+ */
+static int log_request(struct sip_msg* rq, str* ouri, struct hdr_field* to, unsigned int code, time_t req_time)
+{
+	VALUE_PAIR *send;
+	UINT4 av_type;
+
+	send = NULL;
+	if (skip_cancel(rq)) return 1;
+
+	if (fmt2rad(log_fmt, rq, ouri, to, code, &send, req_time) < 0) goto error;
+
+	     /* Add Acct-Status-Type attribute */
+	av_type = rad_status(rq, code);
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_ACCT_STATUS_TYPE].v), &av_type, -1, 
+			   VENDOR(attrs[A_ACCT_STATUS_TYPE].v))) {
+		ERR("Add Status-Type\n");
+		goto error;
+	}
+
+	     /* Add Service-Type attribute */
+	av_type = (service_type != -1) ? service_type : vals[V_SIP_SESSION].v;
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SERVICE_TYPE].v), &av_type, -1, 
+			   VENDOR(attrs[A_SERVICE_TYPE].v))) {
+		ERR("add STATUS_TYPE\n");
+		goto error;
+	}
+
+	     /* Add User-Name attribute */
+	if (add_user_name(rq, rh, &send) < 0) goto error;
+
+	     /* Send the request out */
+	if (rc_acct(rh, SIP_PORT, send) != OK_RC) {
+		ERR("RADIUS accounting request failed\n");
+		goto error;
+	}
+
+	rc_avpair_free(send);
+	return 1;
+
+error:
+	rc_avpair_free(send);
+	return -1;
+}
+
+
+static void log_reply(struct cell* t , struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+        str* ouri;
+
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+
+	log_request(t->uas.request, ouri, valid_to(t, reply), code, req_time);
+}
+
+
+static void log_ack(struct cell* t , struct sip_msg *ack, time_t req_time)
+{
+	struct sip_msg *rq;
+	struct hdr_field *to;
+
+	rq = t->uas.request;
+	if (ack->to) to = ack->to;
+	else to = rq->to;
+	log_request(ack, GET_RURI(ack), to, t->uas.status, req_time);
+}
+
+
+static void log_missed(struct cell* t, struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+        str* ouri;
+
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+
+        log_request(t->uas.request, ouri , valid_to(t, reply), code, req_time);
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_rad_request0(struct sip_msg *rq, char* p1, char* p2)
+{
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, 0, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_rad_missed0(struct sip_msg *rq, char* p1, char* p2)
+{
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, 0, time(0));
+}
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_rad_request1(struct sip_msg *rq, char* p1, char* p2)
+{
+    int code;
+    preparse_req(rq);
+    if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
+	code = 0;
+    }
+    return log_request(rq, GET_RURI(rq), rq->to, code, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_rad_missed1(struct sip_msg *rq, char* p1, char* p2)
+{
+    int code;
+    preparse_req(rq);
+    if (get_int_fparam(&code, rq, (fparam_t*)p1) < 0) {
+	code = 0;
+    }
+    return log_request(rq, GET_RURI(rq), rq->to, code, time(0));
+}
+
+
+static void ack_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (is_acc_on(t->uas.request)) {
+		preparse_req(ps->req);
+		log_ack(t, ps->req, (time_t)*(ps->param));
+	}
+}
+
+
+/* initiate a report if we previously enabled MC accounting for this t */
+static void failure_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	/* validation */
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:failure_handler: No uas.request, skipping local transaction\n");
+		return;
+	}
+
+	if (is_invite(t) && ps->code >= 300) {
+		if (is_mc_on(t->uas.request)) {
+			log_missed(t, ps->rpl, ps->code, (time_t)*(ps->param));
+			resetflag(t->uas.request, log_missed_flag);
+		}
+	}
+}
+
+
+/* initiate a report if we previously enabled accounting for this t */
+static void replyout_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:replyout_handler: No uas.request, local transaction, skipping\n");
+		return;
+	}
+
+	     /* acc_onreply is bound to TMCB_REPLY which may be called
+	      * from _reply, like when FR hits; we should not miss this
+	      * event for missed calls either
+	      */
+	failure_handler(t, type, ps);
+	if (!should_acc_reply(t, ps->code)) return;
+	if (is_acc_on(t->uas.request)) log_reply(t, ps->rpl, ps->code, (time_t)*(ps->param));
+}
+
+
+/* parse incoming replies before cloning */
+static void replyin_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	     /* validation */
+	if (t->uas.request == 0) {
+		LOG(L_ERR, "ERROR:acc:replyin_handler:replyin_handler: 0 request\n");
+		return;
+	}
+
+	     /* don't parse replies in which we are not interested */
+	     /* missed calls enabled ? */
+	if (((is_invite(t) && ps->code >= 300 && is_mc_on(t->uas.request))
+	     || should_acc_reply(t, ps->code))
+	    && (ps->rpl && ps->rpl != FAKED_REPLY)) {
+		parse_headers(ps->rpl, HDR_TO_F, 0);
+	}
+}
+
+
+/* prepare message and transaction context for later accounting */
+void on_req(struct cell* t, int type, struct tmcb_params *ps)
+{
+	time_t req_time;
+	     /* Pass the timestamp of the request as a parameter to callbacks */
+	req_time = time(0);
+
+	if (is_acc_on(ps->req) || is_mc_on(ps->req)) {
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_OUT, replyout_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_OUT callback\n");
+			return;
+		}
+
+		if (report_ack) {
+			if (tmb.register_tmcb(0, t, TMCB_E2EACK_IN, ack_handler,
+									(void*)req_time, 0) <= 0) {
+				LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_E2EACK_IN callback\n");
+				return;
+			}
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_ON_FAILURE_RO, failure_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_ON_FAILURE_RO callback\n");
+			return;
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_IN, replyin_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_IN callback\n");
+			return;
+		}
+
+		     /* do some parsing in advance */
+		preparse_req(ps->req);
+		     /* also, if that is INVITE, disallow silent t-drop */
+		if (ps->req->REQ_METHOD == METHOD_INVITE) {
+			DBG("DEBUG: noisy_timer set for accounting\n");
+			t->flags |= T_NOISY_CTIMER_FLAG;
+		}
+	}
+}
+
+
+static int mod_init(void)
+{
+	DICT_VENDOR *vend;
+	load_tm_f load_tm;
+
+	     /* import the TM auto-loading function */
+	if ( !(load_tm=(load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
+		LOG(L_ERR, "ERROR:acc:mod_init: can't import load_tm\n");
+		return -1;
+	}
+	     /* let the auto-loading function load all TM stuff */
+	if (load_tm( &tmb )==-1) return -1;
+	if (verify_fmt(log_fmt)==-1) return -1;
+
+	     /* register callbacks*/
+	     /* listen for all incoming requests  */
+	if (tmb.register_tmcb( 0, 0, TMCB_REQUEST_IN, on_req, 0, 0) <= 0) {
+		LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN "
+		    "callback\n");
+		return -1;
+	}
+
+	memset(attrs, 0, sizeof(attrs));
+	memset(vals, 0, sizeof(vals));
+
+	attrs[A_USER_NAME].n		     = "User-Name";
+	attrs[A_SERVICE_TYPE].n		     = "Service-Type";
+	attrs[A_CALLED_STATION_ID].n	     = "Called-Station-Id";
+	attrs[A_CALLING_STATION_ID].n	     = "Calling-Station-Id";
+	attrs[A_ACCT_STATUS_TYPE].n	     = "Acct-Status-Type";
+	attrs[A_ACCT_SESSION_ID].n	     = "Acct-Session-Id";
+
+	attrs[A_SIP_METHOD].n		     = "Sip-Method";
+	attrs[A_SIP_RESPONSE_CODE].n	     = "Sip-Response-Code";
+	attrs[A_SIP_CSEQ].n		     = "Sip-CSeq";
+	attrs[A_SIP_TO_TAG].n		     = "Sip-To-Tag";
+	attrs[A_SIP_FROM_TAG].n		     = "Sip-From-Tag";
+	attrs[A_SIP_TRANSLATED_REQUEST_ID].n = "Sip-Translated-Request-Id";
+	attrs[A_SIP_SOURCE_IP_ADDRESS].n     = "Sip-Source-IP-Address";
+	attrs[A_SIP_SOURCE_PORT].n           = "Sip-Source-Port";
+
+	attrs[A_SER_ATTR].n                  = "SER-Attr";
+	attrs[A_SER_FROM].n                  = "SER-From";
+	attrs[A_SER_FLAGS].n                 = "SER-Flags";
+	attrs[A_SER_ORIGINAL_REQUEST_ID].n   = "SER-Original-Request-Id";
+	attrs[A_SER_TO].n                    = "SER-To";
+	attrs[A_SER_DIGEST_USERNAME].n       = "SER-Digest-Username";
+	attrs[A_SER_DIGEST_REALM].n          = "SER-Digest-Realm";
+	attrs[A_SER_REQUEST_TIMESTAMP].n     = "SER-Request-Timestamp";
+	attrs[A_SER_TO_DID].n                = "SER-To-DID";
+	attrs[A_SER_FROM_UID].n              = "SER-From-UID";
+	attrs[A_SER_FROM_DID].n              = "SER-From-DID";
+	attrs[A_SER_TO_UID].n                = "SER-To-UID";
+	attrs[A_SER_RESPONSE_TIMESTAMP].n    = "SER-Response-Timestamp";
+	attrs[A_SER_SERVER_ID].n             = "SER-Server-ID";
+
+	vals[V_START].n			     = "Start";
+	vals[V_STOP].n			     = "Stop";
+	vals[V_INTERIM_UPDATE].n             = "Interim-Update";
+	vals[V_FAILED].n		     = "Failed";
+	vals[V_SIP_SESSION].n		     = "Sip-Session";
+
+	     /* open log */
+	rc_openlog("ser");
+	     /* read config */
+	if ((rh = rc_read_config(radius_config)) == NULL) {
+		LOG(L_ERR, "ERROR:acc:mod_init: Error opening radius config file: %s\n",
+		    radius_config);
+		return -1;
+	}
+	     /* read dictionary */
+	if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0) {
+		LOG(L_ERR, "ERROR:acc:mod_init: Error reading radius dictionary\n");
+		return -1;
+	}
+
+	vend = rc_dict_findvend(rh, "iptelorg");
+	if (vend == NULL) {
+		ERR("RADIUS dictionary is missing required vendor 'iptelorg'\n");
+		return -1;
+	}
+
+	INIT_AV(rh, attrs, vals, "acc", -1, -1);
+
+	if (service_type != -1) {
+		vals[V_SIP_SESSION].v = service_type;
+	}
+
+	if (parse_attrs(&avps, &avps_n, attrs_param) < 0) {
+		ERR("Error while parsing 'attrs' module parameter\n");
+		return -1;
+	}
+
+	return 0;
+}

+ 2 - 0
acc_syslog/.cvsignore

@@ -0,0 +1,2 @@
+acc_syslog.7
+

+ 16 - 0
acc_syslog/Makefile

@@ -0,0 +1,16 @@
+# $Id$
+#
+# acc module makefile
+#
+#
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+
+auto_gen=
+NAME=acc_syslog.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 383 - 0
acc_syslog/README

@@ -0,0 +1,383 @@
+1. Acc Module
+
+Jiri Kuthan
+
+   iptel.org
+   <[email protected]>
+
+   Copyright © 2002, 2003 FhG FOKUS
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Dependencies
+   1.3. Parameters
+
+        1.3.1. log_level (integer)
+        1.3.2. log_fmt (string)
+        1.3.3. early_media (integer)
+        1.3.4. failed_transactions (integer)
+        1.3.5. log_flag (integer)
+        1.3.6. log_missed_flag (integer)
+        1.3.7. report_ack (integer)
+        1.3.8. report_cancels (integer)
+        1.3.9. radius_config (string)
+        1.3.10. service_type (integer)
+        1.3.11. radius_flag (integer)
+        1.3.12. radius_missed_flag (integer)
+        1.3.13. db_url (string)
+        1.3.14. db_flag (integer)
+        1.3.15. db_missed_flag (integer)
+        1.3.16. diameter_flag (integer)
+        1.3.17. diameter_missed_flag (integer)
+        1.3.18. diameter_client_host (string)
+        1.3.19. diameter_client_port (int)
+
+   1.4. Functions
+
+        1.4.1. acc_log_request(comment)
+        1.4.2. acc_db_request(comment, table)
+        1.4.3. acc_rad_request(comment)
+        1.4.4. acc_diam_request(comment)
+
+1.1. Overview
+
+   acc module is used to report on transactions to syslog, SQL and RADIUS.
+
+   To report on a transaction using syslog, use "setflag" to mark a
+   transaction you are interested in with a flag, load accounting module
+   and set its "log_flag" to the same flag number. The acc module will
+   then report on completed transaction to syslog. A typical usage of the
+   module takes no acc-specific script command -- the functionality binds
+   invisibly through transaction processing. Script writers just need to
+   mark the transaction for accounting with proper setflag.
+
+   What is printed depends on module's "log_fmt" parameter. It's a string
+   with characters specifying which parts of request should be printed:
+     * c = Call-Id
+     * d = To tag (Dst)
+     * f = From
+     * i = Inbound Request-URI
+     * m = Method
+     * o = Outbound Request-URI
+     * r = fRom
+     * s = Status
+     * t = To
+     * u = digest Username
+     * p = username Part of inbound Request-URI
+
+   If a value is not present in request, "n/a" is accounted instead.
+
+Note
+
+     * A single INVITE may produce multiple accounting reports -- that's
+       due to SIP forking feature
+     * Subsequent ACKs and other requests do not hit the server and can't
+       be accounted unless record-routing is enforced. The ACKs assert
+       very little useful information anyway and reporting on INVITE's 200
+       makes most accounting scenarios happy.
+     * There is no session accounting -- ser maintains no sessions. If one
+       needs to correlate INVITEs with BYEs for example for purpose of
+       billing, then it is better done in the entity which collects
+       accounting information. Otherwise, SIP server would have to become
+       sessions-stateful, which would very badly impact its scalability.
+     * If a UA fails in middle of conversation, a proxy will never learn
+       it. In general, a better practice is to account from an end-device
+       (such as PSTN gateway), which best knows about call status
+       (including media status and PSTN status in case of the gateway).
+
+   Support for SQL and RADIUS works analogously. You need to enable it by
+   recompiling the module with properly set defines. Uncomment the SQL_ACC
+   and RAD_ACC lines in modules/acc/Makefile. To compile SQL support, you
+   need to have mysqlclient package on your system. To compile RADIUS
+   support, you need to have radiusclient installed on your system
+   (version 0.5.0 or higher is required) which is available from
+   http://developer.berlios.de/projects/radiusclient-ng/. The radius
+   client needs to be configured properly. To do so, use the template in
+   sip_router/etc/radiusclient.conf and make sure that module's
+   radius_config parameter points to its location. Uses along with
+   FreeRADIUS and Radiator servers have been reported to us.
+
+   Both mysql and radius libraries must be dynamically linkable. You need
+   to configure your OS so that SER, when started, will find them.
+   Typically, you do so by manipulating LD_LIBRARY_PATH environment
+   variable or configuring ld.so.
+
+   Example 1. General Example
+loadmodule "modules/acc/acc.so"
+modparam("acc", "log_level", 1)
+modparam("acc", "log_flag", 1)
+
+if (uri=~"sip:+49") /* calls to Germany */ {
+    if (!proxy_authorize("iptel.org" /* realm */,
+                         "subscriber" /* table name */))  {
+        proxy_challenge("iptel.org" /* realm */, "0" /* no qop */ );
+        break;
+    }
+
+    if (method=="INVITE" & !check_from()) {
+        log("from!=digest\n");
+        sl_send_reply("403","Forbidden");
+        break;
+    }
+
+    setflag(1); /* set for accounting (the same value as in log_flag!)
+    t_relay();  /* enter stateful mode now */
+};
+
+1.2. Dependencies
+
+   The module depends on the following modules (in the other words the
+   listed modules must be loaded before this module):
+     * tm. Transaction Manager
+     * A database module (mysql,postgres,dbtext). If compiled with
+       database support.
+
+1.3. Parameters
+
+1.3.1. log_level (integer)
+
+   Log level at which accounting messages are issued to syslog.
+
+   Default value is L_NOTICE.
+
+   Example 2. log_level example
+modparam("acc", "log_level", 2)   # Set log_level to 2
+
+1.3.2. log_fmt (string)
+
+   Defines what parts of header fields will be printed to syslog, see
+   "overview" for list of accepted values.
+
+   Default value is "miocfs".
+
+   Example 3. log_fmt example
+modparam("acc", "log_fmt", "mfs")
+
+1.3.3. early_media (integer)
+
+   Should be early media (183) accounted too ?
+
+   Default value is 0 (no).
+
+   Example 4. early_media example
+modparam("acc", "early_media", 1)
+
+1.3.4. failed_transactions (integer)
+
+   This parameter controls whether failed transactions (with final reply
+   >= 300) should be accounted too.
+
+   Default value is 0 (no).
+
+   Example 5. failed_transactions example
+modparam("acc", "failed_transactions", 1)
+
+1.3.5. log_flag (integer)
+
+   Request flag which needs to be set to account a transaction.
+
+   Default value is 1.
+
+   Example 6. log_flag example
+modparam("acc", "log_flag", 2)
+
+1.3.6. log_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls.
+
+   Default value is 2.
+
+   Example 7. log_missed_flag example
+modparam("acc", "log_missed_flag", 3)
+
+1.3.7. report_ack (integer)
+
+   Shall acc attempt to account e2e ACKs too ? Note that this is really
+   only an attempt, as e2e ACKs may take a different path (unless RR
+   enabled) and mismatch original INVITE (e2e ACKs are a separate
+   transaction).
+
+   Default value is 1 (yes).
+
+   Example 8. report_ack example
+modparam("acc", "report_ack", 0)
+
+1.3.8. report_cancels (integer)
+
+   By default, CANCEL reporting is disabled -- most accounting
+   applications are happy to see INVITE's cancellation status. Turn on if
+   you explicitly want to account CANCEL transactions.
+
+   Default value is 0 (no).
+
+   Example 9. report_cancels example
+modparam("acc", "report_cancels", 1)
+
+1.3.9. radius_config (string)
+
+   This parameter is radius specific. Path to radius client configuration
+   file, set the referred config file correctly and specify there address
+   of server, shared secret (should equal that in
+   /usr/local/etc/raddb/clients for freeRadius servers) and dictionary,
+   see etc for an example of config file and dictionary.
+
+   Default value is “/usr/local/etc/radiusclient/radiusclient.conf”.
+
+   Example 10. radius_config example
+modparam("acc", "radius_config", "/etc/radiusclient/radiusclient.conf")
+
+1.3.10. service_type (integer)
+
+   Radius service type used for accounting.
+
+   Default value is 15 (SIP).
+
+   Example 11. service_type example
+modparam("acc", "service_type", 16)
+
+1.3.11. radius_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- RADIUS
+   specific.
+
+   Default value is 1.
+
+   Example 12. radius_flag example
+                modparam("acc", "radius_flag", 2)
+
+1.3.12. radius_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- RADIUS
+   specific.
+
+   Default value is 2.
+
+   Example 13. radius_missed_flag example
+modparam("acc", "radius_missed_flag", 3)
+
+1.3.13. db_url (string)
+
+   SQL address -- database specific.
+
+   Default value is "mysql://ser:heslo@localhost/ser"
+
+   Example 14. db_url example
+modparam("acc", "db_url", "mysql://user:password@localhost/ser")
+
+1.3.14. db_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- database
+   specific.
+
+   Default value is 1.
+
+   Example 15. db_flag example
+modparam("acc", "db_flag", 2)
+
+1.3.15. db_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- database
+   specific.
+
+   Default value is 2.
+
+   Example 16. db_missed_flag example
+modparam("acc", "db_missed_flag", 3)
+
+1.3.16. diameter_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- DIAMETER
+   specific.
+
+   Default value is 1.
+
+   Example 17. diameter_flag example
+modparam("acc", "diameter_flag", 2)
+
+1.3.17. diameter_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- DIAMETER
+   specific.
+
+   Default value is 2.
+
+   Example 18. diameter_missed_flag example
+modparam("acc", "diameter_missed_flag", 3)
+
+1.3.18. diameter_client_host (string)
+
+   Hostname of the machine where the DIAMETER Client is running --
+   DIAMETER specific.
+
+   Default value is "localhost".
+
+   Example 19. diameter_client_host example
+modparam("acc", "diameter_client_host", "iptel.org")
+
+1.3.19. diameter_client_port (int)
+
+   Port number where the Diameter Client is listening -- DIAMETER
+   specific.
+
+   Default value is 3000.
+
+   Example 20. diameter_client_host example
+modparam("acc", "diameter_client_port", 3000)
+
+1.4. Functions
+
+1.4.1. acc_log_request(comment)
+
+   acc_request reports on a request, for example, it can be used to report
+   on missed calls to off-line users who are replied 404. To avoid
+   multiple reports on UDP request retransmission, you would need to embed
+   the action in stateful processing.
+
+   Meaning of the parameters is as follows:
+     * comment - Comment to be appended.
+
+   Example 21. acc_log_request usage
+...
+acc_log_request("Some comment");
+...
+
+1.4.2. acc_db_request(comment, table)
+
+   Like acc_log_request, acc_db_request reports on a request. The report
+   is sent to database at "db_url", in the table referred to in the second
+   action parameter
+
+   Meaning of the parameters is as follows:
+     * comment - Comment to be appended.
+     * table - Database table to be used.
+
+   Example 22. acc_db_request usage
+...
+acc_log_request("Some comment", "Some table");
+...
+
+1.4.3. acc_rad_request(comment)
+
+   Like acc_log_request, acc_rad_request reports on a request. It reports
+   to radius server as configured in "radius_config".
+
+   Meaning of the parameters is as follows:
+     * comment - Comment to be appended.
+
+   Example 23. acc_rad_request usage
+...
+acc_rad_request("Some comment");
+...
+
+1.4.4. acc_diam_request(comment)
+
+   Like acc_log_request, acc_diam_request reports on a request. It reports
+   to Diameter server.
+
+   Meaning of the parameters is as follows:
+     * comment - Comment to be appended.
+
+   Example 24. acc_diam_request usage
+...
+acc_diam_request("Some comment");
+...

+ 943 - 0
acc_syslog/acc_syslog.c

@@ -0,0 +1,943 @@
+/*
+ * Accounting module
+ *
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2005 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../modules/tm/t_hooks.h"
+#include "../../modules/tm/tm_load.h"
+#include "../../modules/tm/h_table.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/digest/digest.h"
+#include "../../usr_avp.h"
+#include "../../id.h"
+#include "attrs.h"
+#include "../../modules/tm/tm_load.h"
+
+/*
+ * TODO:
+ * - Quote attribute values properly
+ * - Save request timestamp
+ * - Save response timestamp
+ */
+
+/*
+ * a: attr
+ * c: sip_callid
+ * d: to_tag
+ * f: sip_from
+ * g: flags
+ * i: inbound_ruri
+ * m: sip_method
+ * n: sip_cseq
+ * o: outbound_ruri
+ * p: source_ip
+ * r: from_tag
+ * s: server_id
+ * t: sip_to
+ * u: digest_username
+ * x: request_timestamp
+ * D: to_did
+ * F: from_uri
+ * I: from_uid
+ * M: from_did
+ * R: digest_realm
+ * P: source_port
+ * S: sip_status
+ * T: to_uri
+ * U: to_uid
+ * X: response_timestamp
+ */
+
+#define ALL_LOG_FMT "acdfgimnoprstuxDFIMPRSTUX"
+#define ALL_LOG_FMT_LEN (sizeof(ALL_LOG_FMT) - 1)
+
+#define A_SEPARATOR ", " /* must be shorter than ACC! */
+#define A_SEPARATOR_LEN (sizeof(A_SEPARATOR) - 1)
+#define A_EQ "="
+#define A_EQ_LEN (sizeof(A_EQ) - 1)
+#define A_EOL "\n\0"
+#define A_EOL_LEN (sizeof(A_EOL) - 1)
+
+#define ACC "ACC: "  /* Prefix of accounting messages in syslog */
+#define ACC_LEN (sizeof(ACC) - 1)
+
+#define ACC_REQUEST "request accounted: "
+#define ACC_MISSED "call missed: "
+#define ACC_ANSWERED "transaction answered: "
+#define ACC_ACKED "request acknowledged: "
+
+#define NA "n/a"
+
+#define ATR(atr) atr_arr[cnt].s = A_##atr; atr_arr[cnt].len = sizeof(A_##atr) - 1;
+
+#define A_ATTRS        "attrs"
+#define A_CALLID       "callid"
+#define A_TOTAG        "to_tag"
+#define A_FROM         "from"
+#define A_FLAGS        "flags"
+#define A_IURI         "in_ruri"
+#define A_METHOD       "sip_method"
+#define A_CSEQ         "cseq"
+#define A_OURI         "out_ruri"
+#define A_FROMTAG      "from_tag"
+#define A_TO           "to"
+#define A_DIGUSER      "digest_username"
+#define A_REQTIMESTAMP "request_timestamp"
+#define A_TODID        "to_did"
+#define A_FROMURI      "from_uri"
+#define A_FROMUID      "from_uid"
+#define A_FROMDID      "from_did"
+#define A_DIGREALM     "digest_realm"
+#define A_STATUS       "sip_status"
+#define A_TOURI        "to_uri"
+#define A_TOUID        "to_uid"
+#define A_RESTIMESTAMP "response_timestamp"
+#define A_SRCIP        "src_ip"
+#define A_SRCPORT      "src_port"
+#define A_SERVERID     "server_id"
+
+MODULE_VERSION
+
+struct tm_binds tmb;
+
+static int mod_init( void );
+static int fix_log_flag( modparam_t type, void* val);
+static int fix_log_missed_flag( modparam_t type, void* val);
+
+static int log_level = L_NOTICE; /* noisiness level logging facilities are used */
+
+static int early_media = 0;         /* Enable/disable early media (183) accounting */
+static int failed_transactions = 0; /* Enable/disable accounting of failed (>= 300) transactions */
+static int report_cancels = 0;      /* Enable/disable CANCEL reporting */
+static int report_ack = 0;          /* Enable/disable end-to-end ACK reports */
+static int log_flag = 0;            /* Flag that marks transactions to be accounted */
+static int log_missed_flag = 0;     /* Transaction having this flag set will be accounted in missed calls when fails */
+static char* log_fmt = ALL_LOG_FMT; /* Formating string that controls what information will be collected and accounted */
+
+/* Attribute-value pairs */
+static char* attrs = "";
+avp_ident_t* avps;
+int avps_n;
+
+static int acc_log_request0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_log_missed0(struct sip_msg *rq, char *p1, char *p2);
+static int acc_log_request1(struct sip_msg *rq, char *p1, char *p2);
+static int acc_log_missed1(struct sip_msg *rq, char *p1, char *p2);
+
+static str na = STR_STATIC_INIT(NA);
+
+
+static cmd_export_t cmds[] = {
+	{"acc_syslog_log",    acc_log_request0, 0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_syslog_missed", acc_log_missed0,  0, 0,               REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_syslog_log",    acc_log_request1, 1, fixup_var_str_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{"acc_syslog_missed", acc_log_missed1,  1, fixup_var_str_1, REQUEST_ROUTE | FAILURE_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+static param_export_t params[] = {
+	{"early_media",		PARAM_INT, &early_media         },
+	{"failed_transactions",	PARAM_INT, &failed_transactions },
+	{"report_ack",		PARAM_INT, &report_ack          },
+	{"report_cancels",	PARAM_INT, &report_cancels 	},
+	{"log_flag",		PARAM_INT, &log_flag         	},
+	{"log_flag",		PARAM_STRING|PARAM_USE_FUNC, fix_log_flag},
+	{"log_missed_flag",	PARAM_INT, &log_missed_flag	},
+	{"log_missed_flag",	PARAM_STRING|PARAM_USE_FUNC, fix_log_missed_flag},
+	{"log_level",		PARAM_INT, &log_level           },
+	{"log_fmt",		PARAM_STRING, &log_fmt          },
+	{"attrs",               PARAM_STRING, &attrs            },
+	{0, 0, 0}
+};
+
+
+struct module_exports exports= {
+	"acc_syslog",
+	cmds,     /* exported functions */
+	0,        /* RPC methods */
+	params,   /* exported params */
+	mod_init, /* initialization module */
+	0,	  /* response function */
+	0,        /* destroy function */
+	0,	  /* oncancel function */
+	0         /* per-child init function */
+};
+
+
+
+/* fixes log_flag param (resolves possible named flags) */
+static int fix_log_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_syslog", "log_flag", &log_flag);
+}
+
+
+
+/* fixes log_missed_flag param (resolves possible named flags) */
+static int fix_log_missed_flag( modparam_t type, void* val)
+{
+	return fix_flag(type, val, "acc_syslog", "log_missed_flag", &log_missed_flag);
+}
+
+
+#define TM_BUF_LEN sizeof("10000-12-31 23:59:59")
+
+static inline int convert_time(str* buf, time_t time)
+{
+	struct tm* tm;
+
+	if (!buf->s || buf->len < TM_BUF_LEN) {
+		LOG(L_ERR, "ERROR:acc:convert_time: Buffer too short\n");
+		return -1;
+	}
+
+	tm = gmtime(&time);
+	buf->len = strftime(buf->s, buf->len, "%Y-%m-%d %H:%M:%S", tm);
+	return 0;
+}
+
+
+static inline int skip_cancel(struct sip_msg *msg)
+{
+        return (msg->REQ_METHOD == METHOD_CANCEL) && report_cancels == 0;
+}
+
+
+/*
+ * Append a constant string, uses sizeof to figure the length
+ * of the string
+ */
+#define append(buf, ptr)                         \
+    do {                                         \
+        memcpy((buf).s, (ptr), sizeof(ptr) - 1); \
+        (buf).s += sizeof(ptr) - 1;              \
+        (buf).len -= sizeof(ptr) - 1;            \
+    } while(0);
+
+
+#define str_append_str(buf, str)			  \
+    do {                                      \
+        memcpy((buf).s, (str).s, (str).len);  \
+        (buf).s += (str).len;                 \
+        (buf).len -= (str).len;               \
+    } while(0);
+
+
+int verify_fmt(char *fmt) {
+
+	if (!fmt) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string zero\n");
+		return -1;
+	}
+
+	if (!(*fmt)) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string empty\n");
+		return -1;
+	}
+
+	if (strlen(fmt) > ALL_LOG_FMT_LEN) {
+		LOG(L_ERR, "ERROR:acc:verify_fmt: formatting string too long\n");
+		return -1;
+	}
+
+	while(*fmt) {
+		if (!strchr(ALL_LOG_FMT, *fmt)) {
+			LOG(L_ERR, "ERROR:acc:verify_fmt: char in log_fmt invalid: %c\n", *fmt);
+			return -1;
+		}
+		fmt++;
+	}
+	return 1;
+}
+
+
+/*
+ * Return true if accounting is enabled and the
+ * transaction is marked for accounting
+ */
+static inline int is_acc_on(struct sip_msg *rq)
+{
+	return log_flag && isflagset(rq, log_flag) == 1;
+}
+
+
+/*
+ * Return true if missed_call accounting is enabled
+ * and the transaction has the flag set
+ */
+static inline int is_mc_on(struct sip_msg *rq)
+{
+	return log_missed_flag && isflagset(rq, log_missed_flag) == 1;
+}
+
+
+static inline void preparse_req(struct sip_msg *rq)
+{
+	     /* try to parse from for From-tag for accounted transactions;
+	      * don't be worried about parsing outcome -- if it failed,
+	      * we will report N/A. There is no need to parse digest credentials
+	      * here even if we account them, because the authentication function
+	      * will do it before us and if not then we will account n/a.
+	      */
+	parse_headers(rq, HDR_CALLID_F | HDR_FROM_F | HDR_TO_F | HDR_CSEQ_F, 0 );
+	parse_from_header(rq);
+}
+
+
+/* is this reply of interest for accounting ? */
+static inline int should_acc_reply(struct cell* t, int code)
+{
+	struct sip_msg *r;
+
+	r = t->uas.request;
+
+	     /* validation */
+	if (r == 0) {
+		LOG(L_ERR, "ERROR:acc:should_acc_reply: 0 request\n");
+		return 0;
+	}
+
+	     /* negative transactions reported otherwise only if explicitly
+	      * demanded */
+	if (!failed_transactions && code >= 300) return 0;
+	if (!is_acc_on(r)) return 0;
+	if (skip_cancel(r)) return 0;
+	if (code < 200 && ! (early_media && code == 183)) return 0;
+	return 1; /* seed is through, we will account this reply */
+}
+
+
+/* Extract username attribute from authorized credentials */
+static inline str* cred_user(struct sip_msg* rq)
+{
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred || !cred->digest.username.user.len)
+		return 0;
+	return &cred->digest.username.user;
+}
+
+
+/* Extract realm attribute from authorized credentials */
+static inline str* cred_realm(struct sip_msg* rq)
+{
+	str* realm;
+	struct hdr_field* h;
+	auth_body_t* cred;
+
+	get_authorized_cred(rq->proxy_auth, &h);
+	if (!h) get_authorized_cred(rq->authorization, &h);
+	if (!h) return 0;
+	cred = (auth_body_t*)(h->parsed);
+	if (!cred) return 0;
+	realm = GET_REALM(&cred->digest);
+	if (!realm->len || !realm->s) {
+		return 0;
+	}
+	return realm;
+}
+
+
+/* Return To header field from the request in case of faked reply or
+ * missing To header field in the reply
+ */
+static inline struct hdr_field* valid_to(struct cell* t, struct sip_msg* reply)
+{
+	if (reply == FAKED_REPLY || !reply || !reply->to) {
+		return t->uas.request->to;
+	} else {
+		return reply->to;
+	}
+}
+
+
+/* create an array of str's for accounting using a formatting string;
+ * this is the heart of the accounting module -- it prints whatever
+ * requested in a way, that can be used for syslog, radius,
+ * sql, whatsoever
+ * tm sip_msg_clones does not clone (shmmem-zed) parsed fields, other then Via1,2. Such fields clone now or use from rq_rp
+ */
+static int fmt2strar(char *fmt,             /* what would you like to account ? */
+		     struct sip_msg *rq,    /* accounted message */
+		     str* ouri,             /* Outbound Request-URI */
+		     struct hdr_field *to,  /* To header field (used to extract tag) */
+		     str *phrase,
+		     int *total_len,        /* total length of accounted values */
+		     int *attr_len,         /* total length of accounted attribute names */
+		     str **val_arr,         /* that's the output -- must have MAX_ACC_COLUMNS */
+		     str *atr_arr,
+		     time_t req_time)       /* Timestamp of the request */
+{
+	static char flags_buf[INT2STR_MAX_LEN], tm_buf[TM_BUF_LEN],
+		rqtm_buf[TM_BUF_LEN], srcip_buf[IP_ADDR_MAX_STR_SIZE],
+		srcport_buf[INT2STR_MAX_LEN], serverid_buf[INT2STR_MAX_LEN];
+	int cnt, tl, al;
+	struct to_body* from, *pto;
+	static str mycode, flags, tm_s, rqtm_s, src_ip, src_port, from_uid, to_uid, server_id_str;
+	str *cr, *at;
+	struct cseq_body *cseq;
+	char* p;
+
+	cnt = tl = al = 0;
+
+	     /* we don't care about parsing here; either the function
+	      * was called from script, in which case the wrapping function
+	      * is supposed to parse, or from reply processing in which case
+	      * TM should have preparsed from REQUEST_IN callback; what's not
+	      * here is replaced with NA
+	      */
+	while(*fmt) {
+		if (cnt == ALL_LOG_FMT_LEN) {
+			LOG(L_ERR, "ERROR:acc:fmt2strar: Formatting string is too long\n");
+			return 0;
+		}
+
+		switch(*fmt) {
+		case 'a': /* attr */
+			at = print_attrs(avps, avps_n, 1);
+			if (!at) {
+				val_arr[cnt] = &na;
+			} else {
+				val_arr[cnt] = at;
+			}
+			ATR(ATTRS);
+			break;
+
+		case 'c': /* sip_callid */
+			val_arr[cnt] = (rq->callid && rq->callid->body.len) ? &rq->callid->body : &na;
+			ATR(CALLID);
+			break;
+
+		case 'd': /* to_tag */
+			val_arr[cnt] = (to && (pto = (struct to_body*)(to->parsed)) && pto->tag_value.len) ? & pto->tag_value : &na;
+			ATR(TOTAG);
+			break;
+
+		case 'f': /* sip_from */
+			val_arr[cnt] = (rq->from && rq->from->body.len) ? &rq->from->body : &na;
+			ATR(FROM);
+			break;
+
+		case 'g': /* flags */
+			p = int2str(rq->flags, &flags.len);
+			memcpy(flags_buf, p, flags.len);
+			flags.s = flags_buf;
+			val_arr[cnt] = &flags;
+			ATR(FLAGS);
+			break;
+
+		case 'i': /* inbound_ruri */
+			val_arr[cnt] = &rq->first_line.u.request.uri;
+			ATR(IURI);
+			break;
+
+		case 'm': /* sip_method */
+			val_arr[cnt] = &rq->first_line.u.request.method;
+			ATR(METHOD);
+			break;
+
+		case 'n': /* sip_cseq */
+			if (rq->cseq && (cseq = get_cseq(rq)) && cseq->number.len) val_arr[cnt] = &cseq->number;
+			else val_arr[cnt]=&na;
+			ATR(CSEQ);
+			break;
+
+		case 'o': /* outbound_ruri */
+		        val_arr[cnt] = ouri;
+			ATR(OURI);
+			break;
+
+		case 'p': /* source_ip */
+			     /* We need to make a copy of the string here because ip_addr2a uses static
+			      * buffer and subseqent calls to the function would destroy the result
+			      */
+			src_ip.s = srcip_buf;
+			p = ip_addr2a(&rq->rcv.src_ip);
+			src_ip.len = strlen(p);
+			memcpy(src_ip.s, p, src_ip.len);
+			val_arr[cnt] = &src_ip;
+			ATR(SRCIP);
+			break;
+
+		case 'r': /* from_tag */
+			if (rq->from && (from = get_from(rq)) && from->tag_value.len) {
+				val_arr[cnt] = &from->tag_value;
+			} else {
+				val_arr[cnt] = &na;
+			}
+			ATR(FROMTAG);
+			break;
+
+		case 's': /* server_id */
+			p = int2str(server_id, &server_id_str.len);
+			memcpy(serverid_buf, p, server_id_str.len);
+			server_id_str.s = serverid_buf;
+			val_arr[cnt] = &server_id_str;
+			ATR(SERVERID);
+			break;
+
+		case 't': /* sip_to */
+			val_arr[cnt] = (to && to->body.len) ? &to->body : &na;
+			ATR(TO);
+			break;
+
+		case 'u': /* digest_username */
+			cr = cred_user(rq);
+			if (cr) val_arr[cnt] = cr;
+			else val_arr[cnt] = &na;
+			ATR(DIGUSER);
+			break;
+
+		case 'x': /* request_timestamp */
+			rqtm_s.s = rqtm_buf;
+			rqtm_s.len = TM_BUF_LEN;
+			convert_time(&rqtm_s, req_time);
+			val_arr[cnt] = &rqtm_s;
+			ATR(REQTIMESTAMP);
+			break;
+
+		case 'D': /* to_did */
+			val_arr[cnt] = &na;
+			ATR(TODID);
+			break;
+
+		case 'F': /* from_uri */
+			if (rq->from && (from = get_from(rq)) && from->uri.len) {
+				val_arr[cnt] = &from->uri;
+			} else val_arr[cnt] = &na;
+			ATR(FROMURI);
+			break;
+
+		case 'I': /* from_uid */
+			if (get_from_uid(&from_uid, rq) < 0) {
+				val_arr[cnt] = &na;
+			} else {
+				val_arr[cnt] = &from_uid;
+			}
+			ATR(FROMUID);
+			break;
+
+		case 'M': /* from_did */
+			val_arr[cnt] = &na;
+			ATR(FROMDID);
+			break;
+
+		case 'P': /* source_port */
+			p = int2str(rq->rcv.src_port, &src_port.len);
+			memcpy(srcport_buf, p, src_port.len);
+			src_port.s = srcport_buf;
+			val_arr[cnt] = &src_port;
+			ATR(SRCPORT);
+			break;
+
+		case 'R': /* digest_realm */
+			cr = cred_realm(rq);
+			if (cr) val_arr[cnt] = cr;
+			else val_arr[cnt] = &na;
+			ATR(DIGREALM);
+			break;
+
+		case 'S': /* sip_status */
+			if (phrase->len >= 3) {
+				mycode.s = phrase->s;
+				mycode.len = 3;
+				val_arr[cnt] = &mycode;
+			} else val_arr[cnt] = &na;
+			ATR(STATUS);
+			break;
+
+		case 'T': /* to_uri */
+			if (rq->to && (pto = get_to(rq)) && pto->uri.len) val_arr[cnt] = &pto->uri;
+			else val_arr[cnt] = &na;
+			ATR(TOURI);
+			break;
+
+		case 'U': /* to_uid */
+			if (get_to_uid(&to_uid, rq) < 0) {
+				val_arr[cnt] = &na;
+			} else {
+				val_arr[cnt] = &to_uid;
+			}
+			ATR(TOUID);
+			break;
+
+		case 'X': /* response_timestamp */
+			tm_s.s = tm_buf;
+			tm_s.len = TM_BUF_LEN;
+			convert_time(&tm_s, time(0));
+			val_arr[cnt] = &tm_s;
+			ATR(RESTIMESTAMP);
+			break;
+
+		default:
+			LOG(L_CRIT, "BUG:acc:fmt2strar: unknown char: %c\n", *fmt);
+			return 0;
+		} /* switch (*fmt) */
+
+		tl += val_arr[cnt]->len;
+		al += atr_arr[cnt].len;
+		fmt++;
+		cnt++;
+	} /* while (*fmt) */
+
+	*total_len = tl;
+	*attr_len = al;
+	return cnt;
+}
+
+
+
+static int log_request(struct sip_msg* rq, str* ouri, struct hdr_field* to, str* txt, str* phrase, time_t req_time)
+{
+	static str* val_arr[ALL_LOG_FMT_LEN];
+	static str atr_arr[ALL_LOG_FMT_LEN];
+	int len, attr_cnt, attr_len, i;
+	char *log_msg;
+	str buf;
+
+	if (skip_cancel(rq)) return 1;
+
+	attr_cnt = fmt2strar(log_fmt, rq, ouri, to, phrase, &len, &attr_len, val_arr, atr_arr, req_time);
+	if (!attr_cnt) {
+		LOG(L_ERR, "ERROR:acc:log_request: fmt2strar failed\n");
+		return -1;
+	}
+
+	len += attr_len + ACC_LEN + txt->len + A_EOL_LEN + attr_cnt * (A_SEPARATOR_LEN + A_EQ_LEN) - A_SEPARATOR_LEN;
+	log_msg = pkg_malloc(len);
+	if (!log_msg) {
+		LOG(L_ERR, "ERROR:acc:log_request: No memory left for %d bytes\n", len);
+		return -1;
+	}
+
+	     /* skip leading text and begin with first item's
+	      * separator ", " which will be overwritten by the
+	      * leading text later
+	      * */
+	buf.s = log_msg + ACC_LEN + txt->len - A_SEPARATOR_LEN;
+	buf.len = len - ACC_LEN - txt->len + A_SEPARATOR_LEN;
+
+	for (i = 0; i < attr_cnt; i++) {
+		append(buf, A_SEPARATOR);
+		str_append_str(buf, atr_arr[i]);
+		append(buf, A_EQ);
+		str_append_str(buf, *(val_arr[i]))
+	}
+
+	     /* terminating text */
+	append(buf, A_EOL);
+
+	     /* leading text */
+	buf.s = log_msg;
+	buf.len = len;
+	append(buf, ACC);
+	str_append_str(buf, *txt);
+
+	LOG(log_level, "%s", log_msg);
+	pkg_free(log_msg);
+	return 1;
+}
+
+
+static void log_reply(struct cell* t , struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+	str code_str, *ouri;
+	static str lead = STR_STATIC_INIT(ACC_ANSWERED);
+	static char code_buf[INT2STR_MAX_LEN];
+	char* p;
+
+	p = int2str(code, &code_str.len);
+	memcpy(code_buf, p, code_str.len);
+	code_str.s = code_buf;
+
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+
+	log_request(t->uas.request, ouri, valid_to(t,reply), &lead, &code_str, req_time);
+}
+
+
+static void log_ack(struct cell* t , struct sip_msg *ack, time_t req_time)
+{
+	struct sip_msg *rq;
+	struct hdr_field *to;
+	static str lead = STR_STATIC_INIT(ACC_ACKED);
+	static char code_buf[INT2STR_MAX_LEN];
+	str code_str;
+	char* p;
+
+	rq = t->uas.request;
+	if (ack->to) to = ack->to;
+	else to = rq->to;
+	p = int2str(t->uas.status, &code_str.len);
+	memcpy(code_buf, p, code_str.len);
+	code_str.s = code_buf;
+	log_request(ack, GET_RURI(ack), to, &lead, &code_str, req_time);
+}
+
+
+static void log_missed(struct cell* t, struct sip_msg* reply, unsigned int code, time_t req_time)
+{
+        str acc_text, *ouri;
+        static str leading_text = STR_STATIC_INIT(ACC_MISSED);
+
+        get_reply_status(&acc_text, reply, code);
+        if (acc_text.s == 0) {
+                LOG(L_ERR, "ERROR:acc:log_missed: get_reply_status failed\n" );
+                return;
+        }
+
+	if (t->relayed_reply_branch >= 0) {
+	    ouri = &t->uac[t->relayed_reply_branch].uri;
+	} else {
+	    ouri = GET_NEXT_HOP(t->uas.request);
+	}
+
+        log_request(t->uas.request, ouri, valid_to(t, reply), &leading_text, &acc_text, req_time);
+        pkg_free(acc_text.s);
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_log_request1(struct sip_msg *rq, char* p1, char* p2)
+{
+	str phrase;
+	str txt = STR_STATIC_INIT(ACC_REQUEST);
+
+	if (get_str_fparam(&phrase, rq, (fparam_t*)p1) < 0) {
+	    phrase.s = 0;
+	    phrase.len = 0;
+	}
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_log_missed1(struct sip_msg *rq, char* p1, char* p2)
+{
+	str phrase;
+	str txt = STR_STATIC_INIT(ACC_MISSED);
+
+	if (get_str_fparam(&phrase, rq, (fparam_t*)p1) < 0) {
+	    phrase.s = 0;
+	    phrase.len = 0;
+	}
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
+}
+
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_log_request0(struct sip_msg *rq, char* p1, char* p2)
+{
+	static str phrase = STR_NULL;
+	str txt = STR_STATIC_INIT(ACC_REQUEST);
+	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
+}
+
+
+/* these wrappers parse all what may be needed; they don't care about
+ * the result -- accounting functions just display "unavailable" if there
+ * is nothing meaningful
+ */
+static int acc_log_missed0(struct sip_msg *rq, char* p1, char* p2)
+{
+	static str phrase = STR_NULL;
+	str txt = STR_STATIC_INIT(ACC_MISSED);
+	preparse_req(rq);
+	return log_request(rq, GET_RURI(rq), rq->to, &txt, &phrase, time(0));
+}
+
+
+
+static void ack_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (is_acc_on(t->uas.request)) {
+		preparse_req(ps->req);
+		log_ack(t, ps->req, (time_t)*(ps->param));
+	}
+}
+
+
+/* initiate a report if we previously enabled MC accounting for this t */
+static void failure_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	/* validation */
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:failure_handler: No uas.request, skipping local transaction\n");
+		return;
+	}
+
+	if (is_invite(t) && ps->code >= 300) {
+		if (is_mc_on(t->uas.request)) {
+			log_missed(t, ps->rpl, ps->code, (time_t)*(ps->param));
+			resetflag(t->uas.request, log_missed_flag);
+		}
+	}
+}
+
+
+/* initiate a report if we previously enabled accounting for this t */
+static void replyout_handler(struct cell* t, int type, struct tmcb_params* ps)
+{
+	if (t->uas.request == 0) {
+		DBG("DBG:acc:replyout_handler: No uas.request, local transaction, skipping\n");
+		return;
+	}
+
+	     /* acc_onreply is bound to TMCB_REPLY which may be called
+	      * from _reply, like when FR hits; we should not miss this
+	      * event for missed calls either
+	      */
+	failure_handler(t, type, ps);
+	if (!should_acc_reply(t, ps->code)) return;
+	if (is_acc_on(t->uas.request)) log_reply(t, ps->rpl, ps->code, (time_t)*(ps->param));
+}
+
+
+/* parse incoming replies before cloning */
+static void replyin_handler(struct cell *t, int type, struct tmcb_params* ps)
+{
+	     /* validation */
+	if (t->uas.request == 0) {
+		LOG(L_ERR, "ERROR:acc:replyin_handler:replyin_handler: 0 request\n");
+		return;
+	}
+
+	     /* don't parse replies in which we are not interested */
+	     /* missed calls enabled ? */
+	if (((is_invite(t) && ps->code >= 300 && is_mc_on(t->uas.request))
+	     || should_acc_reply(t, ps->code))
+	    && (ps->rpl && ps->rpl != FAKED_REPLY)) {
+		parse_headers(ps->rpl, HDR_TO_F, 0);
+	}
+}
+
+
+/* prepare message and transaction context for later accounting */
+void on_req(struct cell* t, int type, struct tmcb_params *ps)
+{
+	time_t req_time;
+	     /* Pass the timestamp of the request as a parameter to callbacks */
+	req_time = time(0);
+
+	if (is_acc_on(ps->req) || is_mc_on(ps->req)) {
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_OUT, replyout_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_OUT callback\n");
+			return;
+		}
+
+		if (report_ack) {
+			if (tmb.register_tmcb(0, t, TMCB_E2EACK_IN, ack_handler,
+									(void*)req_time, 0) <= 0) {
+				LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_E2EACK_IN callback\n");
+				return;
+			}
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_ON_FAILURE_RO, failure_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_ON_FAILURE_RO callback\n");
+			return;
+		}
+
+		if (tmb.register_tmcb(0, t, TMCB_RESPONSE_IN, replyin_handler,
+								(void*)req_time, 0) <= 0) {
+			LOG(L_ERR, "ERROR:acc:on_req: Error while registering TMCB_RESPONSE_IN callback\n");
+			return;
+		}
+
+		     /* do some parsing in advance */
+		preparse_req(ps->req);
+		     /* also, if that is INVITE, disallow silent t-drop */
+		if (ps->req->REQ_METHOD == METHOD_INVITE) {
+			DBG("DEBUG: noisy_timer set for accounting\n");
+			t->flags |= T_NOISY_CTIMER_FLAG;
+		}
+	}
+}
+
+
+static int mod_init(void)
+{
+	load_tm_f load_tm;
+
+	     /* import the TM auto-loading function */
+	if ( !(load_tm=(load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
+		LOG(L_ERR, "ERROR:acc:mod_init: can't import load_tm\n");
+		return -1;
+	}
+	     /* let the auto-loading function load all TM stuff */
+	if (load_tm( &tmb )==-1) return -1;
+	if (verify_fmt(log_fmt)==-1) return -1;
+
+	     /* register callbacks*/
+	     /* listen for all incoming requests  */
+	if (tmb.register_tmcb( 0, 0, TMCB_REQUEST_IN, on_req, 0, 0) <= 0) {
+		LOG(L_ERR,"ERROR:acc:mod_init: cannot register TMCB_REQUEST_IN "
+		    "callback\n");
+		return -1;
+	}
+
+	if (parse_attrs(&avps, &avps_n, attrs) < 0) {
+		ERR("Error while parsing 'attrs' module parameter\n");
+		return -1;
+	}
+
+	return 0;
+}

+ 762 - 0
acc_syslog/acc_syslog.xml

@@ -0,0 +1,762 @@
+<?xml version='1.0'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbookid/id/g/4.5/docbookx.dtd">
+
+<refentry xml:id="module.acc_syslog"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>acc_syslog</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>acc_syslog</refname>
+    <refpurpose>Transaction Accounting into the System Log</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>acc_syslog</command> SER module stores information
+      about processed SIP transactions in the system log.
+    </para>
+    <para>
+      Unlike most other modules, <command>acc_syslog</command> is normally
+      not used by calling one of its functions. Instead, it is
+      invoked by setting a certain flag and then starting a transaction
+      by calling a function from the <serdoc:module>tm</serdoc:module>
+      module, usually <serdoc:func>t_relay</serdoc:func>.
+    </para>
+    <para>
+      There are two flags. Their names are determined by the module
+      parameters <serdoc:modparam module="acc_db">log_flag</serdoc:modparam>
+      and <serdoc:modparam module="acc_db">log_missed_flag</serdoc:modparam>
+      respectively. The former is intended to be used for all transactions
+      that are involved in a successfully established call, while the latter
+      is to be used for transactions of a failed call attempt. If either of
+      the flags is set, a line is written to the system log upon completion
+      of the transaction. The difference between the two flags lies in the
+      text that starts the entry. If the
+      <serdoc:modparam module="acc_db">log_flag</serdoc:modparam> is set,
+      the entry starts with the text
+      <literal>"ACC: transaction answered"</literal>, while the
+      <serdoc:modparam module="acc_db">log_missed_flag</serdoc:modparam>
+      causes the entry to start with <literal>"ACC: call missed"</literal>.
+    </para>
+    <para>
+      Neither flag is set by default. In order to activate the writing of
+      accounting entries, you have to explicitely choose flags by setting
+      the module parameters.
+    </para>
+    <para>
+      Normally, you can use the same flag for both parameters since
+      <command>acc_syslog</command>'s internal logic takes care of
+      figuring out the right introduction. It only uses
+      <literal>"ACC: transaction answered"</literal> if the transaction ended
+      with a final response sent upstream with a 2xx
+      status, ie., was successful. Likewise, the module starts entries
+      with <literal>"ACC: call missed"</literal> if the final response sent
+      upstream had a status of 300 or up.
+    </para>
+    <para>
+      Note that the <command>acc_syslog</command> module only writes accounting
+      entries for individual transactions. A call usually consists at least
+      of an INVITE and a BYE transaction for the start and end of a call.
+      Searching the accounting records for the call and translating them
+      into call detail records is not performed by the module.
+    </para>
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_syslog.functions">
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.acc_syslog_log">
+      <title>
+        <function>acc_syslog_log</function>
+        ([<symbol>status</symbol>])
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>acc_syslog_log()</function> function forces
+        <command>acc_syslog</command> to write a syslog entry with
+        information taken from the currently processed request. The
+        entry will by prefixed with the phrase
+        <literal>"ACC: request accounted"</literal>.
+      </para>
+      <para>
+        If the argument <symbol>status</symbol> is given, it contains the
+        status code that should be stored in the field
+        <varname>sip_status</varname>. If it is missing, the field will
+        be set to <literal>0</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.acc_syslog_missed">
+      <title>
+        <function>acc_syslog_missed</function>
+        ([<symbol>status</symbol>])
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>acc_syslog_log()</function> function forces
+        <command>acc_syslog</command> to write a syslog entry with
+        information taken from the currently processed request. The
+        entry will by prefixed with the phrase
+        <literal>"ACC: call missed"</literal>.
+      </para>
+      <para>
+        If the argument <symbol>status</symbol> is given, it contains the
+        status code that should be stored in the field
+        <varname>sip_status</varname>. If it is missing, the field will
+        be set to <literal>0</literal>.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_syslog.parameters">
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.acc_syslog.attrs">
+      <title><parameter>attrs</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>attrs</parameter> parameter contains a comma 
+        separated list of those attributes whose values should be written
+        in the accounting entry. See the description of the field
+        <varname>attrs</varname> in the section
+        <serdoc:link linend="module.acc_syslog.entries">Syslog
+        Entries</serdoc:link> below for information on how these values
+        are stored.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.early_media">
+      <title><parameter>early_media</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>early_media</parameter> parameter determines,
+        whether the arrival of a reponse 183 (Session Progress) should
+        trigger writing of an accounting entry, too. If the parameter is
+        set to <literal>yes</literal>, a 183 response will be treated as
+        a successful response and an entry will be written if
+        the <serdoc:modparam module="acc_syslog">log_flag</serdoc:modparam>
+        is set.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.failed_transactions">
+      <title><parameter>failed_transaction</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>failed_transactions</parameter> parameter determines,
+        whether transaction with a final response indicating a failure
+        (ie., status codes of 300 and up) should be accounted too if the
+        <serdoc:modparam module="acc_syslog">log_flag</serdoc:modparam>
+        is set.
+      </para>
+      <para>
+        The value of the <parameter>failed_transactions</parameter> parameter
+        has no influence on accounting if the
+        <serdoc:modparam module="acc_syslog">log_missed_flag</serdoc:modparam>
+        is set, in which case the transaction will be accounted for only
+        if the final response indicates a failure.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.log_flag">
+      <title><parameter>log_flag</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>flag name</serdoc:paramtype>
+        <serdoc:paramdefault>none</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_flag</parameter> parameter sets the name of the
+        flag that decides whether information about a succeeeded
+        transaction (ie., one with a final response from the 2xx group) is
+        to be written to the system log.
+      </para>
+      <para>
+        By setting the
+        <serdoc:modparam module="acc_syslog">early_media</serdoc:modparam>
+        and
+        <serdoc:modparam module="acc_syslog">failed_transactions</serdoc:modparam>
+        to <literal>yes</literal> setting the <parameter>log_flag</parameter>
+        also triggers writing for 183 provisional responses and failed
+        transactions respectively.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.log_fmt">
+      <title><parameter>log_fmt</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"acdfgimnoprstuxDFIMPRSTUX"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_fmt</parameter> parameter determines, which
+        information is written to the system log. Its value is a string.
+        The various fields described in the section
+        <serdoc:link linkend="module.acc_syslog.entries">Syslog
+        Entries</serdoc:link> below are assigned a letter. If the letter
+        for the field is included in the string, the field will be written.
+      </para>
+      <para>
+        The mapping between letters and fields is as follows:
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><literal>a</literal></term>
+          <listitem><serdoc:field table="acc">attr</serdoc:field></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>c</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_callid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>d</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_tag</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>f</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_from</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>g</literal></term>
+          <listitem>
+            <serdoc:field table="acc">flags</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>i</literal></term>
+          <listitem>
+            <serdoc:field table="acc">in_ruri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>m</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_method</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>n</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_cseq</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>o</literal></term>
+          <listitem>
+            <serdoc:field table="acc">out_ruri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>p</literal></term>
+          <listitem>
+            <serdoc:field table="acc">src_ip</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>r</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_tag</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>t</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_to</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>u</literal></term>
+          <listitem>
+            <serdoc:field table="acc">digest_username</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>x</literal></term>
+          <listitem>
+            <serdoc:field table="acc">request_timestamp</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>D</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_did</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>F</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_uri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>I</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_uid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>M</literal></term>
+          <listitem>
+            <serdoc:field table="acc">from_did</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>P</literal></term>
+          <listitem>
+            <serdoc:field table="acc">src_port</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>R</literal></term>
+          <listitem>
+            <serdoc:field table="acc">digest_realm</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>S</literal></term>
+          <listitem>
+            <serdoc:field table="acc">sip_status</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>T</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_uri</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>U</literal></term>
+          <listitem>
+            <serdoc:field table="acc">to_uid</serdoc:field>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>X</literal></term>
+          <listitem>
+            <serdoc:field table="acc">response_timestamp</serdoc:field>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+      <para>
+        By default, all of the fields are active.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.log_level">
+      <title><parameter>log_level</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>int</serdoc:paramtype>
+        <serdoc:paramdefault>2</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_level</parameter> parameter sets the log
+        level that <command>acc_syslog</command> will use when writing
+        to the syslog. Its value is an integer that correspondents to
+        the priority values defined by syslog as follows:
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><literal>4</literal></term>
+          <listitem><literal>debug</literal></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>3</literal></term>
+          <listitem><literal>info</literal></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>2</literal></term>
+          <listitem><literal>notice</literal></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>1</literal></term>
+          <listitem>
+            <literal>warning</literal> (or <literal>warn</literal>)
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>-1</literal></term>
+          <listitem>
+            <literal>err</literal> (or <literal>error</literal>)
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>-2</literal></term>
+          <listitem><literal>crit</literal></listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>-3</literal></term>
+          <listitem><literal>alert</literal></listitem>
+        </varlistentry>
+      </variablelist>
+      <para>
+        Note that the <parameter>log_level</parameter> must be at less
+        or equal than the core parameter
+        <serdoc:coreparam>debug</serdoc:coreparam> or no entries will
+        be written at all.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.log_missed_flag">
+      <title><parameter>log_missed_flag</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>flag name</serdoc:paramtype>
+        <serdoc:paramdefault>none</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>log_missed_flag</parameter> parameter sets the name
+        of the flag that decides whether information about a failed
+        transaction (ie., one with a final response of status 300 or up) is
+        to be written to the system log.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.report_ack">
+      <title><parameter>report_ack</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>report_ack</parameter> parameter determines,
+        whether a separate accounting entry should be written for the
+        ACK following a 200 OK response. If activated, such entries will
+        be written with the starting text
+        <literal>"ACC: request acknowledged"</literal>.
+      </para>
+      <para>
+        Usually, having the entry for the first transaction is enough
+        and no additional entry is necessary. There is, however, a chance
+        that the ACK does not reach its destination and the call does
+        in fact not start. If you need to know about those cases, you can
+        enable the <parameter>report_ack</parameter> parameter and check
+        that there is an ACK for every INVITE.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.acc_syslog.report_cancels">
+      <title><parameter>report_cancels</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>report_cancels</parameter> parameter determines,
+        whether accounting entries should be made for CANCEL transactions.
+      </para>
+      <para>
+        You can recognize a canceled transaction by its status 487 in the
+        <varname>sip_status</varname> field.
+        Because of this, there is usually no need for the extra entries
+        the CANCEL transaction itself may create.
+      </para>
+    </refsect2>
+
+    <!--
+    <refsect2 xml:id="module.acc_syslog.">
+      <title><parameter></parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype></serdoc:paramtype>
+        <serdoc:paramdefault></serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <serdoc:todo />
+    </refsect2>
+    -->
+
+  </refsect1>
+
+  <refsect1 xml:id="module.acc_syslog.entries">
+    <title>Syslog Entries</title>
+
+    <para>
+      All accounting entries that are written to the system log consist of
+      a single line. It is started with any of the prefixes mentioned
+      above, followed by a colon and a comma-separated list of accounting
+      fields. Each field has a name, followed by an equals sign followed
+      by the fields value.
+    </para>
+    <para>
+      Which entries will be written is determined by the module parameter
+      <serdoc:modparam module="acc_syslog">log_fmt</serdoc:modparam>. By
+      default, it prints all available fields.
+    </para>
+    <para>
+      The following lists and explains all the accounting fields.
+    </para>
+
+    <refsect2>
+      <title><varname>attrs</varname></title>
+      <para>
+        The <varname>attrs</varname> field will contain the attribute and
+        their values that have been selected for storing with the accounting
+        entry.
+      </para>
+      <para>
+        The field will contain a comma separated list of entries. Each
+        entry will start with the name of the attribute, followed by a
+        colon, followed by its value enclosed in double quotation marks.
+        Which attributes will be in the list is controlled by the
+        <serdoc:modparam module="acc_db">attrs</serdoc:modparam> module
+        parameter.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>digest_username</varname></title>
+      <para>
+        The <varname>digest_username</varname> field will contain the
+        username used in authenticating the request. If no
+        authentication was done or the authentication failed, the field
+        will be <literal>NULL</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>digest_realm</varname></title>
+      <para>
+        The <varname>digest_realm</varname> field will contain the realm
+        used in authenticating the request. If no authentication was done
+        or it failed, the field will be <literal>NULL</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>flags</varname></title>
+      <para>
+        The <varname>flags</varname> field will contain the combined
+        numerical value of the flags that where set for the transaction
+        when the entry was written. The value is determined by treating
+        the flags as a bit field with the flag's number as the number of
+        the corresponding bit.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>from_did</varname></title>
+      <para>
+        The <varname>from_did</varname> field will contain the domain ID
+        determined for the callee's domain and stored in the
+        <varname>$fd.did</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>from_tag</varname></title>
+      <para>
+        The <varname>from_tag</varname> field will contain the value of the
+        tag parameter of the From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>from_uid</varname></title>
+      <para>
+        The <varname>from_uid</varname> field will contain the user ID
+        determined for the callee and stored in the
+        <varname>$fu.uid</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>from_uri</varname></title>
+      <para>
+        The <varname>from_uri</varname> field will contain the URI of the
+        From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>in_ruri</varname></title>
+      <para>
+        The <varname>in_ruri</varname> field will contain the Request-URI
+        the request arrived with.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>out_ruri</varname></title>
+      <para>
+        The <varname>out_ruri</varname> field will contain the Request-URI
+        of the winning branch, ie., of that relayed request which caused
+        the final response that was sent upstram.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>request_timestamp</varname></title>
+      <para>
+        The <varname>request_timestamp</varname> field will contain the
+        date and time when the request was received, ie., when the
+        transaction started.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>response_timestamp</varname></title>
+      <para>
+        The <varname>response_timestamp</varname> field will contain the
+        date and time when the response was sent upstream, ie. when the
+        transaction ended.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>server_id</varname></title>
+      <para>
+        The <varname>server_id</varname> field will contain the server ID
+        of the SER instance that processed the transaction. This is useful
+        in a cluster of several SER machines. By giving each machine its
+        own
+        <serdoc:link linkend="core.parameter.server_id">server_id</serdoc:link>
+        you can later determine, which server the accounting entry
+        originated from.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_callid</varname></title>
+      <para>
+        The <varname>sip_callid</varname> field will contain the content
+        of the Call-ID header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_cseq</varname></title>
+      <para>
+        The <varname>sip_cseq</varname> field will contain the sequence
+        number contained in the CSeq header field of the request. The
+        method in that header field (which should be identical to the
+        method of the request) can be found in the
+        <serdoc:field table="acc">sip_method</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_from</varname></title>
+      <para>
+        The <varname>sip_from</varname> field will contain the content
+        of the From header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_method</varname></title>
+      <para>
+        The <varname>sip_method</varname> field will contain the method
+        of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_status</varname></title>
+      <para>
+        The <varname>sip_status</varname> field will contain the status code
+        of the final response or, if
+        <serdoc:modparam module="acc_db">early_media</serdoc:modparam> is set,
+        183 response transmitted upstream for the transaction.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>sip_to</varname></title>
+      <para>
+        The <varname>sip_to</varname> field will contain the content of the
+        To header field of the request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>src_ip</varname></title>
+      <para>
+        The <varname>src_ip</varname> field will contain the source IP
+        address of the received request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>src_port</varname></title>
+      <para>
+        The <varname>src_port</varname> field will contain the source port
+        of the received request.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>to_did</varname></title>
+      <para>
+        The <varname>to_did</varname> field will contain the domain ID
+        determined for the caller's domain and stored in the
+        <varname>$td.did</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>to_tag</varname></title>
+      <para>
+        The <varname>to_tag</varname> field will contain the value of the
+        tag parameter of the response sent upstream or <literal>NULL</literal>
+        if the response was missing this parameter.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>to_uid</varname></title>
+      <para>
+        The <varname>to_uid</varname> field will contain the user ID
+        determined for the caller and stored in the
+        <varname>$tu.uid</varname> attribute or <literal>NULL</literal>
+        if the attribute was not set.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><varname>to_uri</varname></title>
+      <para>
+        The <varname>to_uri</varname> field will contain the URI of the
+        To header field of the request.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 role="manpage">
+    <title>See Also</title>
+    <simplelist type="inline">
+      <member><serdoc:sbin>ser</serdoc:sbin></member>
+      <member><serdoc:file>ser.cfg</serdoc:file></member>
+      <member><serdoc:module>acc_db</serdoc:module></member>
+      <member><serdoc:module>acc_radius</serdoc:module></member>
+      <member><serdoc:module>tm</serdoc:module></member>
+      <member><serdoc:sbin>syslogd</serdoc:sbin></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+<!-- vim:sw=2 sta et sts=2 ai
+  -->

+ 203 - 0
acc_syslog/attrs.h

@@ -0,0 +1,203 @@
+/*
+ * Accounting module
+ *
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG FOKUS
+ * Copyright (C) 2005 iptelorg GmbH
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef _ATTRS_H
+#define _ATTRS_H
+
+#include <string.h>
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../usr_avp.h"
+#include "../../trim.h"
+#include "../../str.h"
+#include "../../ut.h"
+
+
+/*
+ * Parse the value of attrs parameter 
+ */
+static int parse_attrs(avp_ident_t** avps, int* avps_n, char* attrs)
+{
+	str token;
+
+	token.s = strtok(attrs, ",");
+
+	*avps = 0;
+	*avps_n = 0;
+	while(token.s) {
+		token.len = strlen(token.s);
+		trim(&token);
+		
+		if (token.len && token.s[0] == '$') {
+			token.s++;
+			token.len--;
+		} else goto skip;
+
+		*avps = pkg_realloc(*avps, sizeof(avp_ident_t) * (*avps_n + 1));
+		if (!*avps) {
+			ERR("No memory left\n");
+			goto err;
+		}
+
+		if (parse_avp_ident(&token, &(*avps)[*avps_n]) < 0) {
+			ERR("Error while parsing AVP id '%.*s'\n", token.len, ZSW(token.s));
+			goto err;
+		}
+		DBG("Found attribute $%.*s\n", (*avps)[*avps_n].name.s.len, (*avps)[*avps_n].name.s.s);
+
+		(*avps_n)++;
+	skip:
+		token.s = strtok(0, ",");
+	}
+	return 0;
+ err:
+	if (*avps) pkg_free(*avps);
+	return -1;
+}
+
+
+
+#define attrs_append(dst, src)           \
+do {                                     \
+    if ((dst).len < (src).len) {         \
+	ERR("Buffer too small\n");       \
+	goto error;                      \
+    }                                    \
+    memcpy((dst).s, (src).s, (src).len); \
+    (dst).s += (src).len;                \
+    (dst).len -= (src).len;              \
+} while(0)
+
+
+/*
+ * Escape delimiter characters
+ */
+#define attrs_append_esc(dst, src, esc_quote)               \
+do {                                                        \
+	int i;                                              \
+	char* w;                                            \
+	                                                    \
+	if ((dst).len < ((src).len * 2)) {                  \
+		ERR("Buffer too small\n");                  \
+		goto error;                                 \
+	}                                                   \
+                                                            \
+	w = (dst).s;                                        \
+	for(i = 0; i < (src).len; i++) {                    \
+		switch((src).s[i]) {                        \
+		case '\n': *w++ = '\\'; *w++ = 'n';  break; \
+		case '\r': *w++ = '\\'; *w++ = 'r';  break; \
+		case '\t': *w++ = '\\'; *w++ = 't';  break; \
+		case '\\': *w++ = '\\'; *w++ = '\\'; break; \
+		case '\0': *w++ = '\\'; *w++ = '0';  break; \
+                case '"':                                   \
+                    if (esc_quote) {                        \
+                        *w++ = '\\'; *w++ = 'q';            \
+                    } else {                                \
+                        *w++ = (src).s[i];                  \
+                    }                                       \
+                    break;                                  \
+		case ':':  *w++ = '\\'; *w++ = 'o';  break; \
+		case ',':  *w++ = '\\'; *w++ = 'c';  break; \
+		default:   *w++ = (src).s[i];        break; \
+		}                                           \
+	}                                                   \
+	(dst).len -= w - (dst).s;                           \
+	(dst).s = w;                                        \
+} while(0)
+
+
+#define attrs_append_printf(dst, fmt, args...)              \
+do {                                                        \
+	int len = snprintf((dst).s, (dst).len, (fmt), ## args);	\
+	if (len < 0 || len >= (dst).len) {                      \
+		ERR("Buffer too small\n");                          \
+		goto error;                                         \
+	}                                                       \
+	(dst).s += len;                                         \
+	(dst).len -= len;                                       \
+} while(0)
+
+
+
+#define ATTRS_BUF_LEN 4096
+static str* print_attrs(avp_ident_t* avps, int avps_n, int quote)
+{
+	static str quote_s = STR_STATIC_INIT("\"");
+	static str attrs_name_delim = STR_STATIC_INIT(":");
+	static str attrs_delim = STR_STATIC_INIT(",");
+	static char buf[ATTRS_BUF_LEN];
+	static str res;
+	int i;
+	struct search_state st;
+	avp_value_t val;
+	str p;
+
+	p.s = buf;
+	p.len = ATTRS_BUF_LEN - 1;
+
+	if (quote && avps_n) {
+		attrs_append(p, quote_s);
+	}
+
+	for(i = 0; i < avps_n; i++) {
+		avp_t *this_avp = search_first_avp(avps[i].flags, avps[i].name, &val, &st);
+		if (!this_avp) continue;
+		attrs_append(p, avps[i].name.s);
+		attrs_append(p, attrs_name_delim);
+		if (this_avp->flags & AVP_VAL_STR)
+			attrs_append_esc(p, val.s, quote);
+		else
+			attrs_append_printf(p, "%d", val.n);
+
+		while(search_next_avp(&st, &val)) {
+			attrs_append(p, attrs_delim);
+			attrs_append(p, avps[i].name.s);
+			attrs_append(p, attrs_name_delim);
+			attrs_append_esc(p, val.s, quote);
+		}
+		if (i < (avps_n - 1)) attrs_append(p, attrs_delim); 
+	}
+
+	if (quote && avps_n) {
+		attrs_append(p, quote_s);
+	}
+
+	*p.s = '\0';
+	res.s = buf;
+	res.len = p.s - buf;
+	return &res;
+
+ error:
+	return 0;
+}
+
+#endif /* _ATTRS_H */

+ 4 - 0
acc_syslog/doc/Makefile

@@ -0,0 +1,4 @@
+docs = acc_syslog.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 206 - 0
acc_syslog/doc/acc_syslog.xml

@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="acc" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Jiri</firstname>
+		<surname>Kuthan</surname>
+		<affiliation><orgname>iptel.org</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+	</authorgroup>
+
+	<copyright>
+	    <year>2002</year>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+
+    </sectioninfo>
+
+    <title>Acc Module</title>
+    
+    <section id="overview">
+	<title>Overview</title>
+	<para>
+	    acc module is used to report on transactions to syslog,
+	    <abbrev>SQL</abbrev> and <acronym>RADIUS</acronym>.
+	</para>
+	<para>
+	    To report on a transaction using syslog, use "setflag" to mark a
+	    transaction you are interested in with a flag, load accounting
+	    module and set its "log_flag" to the same flag number. The acc
+	    module will then report on completed transaction to syslog. A
+	    typical usage of the module takes no acc-specific script command --
+	    the functionality binds invisibly through transaction processing.
+	    Script writers just need to mark the transaction for accounting
+	    with proper setflag.
+	</para>
+	<para>
+	    What is printed depends on module's "log_fmt" parameter. It's a
+	    string with characters specifying which parts of request should be
+	    printed:
+	    <itemizedlist>
+		<listitem>
+		    <para>c = Call-Id</para>
+		</listitem>
+		<listitem>
+		    <para>d = To tag (Dst)</para>
+		</listitem>
+		<listitem>
+		    <para>f = From</para>
+		</listitem>
+		<listitem>
+		    <para>i = Inbound Request-URI</para>
+		</listitem>
+		<listitem>
+		    <para>m = Method</para>
+		</listitem>
+		<listitem>
+		    <para>o = Outbound Request-URI</para>
+		</listitem>
+		<listitem>
+		    <para>r = fRom</para>
+		</listitem>
+		<listitem>
+		    <para>s = Status</para>
+		</listitem>
+		<listitem>
+		    <para>t = To</para>
+		</listitem>
+		<listitem>
+		    <para>u = digest Username</para>
+		</listitem>
+		<listitem>
+		    <para>p = username Part of inbound Request-URI</para>
+		</listitem>
+	    </itemizedlist>
+	    If a value is not present in request, "n/a" is accounted instead.
+	</para>
+	<note>
+	    <itemizedlist>
+		<listitem>
+		    <para>
+			A single INVITE may produce multiple accounting reports
+			-- that's due to SIP forking feature
+		    </para>
+		</listitem>
+		<listitem>
+		    <para>
+			Subsequent ACKs and other requests do not hit the
+			server and can't be accounted unless record-routing is
+			enforced. The ACKs assert very little useful
+			information anyway and reporting on INVITE's 200 makes
+			most accounting scenarios happy.
+		    </para>
+		</listitem>
+		<listitem>
+		    <para>
+			There is no session accounting -- ser maintains no
+			sessions. If one needs to correlate INVITEs with BYEs
+			for example for purpose of billing, then it is better
+			done in the entity which collects accounting
+			information. Otherwise, SIP server would have to become
+			sessions-stateful, which would very badly impact its
+			scalability.
+		    </para>
+		</listitem>
+		<listitem>
+		    <para>
+			If a UA fails in middle of conversation, a proxy will
+			never learn it.  In general, a better practice is to
+			account from an end-device (such as PSTN gateway),
+			which best knows about call status (including media
+			status and PSTN status in case of the gateway).
+		    </para>
+		</listitem>
+	    </itemizedlist>
+	</note>
+	<para>
+	    Support for SQL and RADIUS works analogously. You need to enable it
+	    by recompiling the module with properly set defines. Uncomment the
+	    SQL_ACC and RAD_ACC lines in
+	    <filename>modules/acc/Makefile</filename>. To compile SQL support,
+	    you need to have mysqlclient package on your system. To compile
+	    RADIUS support, you need to have radiusclient installed on your
+	    system (version 0.5.0 or higher is required) which is available
+	    from
+	    <ulink url='http://developer.berlios.de/projects/radiusclient-ng/'>
+	    http://developer.berlios.de/projects/radiusclient-ng/</ulink>. The
+	    radius client needs to be configured properly. To do so, use the
+	    template in <filename>sip_router/etc/radiusclient.conf</filename>
+	    and make sure that module's <varname>radius_config</varname>
+	    parameter points to its location.
+	    <!-- FIXME In particular, accounting secret
+	    must match that one configured in server and proper dictionary is
+	    used (one is available in ). -->
+	    Uses along with <ulink
+	    url="http://www.freeradius.org">FreeRADIUS</ulink> and <ulink
+	    url="http://www.open.com.au/radiator">Radiator</ulink> servers have
+	    been reported to us.
+	</para>
+	<para>
+	    Both mysql and radius libraries must be dynamically linkable. You
+	    need to configure your OS so that SER, when started, will find
+	    them. Typically, you do so by manipulating LD_LIBRARY_PATH
+	    environment variable or configuring ld.so.
+	</para>
+	<example>
+	    <title>General Example</title>
+	    <programlisting>
+<![CDATA[
+loadmodule "modules/acc/acc.so"
+modparam("acc", "log_level", 1)
+modparam("acc", "log_flag", 1)
+
+if (uri=~"sip:+49") /* calls to Germany */ {
+    if (!proxy_authorize("iptel.org" /* realm */,
+                         "subscriber" /* table name */))  {
+        proxy_challenge("iptel.org" /* realm */, "0" /* no qop */ );
+        break;
+    }
+
+    if (method=="INVITE" & !check_from()) {
+        log("from!=digest\n");
+        sl_send_reply("403","Forbidden");
+        break;
+    }
+
+    setflag(1); /* set for accounting (the same value as in log_flag!)
+    t_relay(); 	/* enter stateful mode now */
+};
+]]>
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="dependencies">
+	<title>Dependencies</title>
+	<para>
+	    The module depends on the following modules (in the other words the listed modules
+	    must be loaded before this module):
+	    <itemizedlist>
+		<listitem>
+		    <formalpara>
+			<title>tm</title>
+			<para>Transaction Manager</para>
+		    </formalpara>
+		</listitem>
+		<listitem>
+		    <formalpara>
+			<title>A database module (mysql,postgres,dbtext)</title>
+			<para>If compiled with database support.</para>
+		    </formalpara>
+		</listitem>
+	    </itemizedlist>
+	</para>
+    </section>
+    
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+    
+</section>
+    

+ 120 - 0
acc_syslog/doc/functions.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="acc.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+    
+    <title>Functions</title>
+    
+    <section id="acc_log_request1">
+	<title><function>acc_log_request(comment)</function></title>
+	<para>
+	    <function>acc_request</function> reports on a request, for example,
+	    it can be used to report on missed calls to off-line users who are
+	    replied 404. To avoid multiple reports on UDP request
+	    retransmission, you would need to embed the action in stateful
+	    processing.
+	</para>
+	<para>
+	    Meaning of the parameters is as follows:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>comment</emphasis> - Comment to be appended.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>acc_log_request usage</title>
+	    <programlisting>
+...
+acc_log_request("Some comment");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="acc_db_request2">
+	<title><function>acc_db_request(comment, table)</function></title>
+	<para>
+	    Like <function>acc_log_request</function>,
+	    <function>acc_db_request</function> reports on a request. The
+	    report is sent to database at "db_url", in the table referred to in
+	    the second action parameter
+	</para>
+	<para>
+	    Meaning of the parameters is as follows:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>comment</emphasis> - Comment to be appended.</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>table</emphasis> - Database table to be used.</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>acc_db_request usage</title>
+	    <programlisting>
+...
+acc_log_request("Some comment", "Some table");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="acc_rad_request">
+	<title><function>acc_rad_request(comment)</function></title>
+	<para>
+	    Like <function>acc_log_request</function>,
+	    <function>acc_rad_request</function> reports on a request. It
+	    reports to radius server as configured in "radius_config".
+	</para>
+	<para>
+	    Meaning of the parameters is as follows:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+		    <emphasis>comment</emphasis> - Comment to be appended.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>acc_rad_request usage</title>
+	    <programlisting>
+...
+acc_rad_request("Some comment");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="acc_diam_request">
+	    <title><function>acc_diam_request(comment)</function></title>
+	<para>
+	    Like <function>acc_log_request</function>, <function
+		moreinfo="none">acc_diam_request</function> reports on a
+	    request. It reports to Diameter server.  
+	</para>
+	<para>
+	    Meaning of the parameters is as follows:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>comment</emphasis> - Comment to be appended.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>acc_diam_request usage</title>
+	    <programlisting>
+...
+acc_diam_request("Some comment");
+...
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 334 - 0
acc_syslog/doc/params.xml

@@ -0,0 +1,334 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="acc.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="log_level">
+	<title><varname>log_level</varname> (integer)</title>
+	<para>
+	    Log level at which accounting messages are issued to syslog.
+	</para>
+	<para>
+	    Default value is L_NOTICE.
+	</para>
+	<example>
+	    <title>log_level example</title>
+	    <programlisting>
+modparam("acc", "log_level", 2)   # Set log_level to 2
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="log_fmt">
+	<title><varname>log_fmt</varname> (string)</title>
+	<para>
+	    Defines what parts of header fields will be printed to syslog, see
+	    "overview" for list of accepted values.
+	</para>
+	<para>
+	    Default value is "miocfs".
+	</para>
+
+	<example>
+	    <title>log_fmt example</title>
+	    <programlisting>
+modparam("acc", "log_fmt", "mfs")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="early_media">
+	<title><varname>early_media</varname> (integer)</title>
+	<para>
+	    Should be early media (183) accounted too ?
+	</para>
+	<para>
+	    Default value is 0 (no).
+	</para>
+	<example>
+	    <title>early_media example</title>
+	    <programlisting>
+modparam("acc", "early_media", 1)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="failed_transactions">
+	<title><varname>failed_transactions</varname> (integer)</title>
+	<para>
+	    This parameter controls whether failed transactions (with final
+	    reply &gt;= 300) should be accounted too.
+	</para>
+	<para>
+	    Default value is 0 (no).
+	</para>
+	<example>
+	    <title>failed_transactions example</title>
+	    <programlisting>
+modparam("acc", "failed_transactions", 1)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="log_flag">
+	<title><varname>log_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account a transaction.
+	</para>
+	<para>
+	    Default value is 1.
+	</para>
+	<example>
+	    <title>log_flag example</title>
+	    <programlisting>
+modparam("acc", "log_flag", 2)
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="log_missed_flag">
+	<title><varname>log_missed_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account missed calls.
+	</para>
+	<para>
+	    Default value is 2.
+	</para>
+	<example>
+	    <title>log_missed_flag example</title>
+	    <programlisting>
+modparam("acc", "log_missed_flag", 3)
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="report_ack">
+	<title><varname>report_ack</varname> (integer)</title>
+	<para>
+	    Shall acc attempt to account e2e ACKs too ? Note that this is
+	    really only an attempt, as e2e ACKs may take a different path
+	    (unless RR enabled) and mismatch original INVITE (e2e ACKs are a
+	    separate transaction).
+	</para>
+	<para>
+	    Default value is 1 (yes).
+	</para>
+	<example>
+	    <title>report_ack example</title>
+	    <programlisting>
+modparam("acc", "report_ack", 0)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="report_cancels">
+	<title><varname>report_cancels</varname> (integer)</title>
+	<para>
+	    By default, CANCEL reporting is disabled -- most accounting
+	    applications are happy to see INVITE's cancellation status.  Turn
+	    on if you explicitly want to account CANCEL transactions.
+	</para>
+	<para>
+	    Default value is 0 (no).
+	</para>
+	<example>
+	    <title>report_cancels example</title>
+	    <programlisting>
+modparam("acc", "report_cancels", 1)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="radius_config">
+	<title><varname>radius_config</varname> (string)</title>
+	<para>
+	    <emphasis>This parameter is radius specific.</emphasis> Path to
+	    radius client configuration file, set the referred config file
+	    correctly and specify there address of server, shared secret
+	    (should equal that in
+	    <filename>/usr/local/etc/raddb/clients</filename> for freeRadius
+	    servers) and dictionary, see etc for an example of config file and
+	    dictionary.
+	</para>
+	<para>
+	    Default value is <quote>/usr/local/etc/radiusclient/radiusclient.conf</quote>.
+	</para>
+	<example>
+	    <title>radius_config example</title>
+	    <programlisting>
+modparam("acc", "radius_config", "/etc/radiusclient/radiusclient.conf")
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="service_type">
+	<title><varname>service_type</varname> (integer)</title>
+	<para>
+	    Radius service type used for accounting.
+	</para>
+	<para>
+	    Default value is 15 (SIP).
+	</para>
+	<example>
+	    <title>service_type example</title>
+	    <programlisting>
+modparam("acc", "service_type", 16)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="radius_flag">
+	<title><varname>radius_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account a transaction --
+	    RADIUS specific.
+	</para>
+	<para>
+	    Default value is 1.
+	</para>
+	<example>
+	    <title>radius_flag example</title>
+	    <programlisting>
+		modparam("acc", "radius_flag", 2)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="radius_missed_flag">
+	<title><varname>radius_missed_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account missed calls --
+	    RADIUS specific.
+	</para>
+	<para>
+	    Default value is 2.
+	</para>
+	<example>
+	    <title>radius_missed_flag example</title>
+	    <programlisting>
+modparam("acc", "radius_missed_flag", 3)
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="db_url">
+	<title><varname>db_url</varname> (string)</title>
+	<para>
+	    SQL address -- database specific.
+	</para>
+	<para>
+	    Default value is "mysql://ser:heslo@localhost/ser"
+	</para>
+	<example>
+	    <title>db_url example</title>
+	    <programlisting>
+modparam("acc", "db_url", "mysql://user:password@localhost/ser")
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="db_flag">
+	<title><varname>db_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account a transaction --
+	    database specific.
+	</para>
+	<para>
+	    Default value is 1.
+	</para>
+	<example>
+	    <title>db_flag example</title>
+	    <programlisting>
+modparam("acc", "db_flag", 2)
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="db_missed_flag">
+	<title><varname>db_missed_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account missed calls --
+	    database specific.
+	</para>
+	<para>
+	    Default value is 2.
+	</para>
+	<example>
+	    <title>db_missed_flag example</title>
+	    <programlisting>
+modparam("acc", "db_missed_flag", 3)
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="diameter_flag">
+	<title><varname>diameter_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account a transaction --
+	    DIAMETER specific.
+	</para>
+	<para>
+	    Default value is 1.
+	</para>
+	<example>
+	    <title>diameter_flag example</title>
+	    <programlisting>
+modparam("acc", "diameter_flag", 2)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="diameter_missed_flag">
+	<title><varname>diameter_missed_flag</varname> (integer)</title>
+	<para>
+	    Request flag which needs to be set to account missed calls --
+	    DIAMETER specific.
+	</para>
+	<para>
+	    Default value is 2.
+	</para>
+	<example>
+	    <title>diameter_missed_flag example</title>
+	    <programlisting>
+modparam("acc", "diameter_missed_flag", 3)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="diameter_client_host">
+	<title><varname>diameter_client_host</varname> (string)</title>
+	<para>
+	    Hostname of the machine where the DIAMETER Client is running -- DIAMETER specific.
+	</para>
+	<para>
+	    Default value is "localhost".
+	</para>
+	<example>
+	    <title>diameter_client_host example</title>
+	    <programlisting>
+modparam("acc", "diameter_client_host", "iptel.org")
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="diameter_client_port">
+	<title><varname>diameter_client_port</varname> (int)</title>
+	<para>
+	    Port number where the Diameter Client is listening -- DIAMETER specific.
+	</para>
+	<para>
+	    Default value is 3000.
+	</para>
+	<example>
+	    <title>diameter_client_host example</title>
+	    <programlisting>
+modparam("acc", "diameter_client_port", 3000)
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 17 - 0
auth_radius/Makefile

@@ -0,0 +1,17 @@
+# $Id$
+#
+# Digest Authentication - Radius support
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+include ../../Makefile.radius
+
+auto_gen=
+NAME=auth_radius.so
+
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 147 - 0
auth_radius/README

@@ -0,0 +1,147 @@
+1. Auth_radius Module
+
+Jan Janak
+
+   FhG Fokus
+   <[email protected]>
+
+Juha Heinanen
+
+   Song Networks
+   <[email protected]>
+
+Stelios Sidiroglou-Douskos
+
+   Copyright © 2002, 2003 FhG FOKUS
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Dependencies
+   1.3. Parameters
+
+        1.3.1. radius_config (string)
+        1.3.2. service_type (integer)
+        1.3.3. use_ruri_flag (integer)
+
+   1.4. Functions
+
+        1.4.1. radius_www_authorize(realm)
+        1.4.2. radius_proxy_authorize(realm)
+
+1.1. Overview
+
+   This module contains functions that are used to perform authentication
+   using a Radius server. Basically the proxy will pass along the
+   credentials to the radius server which will in turn send a reply
+   containing result of the authentication. So basically the whole
+   authentication is done in the Radius server. Before sending the request
+   to the radius server we perform some sanity checks over the credentials
+   to make sure that only well formed credentials will get to the server.
+   We have implemented radius authentication according to
+   draft-sterman-aaa-sip-00. This module requires radiusclient library
+   version 0.5.0 or higher which is available from
+   http://developer.berlios.de/projects/radiusclient-ng/.
+
+1.2. Dependencies
+
+   The module depends on the following modules (in the other words the
+   listed modules must be loaded before this module):
+     * auth.  Generic authentication functions.
+
+1.3. Parameters
+
+1.3.1. radius_config (string)
+
+   This is the location of the configuration file of radius client
+   libraries.
+
+   Default value is "/usr/local/etc/radiusclient/radiusclient.conf".
+
+   Example 1. radius_config parameter usage
+modparam("auth_radius", "radius_config", "/etc/radiusclient.conf")
+
+1.3.2. service_type (integer)
+
+   This is the value of the Service-Type radius attribute to be used. The
+   default should be fine for most people. See your radius client include
+   files for numbers to be put in this parameter if you need to change it.
+
+   Default value is "15".
+
+   Example 2. service_type usage
+modparam("auth_radius", "service_type", 15)
+
+1.3.3. use_ruri_flag (integer)
+
+   When this parameter is set to the value other than "-1" and the request
+   being authenticated has flag with matching number set via setflag()
+   function, use Request URI instead of uri parameter value from the
+   Authorization / Proxy-Authorization header field to perform RADIUS
+   authentication. This is intended to provide workaround for misbehaving
+   NAT / routers / ALGs that alter request in the transit, breaking
+   authentication. At the time of this writing, certain versions of
+   Linksys WRT54GL are known to do that.
+
+   Default value is "-1".
+
+   Example 3. use_ruri_flag usage
+modparam("auth_radius", "use_ruri_flag", 22)
+
+1.4. Functions
+
+1.4.1. radius_www_authorize(realm)
+
+   The function verifies credentials according to RFC2617. If the
+   credentials are verified successfully then the function will succeed
+   and mark the credentials as authorized (marked credentials can be later
+   used by some other functions). If the function was unable to verify the
+   credentials for some reason then it will fail and the script should
+   call www_challenge which will challenge the user again.
+
+   This function will, in fact, perform sanity checks over the received
+   credentials and then pass them along to the radius server which will
+   verify the credentials and return whether they are valid or not.
+
+   Meaning of the parameter is as follows:
+     * realm - Realm is a opaque string that the user agent should present
+       to the user so he can decide what username and password to use.
+       Usually this is domain of the host the server is running on.
+       If an empty string "" is used then the server will generate it from
+       the request. In case of REGISTER requests To header field domain
+       will be used (because this header field represents a user being
+       registered), for all other messages From header field domain will
+       be used.
+
+   Example 4. radius_www_authorize usage
+...
+if (!radius_www_authorize("iptel.org")) {
+    www_challenge("iptel.org", "1");
+};
+...
+
+1.4.2. radius_proxy_authorize(realm)
+
+   The function verifies credentials according to RFC2617. If the
+   credentials are verified successfully then the function will succeed
+   and mark the credentials as authorized (marked credentials can be later
+   used by some other functions). If the function was unable to verify the
+   credentials for some reason then it will fail and the script should
+   call proxy_challenge which will challenge the user again.
+
+   This function will, in fact, perform sanity checks over the received
+   credentials and then pass them along to the radius server which will
+   verify the credentials and return whether they are valid or not.
+
+   Meaning of the parameter is as follows:
+     * realm - Realm is a opaque string that the user agent should present
+       to the user so he can decide what username and password to use.
+       Usually this is domain of the host the server is running on.
+       If an empty string "" is used then the server will generate it from
+       the request. From header field domain will be used as realm.
+
+   Example 5. proxy_authorize usage
+...
+if (!radius_proxy_authorize("")) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...

+ 313 - 0
auth_radius/authorize.c

@@ -0,0 +1,313 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on authorize.c from radius_auth (janakj)
+ */
+
+
+#include <string.h>
+#include <stdlib.h>
+#include "../../mem/mem.h"
+#include "../../str.h"
+#include "../../sr_module.h"
+#include "../../parser/hf.h"
+#include "../../parser/digest/digest.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_to.h"
+#include "../../dprint.h"
+#include "../../id.h"
+#include "../../ut.h"
+#include "../../modules/auth/api.h"
+#include "authorize.h"
+#include "sterman.h"
+#include "authrad_mod.h"
+
+
+static void attr_name_value(str* name, str* value, VALUE_PAIR* vp)
+{
+    int i;
+    
+    for (i = 0; i < vp->lvalue; i++) {
+	if (vp->strvalue[i] == ':' || vp->strvalue[i] == '=') {
+	    name->s = vp->strvalue;
+	    name->len = i;
+	    
+	    if (i == (vp->lvalue - 1)) {
+		value->s = (char*)0;
+		value->len = 0;
+	    } else {
+		value->s = vp->strvalue + i + 1;
+		value->len = vp->lvalue - i - 1;
+	    }
+	    return;
+	}
+    }
+
+    name->len = value->len = 0;
+    name->s = value->s = (char*)0;
+}
+
+
+/*
+ * Generate AVPs from the database result
+ */
+static int generate_avps(VALUE_PAIR* received)
+{
+	int_str name, val;
+	VALUE_PAIR *vp;
+	
+	vp = rc_avpair_get(received, ATTRID(attrs[A_SER_UID].v), VENDOR(attrs[A_SER_UID].v));
+	if (vp == NULL) {
+	    WARN("RADIUS server did not send SER-UID attribute in digest authentication reply\n");
+	    return -1;
+	}
+	val.s.len = vp->lvalue;
+	val.s.s = vp->strvalue;
+	name.s.s = "uid";
+	name.s.len = 3;
+
+	if (add_avp(AVP_TRACK_FROM | AVP_CLASS_USER | AVP_NAME_STR | AVP_VAL_STR, name, val) < 0) {
+	    ERR("Unable to create UID attribute\n");
+	    return -1;
+	}
+
+	vp = received;
+	while ((vp = rc_avpair_get(vp, ATTRID(attrs[A_SER_ATTR].v), VENDOR(attrs[A_SER_ATTR].v)))) {
+		attr_name_value(&name.s, &val.s, vp);
+		if (name.s.len == 0) {
+		    ERR("Missing attribute name\n");
+		    return -1;
+		}
+		
+		if (add_avp(AVP_TRACK_FROM | AVP_CLASS_USER | AVP_NAME_STR | AVP_VAL_STR, name, val) < 0) {
+			LOG(L_ERR, "generate_avps: Unable to create a new AVP\n");
+			return -1;
+		} else {
+			DBG("generate_avps: AVP '%.*s'='%.*s' has been added\n",
+			    name.s.len, ZSW(name.s.s), 
+			    val.s.len, ZSW(val.s.s));
+		}
+		vp = vp->next;
+	}
+	
+	return 0;
+}
+
+
+
+
+/* 
+ * Extract URI depending on the request from To or From header 
+ */
+static inline int get_uri(struct sip_msg* _m, str** _uri)
+{
+	if ((REQ_LINE(_m).method.len == 8) && (memcmp(REQ_LINE(_m).method.s, "REGISTER", 8) == 0)) {
+		if (!_m->to && ((parse_headers(_m, HDR_TO_F, 0) == -1) || !_m->to)) {
+			LOG(L_ERR, "get_uri(): To header field not found or malformed\n");
+			return -1;
+		}
+		*_uri = &(get_to(_m)->uri);
+	} else {
+		if (parse_from_header(_m) == -1) {
+			LOG(L_ERR, "get_uri(): Error while parsing headers\n");
+			return -2;
+		}
+		*_uri = &(get_from(_m)->uri);
+	}
+	return 0;
+}
+
+
+/*
+ * Authorize digest credentials
+ */
+static inline int authenticate(struct sip_msg* msg, str* realm,
+			       hdr_types_t hftype)
+{
+	int res;
+	auth_result_t ret;
+	struct hdr_field* h;
+	auth_body_t* cred;
+	str* uri;
+	struct sip_uri puri;
+	str user, did;
+	VALUE_PAIR* received;
+
+	cred = 0;
+	ret = -1;
+	user.s = 0;
+	received = NULL;
+
+	switch(auth_api.pre_auth(msg, realm, hftype, &h, NULL)) {
+	default:
+		BUG("unexpected reply '%d'.\n", auth_api.pre_auth(msg, realm, hftype,
+				&h, NULL));
+#ifdef EXTRA_DEBUG
+		abort();
+#endif
+	case NONCE_REUSED:
+		LM_DBG("nonce reused");
+		ret = AUTH_NONCE_REUSED;
+		goto end;
+	case STALE_NONCE:
+		LM_DBG("stale nonce\n");
+		ret = AUTH_STALE_NONCE;
+		goto end;
+	case NO_CREDENTIALS:
+		LM_DBG("no credentials\n");
+		ret = AUTH_NO_CREDENTIALS;
+	case ERROR:
+	case BAD_CREDENTIALS:
+	    ret = -3;
+	    goto end;
+
+	case NOT_AUTHENTICATED:
+	    ret = -1;
+	    goto end;
+
+	case DO_AUTHENTICATION:
+	    break;
+
+	case AUTHENTICATED:
+	    ret = 1;
+	    goto end;
+	}
+
+	cred = (auth_body_t*)h->parsed;
+
+	if (use_did) {
+	    if (msg->REQ_METHOD == METHOD_REGISTER) {
+			ret = get_to_did(&did, msg);
+	    } else {
+			ret = get_from_did(&did, msg);
+	    }
+	    if (ret == 0) {
+			did.s = DEFAULT_DID;
+			did.len = sizeof(DEFAULT_DID) - 1;
+	    }
+	} else {
+	    did.len = 0;
+	    did.s = 0;
+	}
+
+	if (get_uri(msg, &uri) < 0) {
+		LOG(L_ERR, "authorize(): From/To URI not found\n");
+		ret = -1;
+		goto end;
+	}
+	
+	if (parse_uri(uri->s, uri->len, &puri) < 0) {
+		LOG(L_ERR, "authorize(): Error while parsing From/To URI\n");
+		ret = -1;
+		goto end;
+	}
+
+	user.s = (char *)pkg_malloc(puri.user.len);
+	if (user.s == NULL) {
+		LOG(L_ERR, "authorize: No memory left\n");
+		ret = -1;
+		goto end;
+	}
+	un_escape(&(puri.user), &user);
+
+	res = radius_authorize_sterman(&received, msg, &cred->digest, &msg->first_line.u.request.method, &user);
+	if (res == 1) {
+	    switch(auth_api.post_auth(msg, h)) {
+	    case ERROR:             
+	    case BAD_CREDENTIALS:
+		ret = -2;
+		break;
+
+	    case NOT_AUTHENTICATED:
+		ret = -1;
+		break;
+
+	    case AUTHENTICATED:
+		if (generate_avps(received) < 0) {
+		    ret = -1;
+		    break;
+		}
+		ret = 1;
+		break;
+
+	    default:
+		ret = -1;
+		break;
+	    }
+	} else {
+	    ret = -1;
+	}
+
+ end:
+	if (received) rc_avpair_free(received);
+	if (user.s) pkg_free(user.s);
+	if (ret < 0) {
+	    if (auth_api.build_challenge(msg, (cred ? cred->stale : 0), realm, NULL, NULL, hftype) < 0) {
+		ERR("Error while creating challenge\n");
+		ret = -2;
+	    }
+	}
+	return ret;
+}
+
+
+/*
+ * Authorize using Proxy-Authorize header field
+ */
+int radius_proxy_authorize(struct sip_msg* _msg, char* p1, char* p2)
+{
+    str realm;
+
+    if (get_str_fparam(&realm, _msg, (fparam_t*)p1) < 0) {
+	ERR("Cannot obtain digest realm from parameter '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+	 /* realm parameter is converted to str* in str_fixup */
+    return authenticate(_msg, &realm, HDR_PROXYAUTH_T);
+}
+
+
+/*
+ * Authorize using WWW-Authorize header field
+ */
+int radius_www_authorize(struct sip_msg* _msg, char* p1, char* p2)
+{
+    str realm;
+
+    if (get_str_fparam(&realm, _msg, (fparam_t*)p1) < 0) {
+	ERR("Cannot obtain digest realm from parameter '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+    return authenticate(_msg, &realm, HDR_AUTHORIZATION_T);
+}
+

+ 52 - 0
auth_radius/authorize.h

@@ -0,0 +1,52 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on authorize.h from radius_auth (janakj)
+ */
+
+#ifndef AUTHORIZE_H
+#define AUTHORIZE_H
+
+#include "../../parser/msg_parser.h"
+
+
+/*
+ * Authorize using Proxy-Authorization header field
+ */
+int radius_proxy_authorize(struct sip_msg* _msg, char* _realm, char* _s2);
+
+
+/*
+ * Authorize using WWW-Authorization header field
+ */
+int radius_www_authorize(struct sip_msg* _msg, char* _realm, char* _s2);
+
+
+#endif /* AUTHORIZE_H */

+ 204 - 0
auth_radius/authrad_mod.c

@@ -0,0 +1,204 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ *  2003-03-09: Based on auth_mod.c from radius_auth (janakj)
+ *  2003-03-11: New module interface (janakj)
+ *  2003-03-16: flags export parameter added (janakj)
+ *  2003-03-19  all mallocs/frees replaced w/ pkg_malloc/pkg_free (andrei)
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../../sr_module.h"
+#include "../../error.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../config.h"
+#include "authrad_mod.h"
+#include "authorize.h"
+
+#ifdef RADIUSCLIENT_NG_4
+#  include <radiusclient.h>
+# else
+#  include <radiusclient-ng.h>
+#endif
+
+MODULE_VERSION
+
+struct attr attrs[A_MAX];
+struct val vals[V_MAX];
+void *rh;
+
+auth_api_s_t auth_api;
+
+static int mod_init(void);                        /* Module initialization function */
+
+int use_did = 1;
+int use_ruri_flag = -1;
+
+
+/*
+ * Module parameter variables
+ */
+static char* radius_config = "/usr/local/etc/radiusclient/radiusclient.conf";
+static int service_type = -1;
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+	{"radius_www_authorize",      radius_www_authorize,   1, fixup_var_str_1, REQUEST_ROUTE},
+	{"radius_proxy_authorize",    radius_proxy_authorize, 1, fixup_var_str_1, REQUEST_ROUTE},
+	{"radius_www_authenticate",   radius_www_authorize,   1, fixup_var_str_1, REQUEST_ROUTE},
+	{"radius_proxy_authenticate", radius_proxy_authorize, 1, fixup_var_str_1, REQUEST_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"radius_config",    PARAM_STRING, &radius_config },
+	{"service_type",     PARAM_INT,   &service_type   },
+	{"use_did",          PARAM_INT,   &use_did },
+	{"use_ruri_flag",    PARAM_INT,   &use_ruri_flag },
+	{0, 0, 0}
+};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+	"auth_radius",
+	cmds,       /* Exported functions */
+	0,          /* RPC methods */
+	params,     /* Exported parameters */
+	mod_init,   /* module initialization function */
+	0,          /* response function */
+	0,          /* destroy function */
+	0,          /* oncancel function */
+	0           /* child initialization function */
+};
+
+
+/*
+ * Module initialization function
+ */
+static int mod_init(void)
+{
+	DICT_VENDOR *vend;
+	bind_auth_s_t bind_auth;
+
+	DBG("auth_radius - Initializing\n");
+
+	memset(attrs, 0, sizeof(attrs));
+	memset(vals, 0, sizeof(vals));
+
+	     /* RFC2865, RFC2866 */
+	attrs[A_USER_NAME].n			= "User-Name";
+	attrs[A_SERVICE_TYPE].n			= "Service-Type";
+
+	     /* draft-sterman-aaa-sip-00 */
+	attrs[A_DIGEST_RESPONSE].n		= "Digest-Response";
+	attrs[A_DIGEST_REALM].n			= "Digest-Realm";
+	attrs[A_DIGEST_NONCE].n			= "Digest-Nonce";
+	attrs[A_DIGEST_METHOD].n		= "Digest-Method";
+	attrs[A_DIGEST_URI].n			= "Digest-URI";
+	attrs[A_DIGEST_QOP].n			= "Digest-QOP";
+	attrs[A_DIGEST_ALGORITHM].n		= "Digest-Algorithm";
+	attrs[A_DIGEST_BODY_DIGEST].n		= "Digest-Body-Digest";
+	attrs[A_DIGEST_CNONCE].n		= "Digest-CNonce";
+	attrs[A_DIGEST_NONCE_COUNT].n		= "Digest-Nonce-Count";
+	attrs[A_DIGEST_USER_NAME].n		= "Digest-User-Name";
+
+	     /* SER-specific */
+	attrs[A_SER_URI_USER].n			= "SER-Uri-User";
+	attrs[A_SER_ATTR].n	                = "SER-Attr";
+	attrs[A_SER_UID].n                      = "SER-UID";
+	attrs[A_SER_SERVICE_TYPE].n             = "SER-Service-Type";
+
+	     /* SER-Service-Type */
+	vals[V_DIGEST_AUTHENTICATION].n         = "Digest-Authentication";
+
+	attrs[A_CISCO_AVPAIR].n			= "Cisco-AVPair";
+
+	     /* draft-schulzrinne-sipping-radius-accounting-00 */
+	vals[V_SIP_SESSION].n			= "Sip-Session";
+
+
+	if ((rh = rc_read_config(radius_config)) == NULL) {
+		LOG(L_ERR, "auth_radius: Error opening configuration file \n");
+		return -1;
+	}
+
+	if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0) {
+		LOG(L_ERR, "auth_radius: Error opening dictionary file \n");
+		return -2;
+	}
+
+	vend = rc_dict_findvend(rh, "Cisco");
+	if (vend == NULL) {
+		DBG("auth_radius: No `Cisco' vendor in Radius "
+			   "dictionary\n");
+		attrs[A_CISCO_AVPAIR].n = NULL;
+	}
+	
+	vend = rc_dict_findvend(rh, "iptelorg");
+	if (vend == NULL) {
+		ERR("RADIUS dictionary is missing required vendor 'iptelorg'\n");
+		return -1;
+	}
+
+
+        bind_auth = (bind_auth_s_t)find_export("bind_auth_s", 0, 0);
+        if (!bind_auth) {
+		LOG(L_ERR, "auth_radius: Unable to find bind_auth function\n");
+	        return -1;
+	}
+
+	if (bind_auth(&auth_api) < 0) {
+		LOG(L_ERR, "auth_radius: Cannot bind to auth module\n");
+		return -4;
+	}
+
+	INIT_AV(rh, attrs, vals, "auth_radius", -5, -6);
+
+	if (service_type != -1) {
+		vals[V_SIP_SESSION].v = service_type;
+	}
+
+	return 0;
+}
+

+ 50 - 0
auth_radius/authrad_mod.h

@@ -0,0 +1,50 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on auth_mod.h from radius_authorize (janakj)
+ */
+
+
+#ifndef AUTHRAD_MOD_H
+#define AUTHRAD_MOD_H
+
+#include "../../modules/auth/api.h"
+#include "../../rad_dict.h"
+
+extern struct attr attrs[];
+extern struct val vals[];
+extern void *rh;
+
+extern int use_did;
+extern int use_ruri_flag;
+
+extern auth_api_s_t auth_api;
+
+#endif /* AUTHRAD_MOD_H */

+ 4 - 0
auth_radius/doc/Makefile

@@ -0,0 +1,4 @@
+docs = auth_radius.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 74 - 0
auth_radius/doc/auth_radius.xml

@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="auth_radius" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Jan</firstname>
+		<surname>Janak</surname>
+		<affiliation><orgname>FhG Fokus</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+	    <author>
+		<firstname>Juha</firstname>
+		<surname>Heinanen</surname>
+		<affiliation><orgname>Song Networks</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+	    <author>
+		<firstname>Stelios</firstname>
+		<surname>Sidiroglou-Douskos</surname>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2002</year>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+    </sectioninfo>
+
+    <title>Auth_radius Module</title>
+
+    <section id="auth_radius.overview">
+	<title>Overview</title>
+	<para>
+	    This module contains functions that are used to perform
+	    authentication using a Radius server. Basically the proxy will pass
+	    along the credentials to the radius server which will in turn send
+	    a reply containing result of the authentication. So basically the
+	    whole authentication is done in the Radius server. Before sending
+	    the request to the radius server we perform some sanity checks over
+	    the credentials to make sure that only well formed credentials will
+	    get to the server. We have implemented radius authentication
+	    according to draft-sterman-aaa-sip-00. This module requires
+	    radiusclient library version 0.5.0 or higher which is available
+	    from <ulink
+	    url='http://developer.berlios.de/projects/radiusclient-ng/'>
+	    http://developer.berlios.de/projects/radiusclient-ng/</ulink>.
+	</para>
+    </section>
+
+    <section id="auth_radius.dep">
+	<title>Dependencies</title>
+	<para>
+	    The module depends on the following modules (in the other words the listed modules
+	    must be loaded before this module):
+	    <itemizedlist>
+	    	<listitem>
+		    <formalpara>
+		        <title>auth</title>
+		        <para>
+			    Generic authentication functions.
+		        </para>
+		    </formalpara>
+		</listitem>
+	    </itemizedlist>
+	</para>
+    </section>
+
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+    
+</section>

+ 104 - 0
auth_radius/doc/functions.xml

@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="auth_radius.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Functions</title>
+    
+    <section id="radius_www_authorize">
+	<title><function>radius_www_authorize(realm)</function></title>
+	<para>
+	    The function verifies credentials according to <ulink
+		url="http://www.ietf.org/rfc/rfc2617.txt">RFC2617</ulink>. If
+	    the credentials are verified successfully then the function
+	    will succeed and mark the credentials as authorized (marked
+	    credentials can be later used by some other functions). If the
+	    function was unable to verify the credentials for some reason
+	    then it will fail and the script should call
+	    <function>www_challenge</function> which will challenge the
+	    user again.
+	</para>
+	<para>
+	    This function will, in fact, perform sanity checks over the
+	    received credentials and then pass them along to the radius server
+	    which will verify the credentials and return whether they are valid
+	    or not.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+		    <emphasis>realm</emphasis> - Realm is a opaque string that
+		    the user agent should present to the user so he can decide
+		    what username and password to use. Usually this is domain
+		    of the host the server is running on.
+		</para>
+		<para>
+		    If an empty string "" is used then the server will generate
+		    it from the request. In case of REGISTER requests To header
+		    field domain will be used (because this header field
+		    represents a user being registered), for all other messages
+		    From header field domain will be used.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>radius_www_authorize</function> usage</title>
+	    <programlisting>
+...
+if (!radius_www_authorize("iptel.org")) {
+    www_challenge("iptel.org", "1");
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="radius_proxy_authorize">
+	<title><function moreinfo="none">radius_proxy_authorize(realm)</function></title>
+	<para>
+	    The function verifies credentials according to <ulink
+		url="http://www.ietf.org/rfc/rfc2617.txt">RFC2617</ulink>. If
+	    the credentials are verified successfully then the function
+	    will succeed and mark the credentials as authorized (marked
+	    credentials can be later used by some other functions). If the
+	    function was unable to verify the credentials for some reason
+	    then it will fail and the script should call
+	    <function>proxy_challenge</function> which will challenge the
+	    user again.
+	</para>
+	<para>
+	    This function will, in fact, perform sanity checks over the
+	    received credentials and then pass them along to the radius server
+	    which will verify the credentials and return whether they are valid
+	    or not.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>realm</emphasis> - Realm is a opaque string
+		    that the user agent should present to the user so he can
+		    decide what username and password to use. Usually this is
+		    domain of the host the server is running on.
+		</para>
+		<para>
+		    If an empty string "" is used then the server will generate it
+		    from the request. From header field domain will be used as realm.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>proxy_authorize usage</title>
+	    <programlisting>
+...
+if (!radius_proxy_authorize("")) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 70 - 0
auth_radius/doc/params.xml

@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="auth_radius.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="auth_radius.radius_config">
+	<title><varname>radius_config</varname> (string)</title>
+	<para>
+	    This is the location of the configuration file of radius client
+	    libraries.
+	</para>
+	<para>
+	    Default value is "/usr/local/etc/radiusclient/radiusclient.conf".
+	</para>
+	<example>
+	    <title><varname>radius_config</varname> parameter usage</title>
+	    <programlisting>
+modparam("auth_radius", "radius_config", "/etc/radiusclient.conf")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="auth_radius.service_type">
+	<title><varname>service_type</varname> (integer)</title>
+	<para>
+	    This is the value of the Service-Type radius attribute to be
+	    used. The default should be fine for most people. See your radius
+	    client include files for numbers to be put in this parameter if you
+	    need to change it.
+	</para>
+	<para>
+	    Default value is "15".
+	</para>
+	<example>
+	    <title><varname>service_type</varname> usage</title>
+	    <programlisting>
+modparam("auth_radius", "service_type", 15)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="auth_radius.use_ruri_flag">
+	<title><varname>use_ruri_flag</varname> (integer)</title>
+	<para>
+	    When this parameter is set to the value other than "-1" and the
+	    request being authenticated has flag with matching number set
+	    via setflag() function, use Request URI instead of uri parameter
+	    value from the Authorization / Proxy-Authorization header field
+	    to perform RADIUS authentication.  This is intended to provide
+	    workaround for misbehaving NAT / routers / ALGs that alter request
+	    in the transit, breaking authentication.  At the time of this
+	    writing, certain versions of Linksys WRT54GL are known to do that.
+	</para>
+	<para>
+	    Default value is "-1".
+	</para>
+	<example>
+	    <title><varname>use_ruri_flag</varname> usage</title>
+	    <programlisting>
+modparam("auth_radius", "use_ruri_flag", 22)
+	    </programlisting>
+	</example>
+    </section>
+
+</section>

+ 283 - 0
auth_radius/sterman.c

@@ -0,0 +1,283 @@
+/* 
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on digest.c from radius_auth module (janakj)
+ */
+
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../modules/auth/api.h"
+#include "../../rad_dict.h"
+#include "../../usr_avp.h"
+#include "../../ut.h"
+#include "sterman.h"
+#include "authrad_mod.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+static int add_cisco_vsa(VALUE_PAIR** send, struct sip_msg* msg)
+{
+	str callid;
+
+	if (!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) == -1) {
+		LOG(L_ERR, "add_cisco_vsa: Cannot parse Call-ID header field\n");
+		return -1;
+	}
+
+	if (!msg->callid) {
+		LOG(L_ERR, "add_cisco_vsa: Call-ID header field not found\n");
+		return -1;
+	}
+
+	callid.len = msg->callid->body.len + 8;
+	callid.s = pkg_malloc(callid.len);
+	if (callid.s == NULL) {
+		LOG(L_ERR, "add_cisco_vsa: No memory left\n");
+		return -1;
+	}
+
+	memcpy(callid.s, "call-id=", 8);
+	memcpy(callid.s + 8, msg->callid->body.s, msg->callid->body.len);
+
+	if (rc_avpair_add(rh, send, ATTRID(attrs[A_CISCO_AVPAIR].v), callid.s,
+			  callid.len, VENDOR(attrs[A_CISCO_AVPAIR].v)) == 0) {
+		LOG(L_ERR, "add_cisco_vsa: Unable to add Cisco-AVPair attribute\n");
+		pkg_free(callid.s);
+		return -1;
+	}
+
+	pkg_free(callid.s);
+	return 0;
+}
+
+
+/*
+ * This function creates and submits radius authentication request as per
+ * draft-sterman-aaa-sip-00.txt.  In addition, _user parameter is included
+ * in the request as value of a SER specific attribute type SIP-URI-User,
+ * which can be be used as a check item in the request.  Service type of
+ * the request is Authenticate-Only.
+ */
+int radius_authorize_sterman(VALUE_PAIR** received, struct sip_msg* _msg, dig_cred_t* _cred, str* _method, str* _user) 
+{
+	static char msg[4096];
+	VALUE_PAIR *send;
+	UINT4 service, ser_service_type;
+	str method, user, user_name;
+	str *ruri;
+	int i;
+	
+	send = 0;
+
+	if (!(_cred && _method && _user)) {
+		LOG(L_ERR, "radius_authorize_sterman(): Invalid parameter value\n");
+		return -1;
+	}
+
+	method = *_method;
+	user = *_user;
+	
+	/*
+	 * Add all the user digest parameters according to the qop defined.
+	 * Most devices tested only offer support for the simplest digest.
+	 */
+	if (_cred->username.domain.len) {
+	        if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_USER_NAME].v), 
+				   _cred->username.whole.s, _cred->username.whole.len, 
+			           VENDOR(attrs[A_USER_NAME].v))) {
+			LOG(L_ERR, "radius_authorize_sterman(): Unable to add User-Name attribute\n");
+			goto err;
+		}
+	} else {
+		user_name.len = _cred->username.user.len + _cred->realm.len + 1;
+		user_name.s = pkg_malloc(user_name.len);
+		if (!user_name.s) {
+			LOG(L_ERR, "radius_authorize_sterman(): No memory left\n");
+			return -3;
+		}
+		memcpy(user_name.s, _cred->username.whole.s, _cred->username.whole.len);
+		user_name.s[_cred->username.whole.len] = '@';
+		memcpy(user_name.s + _cred->username.whole.len + 1, _cred->realm.s, _cred->realm.len);
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_USER_NAME].v), 
+				   user_name.s, user_name.len, 
+				   VENDOR(attrs[A_USER_NAME].v))) {
+			LOG(L_ERR, "sterman(): Unable to add User-Name attribute\n");
+			pkg_free(user_name.s);
+			goto err;
+		}
+		pkg_free(user_name.s);
+	}
+
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_USER_NAME].v), 
+			   _cred->username.whole.s, _cred->username.whole.len, 
+			   VENDOR(attrs[A_DIGEST_USER_NAME].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Digest-User-Name attribute\n");
+		goto err;
+	}
+
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_REALM].v), 
+			   _cred->realm.s, _cred->realm.len, 
+			   VENDOR(attrs[A_DIGEST_REALM].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Digest-Realm attribute\n");
+		goto err;
+	}
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_NONCE].v), 
+			   _cred->nonce.s, _cred->nonce.len, 
+			   VENDOR(attrs[A_DIGEST_NONCE].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Digest-Nonce attribute\n");
+		goto err;
+	}
+	
+	if (use_ruri_flag < 0 || isflagset(_msg, use_ruri_flag) != 1) {
+		ruri = &_cred->uri;
+	} else {
+		ruri = GET_RURI(_msg);
+	}
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_URI].v), 
+			   ruri->s, ruri->len, 
+			   VENDOR(attrs[A_DIGEST_URI].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Digest-URI attribute\n");
+		goto err;
+	}
+		
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_METHOD].v),
+			   method.s, method.len, 
+			   VENDOR(attrs[A_DIGEST_METHOD].v))) {
+	        LOG(L_ERR, "sterman(): Unable to add Digest-Method attribute\n");
+		goto err;
+	}
+	
+	/* 
+	 * Add the additional authentication fields according to the QOP.
+	 */
+	if (_cred->qop.qop_parsed == QOP_AUTH) {
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_QOP].v), "auth", 4,
+				   VENDOR(attrs[A_DIGEST_QOP].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-QOP attribute\n");
+			goto err;
+		}
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_NONCE_COUNT].v), 
+				   _cred->nc.s, _cred->nc.len,
+				   VENDOR(attrs[A_DIGEST_NONCE_COUNT].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-CNonce-Count attribute\n");
+			goto err;
+		}
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_CNONCE].v), 
+				   _cred->cnonce.s, _cred->cnonce.len,
+				   VENDOR(attrs[A_DIGEST_CNONCE].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-CNonce attribute\n");
+			goto err;
+		}
+	} else if (_cred->qop.qop_parsed == QOP_AUTHINT) {
+	        if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_QOP].v), "auth-int", 8, 
+				   VENDOR(attrs[A_DIGEST_QOP].v))) {
+		        LOG(L_ERR, "sterman(): Unable to add Digest-QOP attribute\n");
+			goto err;
+		}
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_NONCE_COUNT].v), 
+				   _cred->nc.s, _cred->nc.len, 
+				   VENDOR(attrs[A_DIGEST_NONCE_COUNT].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-Nonce-Count attribute\n");
+			goto err;
+		}
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_CNONCE].v), 
+				   _cred->cnonce.s, _cred->cnonce.len, 
+				   VENDOR(attrs[A_DIGEST_CNONCE].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-CNonce attribute\n");
+			goto err;
+		}
+		if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_BODY_DIGEST].v), 
+				   _cred->opaque.s, _cred->opaque.len, 
+				   VENDOR(attrs[A_DIGEST_BODY_DIGEST].v))) {
+			LOG(L_ERR, "sterman(): Unable to add Digest-Body-Digest attribute\n");
+			goto err;
+		}
+		
+	} else  {
+		/* send nothing for qop == "" */
+	}
+
+	/* Add the response... What to calculate against... */
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_DIGEST_RESPONSE].v), 
+			   _cred->response.s, _cred->response.len, 
+			   VENDOR(attrs[A_DIGEST_RESPONSE].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Digest-Response attribute\n");
+		goto err;
+	}
+
+	/* Indicate the service type, Authenticate only in our case */
+	service = vals[V_SIP_SESSION].v;
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SERVICE_TYPE].v), 
+			   &service, -1, 
+			   VENDOR(attrs[A_SERVICE_TYPE].v))) {
+	        LOG(L_ERR, "sterman(): Unable to add Service-Type attribute\n");
+		goto err;
+	}
+
+	/* Indicate the service type, Authenticate only in our case */
+	ser_service_type = vals[V_DIGEST_AUTHENTICATION].v;
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_SERVICE_TYPE].v), 
+			   &ser_service_type, -1, 
+			   VENDOR(attrs[A_SER_SERVICE_TYPE].v))) {
+		LOG(L_ERR, "sterman(): Unable to add SER-Service-Type attribute\n");
+		goto err;
+	}
+
+	/* Add SIP URI as a check item */
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_URI_USER].v), 
+			   user.s, user.len, 
+			   VENDOR(attrs[A_SER_URI_USER].v))) {
+		LOG(L_ERR, "sterman(): Unable to add Sip-URI-User attribute\n");
+		goto err;
+	}
+
+	if (attrs[A_CISCO_AVPAIR].n != NULL) {
+		if (add_cisco_vsa(&send, _msg)) {
+			goto err;
+		}
+	}
+
+	/* Send request */
+	if ((i = rc_auth(rh, SIP_PORT, send, received, msg)) == OK_RC) {
+		DBG("radius_authorize_sterman(): Success\n");
+		rc_avpair_free(send);
+		send = 0;
+		return 1;
+	} else {
+		DBG("radius_authorize_sterman(): Failure\n");
+		goto err;
+	}
+
+ err:
+	if (send) rc_avpair_free(send);	
+	return -1;
+}

+ 56 - 0
auth_radius/sterman.h

@@ -0,0 +1,56 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Radius support
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on auth_mod.h from radius_authorize (janakj)
+ */
+
+#ifndef STERMAN_H
+#define STERMAN_H
+
+#ifdef RADIUSCLIENT_NG_4
+#  include <radiusclient.h>
+#else
+#  include <radiusclient-ng.h>
+#endif
+
+#include "../../str.h"
+#include "../../parser/digest/digest_parser.h"
+
+
+/*
+ * This function creates and submits radius authentication request as per
+ * draft-sterman-aaa-sip-00.txt.  In addition, _user parameter is included
+ * in the request as value of a SER specific attribute type SIP-URI-User,
+ * which can be be used as a check item in the request.  Service type of
+ * the request is Authenticate-Only.
+ */
+int radius_authorize_sterman(VALUE_PAIR** received, struct sip_msg* _msg, dig_cred_t* _cred, str* _method, str* _user); 
+
+#endif /* STERMAN_H */

+ 18 - 0
avp_radius/Makefile

@@ -0,0 +1,18 @@
+# $Id$
+#
+# avp_radius module Makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+include ../../Makefile.radius
+
+auto_gen=
+NAME=avp_radius.so
+
+
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 85 - 0
avp_radius/README

@@ -0,0 +1,85 @@
+1. Avp_radius Module
+
+Juha Heinanen
+
+   <[email protected]>
+
+   Copyright © 2004 Juha Heinanen
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Parameters
+
+        1.2.1. radius_config (string)
+        1.2.2. caller_service_type (integer)
+
+   1.3. Functions
+
+        1.3.1. avp_load_radius(user)
+
+1.1. Overview
+
+   avp_radius module allows loading of user's attributes into AVPs from
+   Radius. User's name and domain can be based on From URI, Request URI,
+   or authenticated credentials.
+
+   The module assumes that Radius returns the AVPs as values of reply
+   attribute SIP-AVP. Its value must be a string of form "name:value" or
+   of form "name#value". In the first case, value is interpreted as a
+   string and in the second case as an int (second case has not been
+   implemented yet).
+
+   The module prefixes each attribute name as returned from Radius by
+   string "caller_" or "callee_" depending if caller's or callee's
+   attributes are loaded.
+
+1.2. Parameters
+
+1.2.1. radius_config (string)
+
+   This is the location of the configuration file of radius client
+   libraries.
+
+   Default value is "/usr/local/etc/radiusclient/radiusclient.conf".
+
+   Example 1. radius_config parameter usage
+modparam("avp_radius", "radius_config", "/etc/radiusclient.conf")
+
+1.2.2. caller_service_type (integer)
+
+   This is the value of the Service-Type radius attribute to be used, when
+   caller's attributes are loaded.
+
+   Default value is dictionary value of "SIP-Caller-AVPs" Service-Type.
+
+   Example 2. radius_config parameter usage
+modparam("avp_radius", "caller_service_type", 18)
+
+1.3. Functions
+
+1.3.1. avp_load_radius(user)
+
+   The functions loads user's attributes from radius and stores them into
+   AVPs. Parameter "user" is used to indicate, whose attributes are
+   loaded. Possible values are "caller", "caller_from_ruri", "callee", and
+   "digest".
+
+   In "caller" case, attributes belong to the user of the From URI, in
+   "callee" case, to the user of the Request URI, and, in "digest" case,
+   to the authenticated user.
+
+   The "caller_from_uri" case loads attribute value pairs defined for
+   caller (default SER-Caller-AVPs), but uses the user in the Request URI.
+   This is useful for the case where a call has been forwarded by callee
+   (Request URI) and you need to look up whether callee is allowed to
+   forward the call to ex. PSTN or if the call should be anonymous (i.e.
+   not show info about who diverted the call).
+
+   AVP name returned from Radius is prefixed by string "caller_", if
+   avp_load_radius parameter is "caller" or "digest", and by "callee_", if
+   parameter is "callee".
+
+   Example 3. avp_load_radius usage
+...
+avp_load_radius("callee")
+...

+ 413 - 0
avp_radius/avp_radius.c

@@ -0,0 +1,413 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2004 Juha Heinanen <[email protected]>
+ * Copyright (C) 2004 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifdef RADIUSCLIENT_NG_4
+#  include <radiusclient.h>
+# else
+#  include <radiusclient-ng.h>
+#endif
+
+#include "../../rad_dict.h"
+#include "../../sr_module.h"
+#include "../../mem/mem.h"
+#include "../../parser/digest/digest_parser.h"
+#include "../../parser/digest/digest.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/parse_from.h"
+#include "../domain/domain.h"
+#include "../../usr_avp.h"
+#include "../../ut.h"
+#include "../../config.h"
+
+
+MODULE_VERSION
+
+
+static int mod_init(void);
+static int radius_load_attrs(struct sip_msg*, char*, char*);
+static int attrs_fixup(void**, int);
+
+static char *radius_config = "/usr/local/etc/radiusclient/radiusclient.conf";
+
+static void *rh;
+struct attr attrs[A_MAX];
+struct val vals[V_MAX];
+
+static domain_get_did_t dm_get_did = NULL;
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+    {"radius_load_attrs", radius_load_attrs, 2, attrs_fixup, REQUEST_ROUTE | FAILURE_ROUTE},
+    {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+    {"radius_config", PARAM_STRING, &radius_config },
+    {0, 0, 0}
+};
+
+
+struct module_exports exports = {
+    "avp_radius",
+    cmds,      /* Exported commands */
+    0,         /* RPC methods */
+    params,    /* Exported parameters */
+    mod_init,  /* module initialization function */
+    0,         /* response function*/
+    0,         /* destroy function */
+    0,         /* oncancel function */
+    0          /* per-child init function */
+};
+
+
+static int mod_init(void)
+{
+    DICT_VENDOR *vend;
+    memset(attrs, 0, sizeof(attrs));
+    memset(vals, 0, sizeof(vals));
+    
+    attrs[A_USER_NAME].n        = "User-Name";
+    attrs[A_SER_SERVICE_TYPE].n = "SER-Service-Type";
+    attrs[A_SER_ATTR].n	        = "SER-Attr";
+    attrs[A_SER_DID].n          = "SER-DID";
+    attrs[A_SER_URI_SCHEME].n   = "SER-Uri-Scheme";
+    
+    vals[V_GET_URI_ATTRS].n  = "Get-URI-Attrs";
+    vals[V_GET_USER_ATTRS].n = "Get-User-Attrs";
+
+	 /* open log */
+    rc_openlog("ser");
+    
+	 /* read config */
+    if ((rh = rc_read_config(radius_config)) == NULL) {
+	LOG(L_ERR, "avp_radius: Error opening radius config file: %s\n",
+	    radius_config);
+	return -1;
+    }
+    
+	 /* read dictionary */
+    if (rc_read_dictionary(rh, rc_conf_str(rh, "dictionary")) != 0) {
+	LOG(L_ERR, "avp_radius: Error reading radius dictionary\n");
+	return -1;
+    }
+    
+    vend = rc_dict_findvend(rh, "iptelorg");
+    if (vend == NULL) {
+	ERR("RADIUS dictionary is missing required vendor 'iptelorg'\n");
+	return -1;
+    }
+
+    INIT_AV(rh, attrs, vals, "avp", -1, -1);
+    return 0;
+}
+
+
+static void attr_name_value(str* name, str* value, VALUE_PAIR* vp)
+{
+    int i;
+    
+    for (i = 0; i < vp->lvalue; i++) {
+	if (vp->strvalue[i] == ':' || vp->strvalue[i] == '=') {
+	    name->s = vp->strvalue;
+	    name->len = i;
+	    
+	    if (i == (vp->lvalue - 1)) {
+		value->s = (char*)0;
+		value->len = 0;
+	    } else {
+		value->s = vp->strvalue + i + 1;
+		value->len = vp->lvalue - i - 1;
+	    }
+	    return;
+	}
+    }
+
+    name->len = value->len = 0;
+    name->s = value->s = (char*)0;
+}
+
+
+/*
+ * Generate AVPs from the database result
+ */
+static int generate_avps(unsigned int flags, VALUE_PAIR* received)
+{
+    int_str name, val;
+    VALUE_PAIR *vp;
+    
+    vp = received;
+    while ((vp = rc_avpair_get(vp, ATTRID(attrs[A_SER_ATTR].v), VENDOR(attrs[A_SER_ATTR].v)))) {
+	attr_name_value(&name.s, &val.s, vp);
+	if (name.s.len == 0) {
+	    ERR("Missing attribute name\n");
+	    return -1;
+	}
+
+	if (add_avp(flags | AVP_NAME_STR | AVP_VAL_STR, name, val) < 0) {
+	    ERR("Unable to create a new SER attribute\n");
+	    return -1;
+	}
+	vp = vp->next;
+    }
+    
+    return 0;
+}
+
+
+static int load_user_attrs(struct sip_msg* msg, unsigned long flags, fparam_t* fp)
+{
+    static char rad_msg[4096];
+    str uid;
+    UINT4 service;
+    VALUE_PAIR* send, *received;
+
+    send = NULL;
+    received = NULL;
+
+    if (get_str_fparam(&uid, msg, (fparam_t*)fp) < 0) {
+	ERR("Unable to get UID\n");
+	return -1;
+    }
+
+    service = vals[V_GET_USER_ATTRS].v;
+
+    if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_USER_NAME].v), 
+		       uid.s, uid.len,
+		       VENDOR(attrs[A_USER_NAME].v))) {
+	ERR("Error while adding A_USER_NAME\n");
+	goto error;
+    }
+    
+    if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_SERVICE_TYPE].v), 
+		       &vals[V_GET_USER_ATTRS].v, -1, 
+		       VENDOR(attrs[A_SER_SERVICE_TYPE].v))) {
+	ERR("Error adding A_SERVICE_TYPE\n");
+	goto error;
+    }
+
+
+    if (rc_auth(rh, 0, send, &received, rad_msg) != OK_RC) {
+	DBG("load_user_attrs: Failure\n");
+	goto error;
+    }
+
+    DBG("load_user_attrs: Success\n");
+    rc_avpair_free(send);
+
+    if (generate_avps(flags, received) < 0) {
+	rc_avpair_free(received);
+	goto error;
+    }
+
+    rc_avpair_free(received);
+    return 1;
+
+ error:
+    if (send) rc_avpair_free(send);
+    if (received) rc_avpair_free(send);
+    return -1;
+}
+
+
+static int load_uri_attrs(struct sip_msg* msg, unsigned long flags, fparam_t* fp)
+{
+    static char rad_msg[4096];
+    struct sip_uri puri;
+    str uri, did, scheme;
+    UINT4 service;
+    VALUE_PAIR* send, *received;
+
+    send = NULL;
+    received = NULL;
+
+    if (get_str_fparam(&uri, msg, (fparam_t*)fp) != 0) {
+	ERR("Unable to get URI\n");
+	return -1;
+    }
+
+    if (parse_uri(uri.s, uri.len, &puri) < 0) {
+	ERR("Error while parsing URI '%.*s'\n", uri.len, uri.s);
+	return -1;
+    }
+
+    if (puri.host.len) {
+	/* domain name is present */
+	if (dm_get_did(&did, &puri.host) < 0) {
+		DBG("Cannot lookup DID for domain %.*s, using default value\n", puri.host.len, ZSW(puri.host.s));
+		did.s = DEFAULT_DID;
+		did.len = sizeof(DEFAULT_DID) - 1;
+	}
+    } else {
+	/* domain name is missing -- can be caused by tel: URI */
+	DBG("There is no domain name, using default value\n");
+	did.s = DEFAULT_DID;
+	did.len = sizeof(DEFAULT_DID) - 1;
+    }
+
+    uri_type_to_str(puri.type, &scheme);
+    service = vals[V_GET_URI_ATTRS].v;
+
+    if (scheme.len) {
+	if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_URI_SCHEME].v), 
+			   scheme.s, scheme.len,
+			   VENDOR(attrs[A_SER_URI_SCHEME].v))) {
+	    ERR("Error while adding A_SER_URI_SCHEME\n");
+	    goto error;
+	}
+    }
+
+    if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_USER_NAME].v), 
+		       puri.user.s, puri.user.len,
+		       VENDOR(attrs[A_USER_NAME].v))) {
+	ERR("Error while adding A_USER_NAME\n");
+	goto error;
+    }
+
+    if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_DID].v), 
+		       did.s, did.len,
+		       VENDOR(attrs[A_SER_DID].v))) {
+	ERR("Error while adding A_SER_DID\n");
+	goto error;
+    }
+    
+    if (!rc_avpair_add(rh, &send, ATTRID(attrs[A_SER_SERVICE_TYPE].v), 
+		       &vals[V_GET_URI_ATTRS].v, -1,
+		       VENDOR(attrs[A_SER_SERVICE_TYPE].v))) {
+	ERR("Error adding A_SERVICE_TYPE\n");
+	goto error;
+    }
+
+    if (rc_auth(rh, 0, send, &received, rad_msg) != OK_RC) {
+	DBG("load_uri_attrs: Failure\n");
+	goto error;
+    }
+
+    DBG("load_uri_attrs: Success\n");
+    rc_avpair_free(send);
+
+    if (generate_avps(flags, received) < 0) {
+	rc_avpair_free(received);
+	goto error;
+    }
+
+    rc_avpair_free(received);
+    return 1;
+
+ error:
+    if (send) rc_avpair_free(send);
+    if (received) rc_avpair_free(send);
+    return -1;
+}
+
+
+/*
+ * Load user attributes
+ */
+static int radius_load_attrs(struct sip_msg* msg, char* fl, char* fp)
+{
+    unsigned long flags;
+    
+    flags = (unsigned long)fl;
+
+    if (flags & AVP_CLASS_URI) {
+       	return load_uri_attrs(msg, flags, (fparam_t*)fp);
+    } else {
+	return load_user_attrs(msg, flags, (fparam_t*)fp);
+    }
+}
+
+
+static int attrs_fixup(void** param, int param_no)
+{
+    unsigned long flags;
+    char* s;
+    
+    if (param_no == 1) {
+	     /* Determine the track and class of attributes to be loaded */
+	s = (char*)*param;
+	flags = 0;
+	if (*s != '$' || (strlen(s) != 3)) {
+	    ERR("Invalid parameter value, $xy expected\n");
+	    return -1;
+	}
+	switch((s[1] << 8) + s[2]) {
+	case 0x4655: /* $fu */
+	case 0x6675:
+	case 0x4675:
+	case 0x6655:
+	    flags = AVP_TRACK_FROM | AVP_CLASS_USER;
+	    break;
+	    
+	case 0x4652: /* $fr */
+	case 0x6672:
+	case 0x4672:
+	case 0x6652:
+	    flags = AVP_TRACK_FROM | AVP_CLASS_URI;
+	    break;
+	    
+	case 0x5455: /* $tu */
+	case 0x7475:
+	case 0x5475:
+	case 0x7455:
+	    flags = AVP_TRACK_TO | AVP_CLASS_USER;
+	    break;
+	    
+	case 0x5452: /* $tr */
+	case 0x7472:
+	case 0x5472:
+	case 0x7452:
+	    flags = AVP_TRACK_TO | AVP_CLASS_URI;
+	    break;
+	    
+	default:
+	    ERR("Invalid parameter value: '%s'\n", s);
+	    return -1;
+	}
+
+	if ((flags & AVP_CLASS_URI) && !dm_get_did) {
+	    dm_get_did = (domain_get_did_t)find_export("get_did", 0, 0);
+	    if (!dm_get_did) {
+		ERR("Domain module required but not found\n");
+		return -1;
+	    }
+	}
+	
+	pkg_free(*param);
+	*param = (void*)flags;
+    } else if (param_no == 2) {
+	return fixup_var_str_12(param, 2);
+    }
+    return 0;
+}

+ 4 - 0
avp_radius/doc/Makefile

@@ -0,0 +1,4 @@
+docs = avp_radius.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 47 - 0
avp_radius/doc/avp_radius.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="avp_radius" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Juha</firstname>
+		<surname>Heinanen</surname>
+		<email>[email protected]</email>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2004</year>
+	    <holder>Juha Heinanen</holder>
+	</copyright>
+    </sectioninfo>
+
+    <title>Avp_radius Module</title>
+
+    <section id="avp_radius.overview">
+	<title>Overview</title>
+	<para>
+	    avp_radius module allows loading of user's attributes into AVPs
+	    from Radius. User's name and domain can be based on From URI,
+	    Request URI, or authenticated credentials.
+	</para>
+	<para>
+	    The module assumes that Radius returns the AVPs as values of reply
+	    attribute SIP-AVP.  Its value must be a string of form "name:value"
+	    or of form "name#value".  In the first case, value is interpreted
+	    as a string and in the second case as an int (second case has not
+	    been implemented yet).
+	</para>
+	<para>
+	    The module prefixes each attribute name as returned from Radius by
+	    string "caller_" or "callee_" depending if caller's or callee's
+	    attributes are loaded.
+	</para>
+    </section>
+
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+
+</section>
+

+ 47 - 0
avp_radius/doc/functions.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="avp_radius.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Functions</title>
+
+    <section id="avp_load_radius">
+	<title><function>avp_load_radius(user)</function></title>
+	<para>
+	    The functions loads user's attributes from radius and stores them
+	    into AVPs.  Parameter "user" is used to indicate, whose attributes
+	    are loaded.  Possible values are "caller", "caller_from_ruri", 
+            "callee", and "digest".
+	</para>
+	<para>
+	    In "caller" case, attributes belong to the user of the From URI, in
+	    "callee" case, to the user of the Request URI, and, in "digest"
+	    case, to the authenticated user.
+	</para>
+        <para>
+            The "caller_from_uri" case loads attribute value pairs defined for
+            caller (default SER-Caller-AVPs), but uses the user in the Request
+            URI. This is useful for the case where a call has been forwarded
+            by callee (Request URI) and you need to look up whether callee
+            is allowed to forward the call to ex. PSTN or if the call should be
+            anonymous (i.e. not show info about who diverted the call).
+        </para>
+	<para>
+	    AVP name returned from Radius is prefixed by string "caller_", if
+	    avp_load_radius parameter is "caller" or "digest", and by
+	    "callee_", if parameter is "callee".
+	</para>
+	<example>
+	    <title><function>avp_load_radius</function> usage</title>
+	    <programlisting>
+...
+avp_load_radius("callee")
+...
+	    </programlisting>
+	</example>
+    </section>
+
+</section>

+ 46 - 0
avp_radius/doc/params.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="avp_radius.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="avp_radius.radius_config">
+	<title><varname>radius_config</varname> (string)</title>
+	<para>
+	    This is the location of the configuration file of radius client
+	    libraries.
+	</para>
+	<para>
+	    Default value is "/usr/local/etc/radiusclient/radiusclient.conf".
+	</para>
+	<example>
+	    <title>radius_config parameter usage</title>
+	    <programlisting>
+modparam("avp_radius", "radius_config", "/etc/radiusclient.conf")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="caller_service_type">
+	<title><varname>caller_service_type</varname> (integer)</title>
+	<para>
+	    This is the value of the Service-Type radius attribute to be used,
+	    when caller's attributes are loaded.
+	</para>
+	<para>
+	    Default value is dictionary value of "SIP-Caller-AVPs"
+	    Service-Type.
+	</para>
+	<example>
+	    <title>radius_config parameter usage</title>
+	    <programlisting>
+modparam("avp_radius", "caller_service_type", 18)
+	    </programlisting>
+	</example>
+    </section>
+
+</section>

+ 26 - 0
bdb/Makefile

@@ -0,0 +1,26 @@
+# $Id$
+#
+# example module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+# extra debug messages
+#DEFS+=-DBDB_EXTRA_DEBUG
+ 
+include ../../Makefile.defs
+auto_gen=
+NAME=bdb.so
+
+# db.h locations
+#DEFS += -I$(LOCALBASE)/include/db41
+#LIBS  = -L$(LOCALBASE)/lib -ldb41
+DEFS += -I$(LOCALBASE)/include/db44
+LIBS  = -L$(LOCALBASE)/lib -ldb-4.4
+
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 197 - 0
bdb/README

@@ -0,0 +1,197 @@
+1. BDB Module
+
+   Sippy Software, Inc.
+
+   Copyright © 2006 Sippy Software, Inc.
+     __________________________________________________________________
+
+   1.1. Overview
+
+        1.1.1. Design of DBD Engine
+
+   1.2. Dependencies
+
+        1.2.1. External libraries or applications
+
+   1.3. Exported parameters
+
+        1.3.1. describe_table (string)
+
+   1.4. Constrains and limitations
+   1.5. Installation and running
+
+        1.5.1. Using BDB With Basic SER Configuration
+
+1.1. Overview
+
+   The SER (SIP Express Router) supports several different persistent
+   storage backends (flatfile, dbtext and several SQL servers). However,
+   in certain cases those existing backends don't satisfy conditions.
+   Particularly, SQL server-based backends typically provide good
+   performance and scalability, but at the same time require considerable
+   efforts to configure and manage and also have significant memory and
+   on-disk footprint, while simpler storage backends (flatfile, dbtext)
+   are lighter and simpler to setup and manage, but scale poorly and don't
+   provide sufficient performance. For certain types of applications (i.e.
+   embedded SIP applications, SIP load balancing farms etc), different
+   solution that would combine some of the non-overlapping properties of
+   those two existing classes of backends is necessary.
+
+   Berkeley DB is widely regarded as industry-leading open source,
+   embeddable database engine that provides developers with fast,
+   reliable, local persistence with almost zero administration.
+
+1.1.1. Design of DBD Engine
+
+   The dbtext database system architecture:
+     * A database is represented by a directory in the local file system
+       where BDB environment is located.
+
+Note
+       When using BDB driver in SER, the database URL for modules must be
+       the path to the directory where the BDB environment is located,
+       prefixed by "bdb://", e.g., "bdb:///var/db/ser". If there is no "/"
+       after "bdb://" then "CFG_DIR/" (the OS-specific path defined at
+       SER's compile time) is inserted at the beginning of the database
+       path automatically. So that, either an absolute path to database
+       directory, or one relative to "CFG_DIR" directory should be
+       provided in the URL.
+     * The individual tables internaly are represented by binary files
+       inside environment directory.
+
+Note
+       The BDB driver uses BTree access method.
+       On-disk storage format has been developed to be as simple as
+       possible, while meeting performance requirements set forth above.
+       It is not compatible with on-disk format of any other BDB-based DB
+       engine (e.g. MySQL's BDB table handler).
+
+1.2. Dependencies
+
+1.2.1. External libraries or applications
+
+   The next libraries or applications must be installed before running SER
+   with this module:
+     * Berkeley DB 4.X
+
+1.3. Exported parameters
+
+1.3.1. describe_table (string)
+
+   Define the table and its structure. Each table that will be used by
+   other modules has to be defined. The format of the parameter is:
+   table_name:column_name_1(column_type_1)[column_name_2(column_type_2)[..
+   . column_name_N(column_type_N)]]
+
+   The names of table and columns must not include white spaces.
+
+   The first column in definition is used as index.
+
+   Between name of table and name of first column must be ":".
+
+   Between two columns definitions must be exactly one white space.
+
+   Type of column has to be enclosed into parentheses.
+
+   Supported column types are:
+     * int
+     * float
+     * double
+     * string
+     * str
+     * datetime
+     * blob
+     * bitmap
+
+1.4. Constrains and limitations
+
+   Use of indexes:
+     * Effective SELECT, UPDATE and DELETE operations on a structured
+       storage that contains any more or less decent number of records are
+       impossible without using some kind of indexing scheme. Since
+       Berkley DB is relatively simple storage engine it provides only
+       basic support for indexing, nearly not as rich as usually expected
+       by an average SQL user. Therefore, it has been decided to limit
+       number of indexes supported to one per table. This is sufficient
+       for most of the uses in the SER (for example: usrloc, auth_db etc).
+     * SELECT/UPDATE/DELETE records matching criteria. Due to its
+       simplicity, Berkley DB only supports exact match on indexed field.
+       In order to support <, >, <= and >= operations mandated by the SIP
+       DB API it is necessary to fall back to sequental scan of the index
+       values, which obviously has significant negative impact on
+       performance. Fortunately those advanced records matching criterias
+       are not required neither by the usrloc module nor by auth_db
+       module.
+     * BDB driver uses index only if key column appears in search clause
+       at least once and records matching operator associated with it is
+       '='.
+     * It is not allowed to set index value to NULL or an empty string.
+     * It is not allowed to update index value. The DELETE followed by
+       INSERT should be used instead.
+
+   BDB driver does not support db_raw_query() method.
+
+   BDB driver does not support ORDER BY clause of db_query() method.
+
+1.5. Installation and running
+
+   Compile the module and load it instead of mysql or other DB modules.
+
+   Example 1. Load the bdb module
+...
+loadmodule "/path/to/ser/modules/dbb.so"
+...
+modparam("module_name", "db_url", "bdb:///path/to/bdb/database")
+...
+
+   Example 2. definition of the standard version table
+...
+modparam("bdb", "describe_table", "version:table_name(str) table_version(int)")
+...
+
+1.5.1. Using BDB With Basic SER Configuration
+
+   Here are the definitions for tables used by usrloc module as well as a
+   part of basic configuration file to use BDB driver with SER. The table
+   structures may change in future releases, so that some adjustment to
+   example below may be necessary. That example corresponds to SER v0.9.4
+
+   According to the configuration file below, the table files will be
+   placed in the //var/db/ser/ directory.
+
+   The table version should be populated manually before the SER is
+   started. To do this version.dump file located in the directotry of BDB
+   driver sources and db_load utility from Berkeley BD distribution should
+   be used as follows:
+
+   Example 3. Population of version table
+$ db_load -h /var/db/ser -f version.dump version
+
+   Example 4. Configuration file
+# ---------- global configuration parameters ------------------------
+
+# [skip]
+
+# ---------- module loading -----------------------------------------
+
+loadmodule "/usr/local/lib/ser/modules/usrloc.so"
+loadmodule "/usr/local/lib/ser/modules/bdb.so"
+
+# ---------- module-specific configuration parameteres --------------
+
+modparam("usrloc", "db_mode", 2)
+modparam("usrloc", "timer_interval", 3)
+modparam("usrloc", "db_url", "bdb:///var/db/ser")
+
+modparam("bdb", "describe_table", "version:table_name(str) table_version(int)")
+
+modparam("bdb", "describe_table", "location:username(str) domain(str) contact(st
+r) i_env(int) expires(datetime) q(double) callid(str) cseq(int) method(str) flag
+s(int) user_agent(str) received(str)")
+modparam("bdb", "describe_table", "aliases:username(str) domain(str) contact(str
+) i_env(int) expires(datetime) q(double) callid(str) cseq(int) method(str) flags
+(int) user_agent(str) received(str)")
+
+# ---------- request routing logic ----------------------------------
+
+# [skip]

+ 105 - 0
bdb/bdb.c

@@ -0,0 +1,105 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+MODULE_VERSION
+
+static int mod_init(void);
+static int child_init(int);
+static void mod_destroy(void);
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+	{"db_use_table",   (cmd_function)bdb_use_table,   2, 0, 0},
+	{"db_init",        (cmd_function)bdb_init,        1, 0, 0},
+	{"db_close",       (cmd_function)bdb_close,       2, 0, 0},
+	{"db_query",       (cmd_function)bdb_query,       2, 0, 0},
+	{"db_raw_query",   (cmd_function)bdb_raw_query,   2, 0, 0},
+	{"db_free_result", (cmd_function)bdb_free_result, 2, 0, 0},
+	{"db_insert",      (cmd_function)bdb_insert,      2, 0, 0},
+	{"db_delete",      (cmd_function)bdb_delete,      2, 0, 0},
+	{"db_update",      (cmd_function)bdb_update,      2, 0, 0},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"describe_table", PARAM_STRING|USE_FUNC_PARAM, (void*)bdb_describe_table},
+	{0, 0, 0}
+};
+
+
+struct module_exports exports = {	
+	"bdb",
+	cmds,     /* Exported functions */
+	0,        /* RPC method */
+	params,   /* Exported parameters */
+	mod_init, /* module initialization function */
+	0,        /* response function*/
+	mod_destroy,	  /* destroy function */
+	0,        /* oncancel function */
+	child_init         /* per-child init function */
+};
+
+bdb_table_p	bdb_tables = NULL;
+
+static int mod_init(void)
+{
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:init...\n");
+#endif
+
+	return 0;
+}
+
+static int child_init(int rank)
+{
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:child_init: rank = %d\n", rank);
+#endif
+
+	return 0;
+}
+
+static void mod_destroy(void)
+{
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:destroy...\n");
+#endif
+
+	if (bdb_tables != NULL) {
+		bdb_free_table_list(bdb_tables);
+		bdb_tables = NULL;
+	}
+}

+ 68 - 0
bdb/bdb.h

@@ -0,0 +1,68 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifndef _BDB_H_
+#define _BDB_H_
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/param.h>
+#include <string.h>
+#include <db.h>
+
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../str.h"
+#include "../../timer.h"
+#include "../../lib/srdb2/db_con.h"
+#include "../../lib/srdb2/db_res.h"
+#include "../../db/db_key.h"
+#include "../../db/db_op.h"
+#include "../../db/db_val.h"
+
+#include "bdb_res.h"
+#include "bdb_api.h"
+#include "bdb_vals.h"
+
+extern bdb_table_p	bdb_tables;
+
+db_con_t* bdb_init(const char* _sqlurl);
+void bdb_close(db_con_t* _h);
+int bdb_free_result(db_con_t* _h, db_res_t* _r);
+int bdb_query(db_con_t* _h, db_key_t* _k, db_op_t* _op, db_val_t* _v, 
+			db_key_t* _c, int _n, int _nc, db_key_t _o, db_res_t** _r);
+int bdb_raw_query(db_con_t* _h, char* _s, db_res_t** _r);
+int bdb_insert(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n);
+int bdb_delete(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v, int _n);
+int bdb_update(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v,
+	      db_key_t* _uk, db_val_t* _uv, int _n, int _un);
+
+#endif
+

+ 869 - 0
bdb/bdb_api.c

@@ -0,0 +1,869 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+
+int bdb_close_table(db_con_t* _h)
+{
+	/* close DB */
+	if (BDB_CON_DB(_h) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_close_table: no need to close\n");
+#endif
+		return 0;
+#ifdef BDB_EXTRA_DEBUG
+	} else {
+		LOG(L_NOTICE, "BDB:bdb_close_table: '%s'\n", CON_TABLE(_h));
+#endif
+	};
+	BDB_CON_DB(_h)->close(BDB_CON_DB(_h), 0);
+
+	CON_TABLE(_h) = NULL;
+
+	BDB_CON_DB(_h) = NULL;
+
+	return 0;
+};
+
+
+int bdb_open_table(db_con_t* _h, const char* _t)
+{
+	int ret;
+	bdb_table_p t;
+
+	if ((t = bdb_find_table(_t)) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_open_table: table: '%s' has not been described\n", _t);
+#endif
+		return -1;
+	}
+	BDB_CON_COL_NUM(_h) = t->col_num;
+
+	CON_TABLE(_h) = t->name.s;
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_open_table: '%s'\n", CON_TABLE(_h));
+#endif
+
+	ret = db_create(&BDB_CON_DB(_h), BDB_CON_DBENV(_h), 0);
+	if (ret != 0) {
+                LOG(L_ERR, "BDB:bdb_open_table: unable to db_create(): %s\n", db_strerror(ret));
+                return -1;
+	};
+
+	ret = BDB_CON_DB(_h)->set_flags(BDB_CON_DB(_h), DB_DUP);
+	if (ret != 0) {
+                LOG(L_ERR, "BDB:bdb_open_table: unable to set_flags(): %s\n", db_strerror(ret));
+                return -1;
+	}
+
+
+	ret = BDB_CON_DB(_h)->open(BDB_CON_DB(_h), NULL, CON_TABLE(_h), NULL, DB_BTREE, DB_CREATE, 0);
+	if (ret != 0) {
+                LOG(L_ERR, "BDB:bdb_open_table: unable to open database '%s': %s\n", CON_TABLE(_h), db_strerror(ret));
+                return -1;
+	};
+
+	return 0;
+};
+
+
+int bdb_use_table(db_con_t* _h, const char* _t)
+{
+	int ret;
+
+	if ((!_h) || (!_t)) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_use_table: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+
+	if (CON_TABLE(_h) != NULL && !strcmp(CON_TABLE(_h), _t)) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_use_table: table '%s' has been already opened\n", _t);
+#endif
+		return 0;
+	}
+
+	/* close table if one was already opened */
+	if (CON_TABLE(_h) != NULL) {
+		bdb_close_table(_h);
+	}
+
+	ret = bdb_open_table(_h, _t);
+
+	return ret;
+}
+
+int bdb_describe_table(modparam_t type, void* val)
+{
+	char *s, *p;
+	bdb_table_p t;
+	bdb_column_p c;
+
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_describe_table: input string: '%s'\n", (char*)val);
+#endif
+	s = (char*) val;
+
+	p = strchr(s, ':');
+	*p = 0;
+	if (bdb_find_table(s) != NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_describe_table: table: '%s' already has been described\n", s);
+#endif
+		return -1;
+	}
+
+	t = pkg_malloc(sizeof(*t));
+	memset(t, 0, sizeof(*t));
+
+	t->name.s = pkg_malloc(strlen(s) + 1);
+	memcpy(t->name.s, s, strlen(s) + 1);
+	t->name.len = strlen(s) + 1;
+
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_describe_table: table: '%.*s'\n", t->name.len, t->name.s);
+#endif
+	bdb_push_table(t);
+
+	s = p + 1;
+	while ((p = strchr(s, '(')) != NULL) {
+		*p = 0;
+		if (bdb_find_column(t, s) != NULL) {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_ERR, "BDB:bdb_describe_table: table: '%.*s': dublicated column: '%s', \n",
+			    t->name.len, t->name.s, s);
+#endif
+			return -1;
+		}
+
+		c = pkg_malloc(sizeof(*c));
+		memset(c, 0, sizeof(*c));
+
+		c->name.s = pkg_malloc(strlen(s) + 1);
+		memcpy(c->name.s, s, strlen(s) + 1);
+		c->name.len = strlen(s) + 1;
+
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_describe_table: column '%.*s'", c->name.len, c->name.s);
+#endif
+		bdb_push_column(t, c);
+		t->col_num++;
+
+		s = ++p;
+		p = strchr(s, ')');
+		*p = 0;
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, ", type: '%s'\n", s);
+#endif
+		if (!strncmp("int", s, strlen(s))) {
+			c->type = DB_INT;
+		} else if (!strncmp("float", s, strlen(s))) {
+			c->type = DB_FLOAT;
+		} else if (!strncmp("double", s, strlen(s))) {
+			c->type = DB_DOUBLE;
+		} else if (!strncmp("string", s, strlen(s))) {
+			c->type = DB_STRING;
+		} else if (!strncmp("str", s, strlen(s))) {
+			c->type = DB_STR;
+		} else if (!strncmp("datetime", s, strlen(s))) {
+			c->type = DB_DATETIME;
+		} else if (!strncmp("blob", s, strlen(s))) {
+			c->type = DB_BLOB;
+		} else if (!strncmp("bitmap", s, strlen(s))) {
+			c->type = DB_BITMAP;
+		} else {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_ERR, "BDB:bdb_describe_table: bad column type: '%s'\n", s);
+#endif
+			return -1;
+		}
+
+		s = ++p;
+		if ((p = strchr(s, ' ')) != NULL)
+			s++;
+	}
+
+	return 0;
+};
+
+void bdb_push_table(bdb_table_p _t)
+{
+	bdb_table_p     t;
+
+	if (bdb_tables == NULL) {
+		bdb_tables = _t;
+		return;
+	}
+	t = bdb_tables;
+	while (t->next != NULL) {
+		t = t->next;
+	}
+	t->next = _t;
+};
+
+void bdb_free_table(bdb_table_p _t)
+{
+	if (_t->name.s) {
+		pkg_free(_t->name.s);
+	}
+	if (_t->cols != NULL) {
+		bdb_free_column_list(_t->cols);
+	}
+	pkg_free(_t);
+};
+
+void bdb_free_table_list(bdb_table_p _t)
+{
+	bdb_table_p     curr, next;
+
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:bdb_free_table_list\n");
+#endif
+	for (curr = _t; curr != NULL;) {
+		next = curr->next;
+		bdb_free_table(curr);
+		curr = next;
+	}
+};
+
+bdb_table_p bdb_find_table(const char* _t)
+{
+	bdb_table_p	t;
+
+	for (t = bdb_tables; t != NULL; t = t->next) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_NOTICE, "BDB:bdb_find_table: search for '%s', found '%s'\n", _t, t->name.s);
+#endif
+		if (!strcmp(_t, t->name.s))
+			return t;
+	}
+
+	return NULL;
+};
+
+void bdb_free_column(bdb_column_p _c)
+{
+	if (_c->name.s) {
+		pkg_free(_c->name.s);
+	}
+	pkg_free(_c);
+};
+
+void bdb_free_column_list(bdb_column_p _c)
+{
+	bdb_column_p     curr, next;
+
+	for (curr = _c; curr != NULL;) {
+		next = curr->next;
+		bdb_free_column(curr);
+		curr = next;
+	}
+};
+
+bdb_column_p bdb_find_column(bdb_table_p _t, const char* _c)
+{
+	bdb_column_p	c;
+
+	for (c = _t->cols; c != NULL; c = c->next) {
+		if (!strcmp(_c, c->name.s))
+			return c;
+	}
+
+	return NULL;
+};
+
+void bdb_push_column(bdb_table_p _t, bdb_column_p _c)
+{
+	bdb_column_p	c;
+
+	if (_t->cols == NULL) {
+		_t->cols = _c;
+		return;
+	}
+	c = _t->cols;
+	while (c->next != NULL) {
+		c = c->next;
+	}
+	c->next = _c;
+};
+
+
+int bdb_update_table(db_con_t* _h, bdb_srow_p s_r, bdb_urow_p u_r)
+{
+	DBC	*cursorp;
+	DBT	key, *keyp, data;
+	u_int32_t flags, nflags;
+	bdb_val_p v;
+	bdb_row_p r;
+	int	ret;
+
+	if (s_r->key.size > 0) {
+		keyp = &(s_r->key);
+		flags = DB_SET;
+		nflags = DB_NEXT_DUP;
+	} else {
+		memset(&key, 0, sizeof(DBT));
+		keyp = &key;
+		flags = DB_NEXT;
+		nflags = DB_NEXT;
+	}
+	memset(&data, 0, sizeof(DBT));
+	r = pkg_malloc(sizeof(*r));
+	memset(r, 0, sizeof(*r));
+
+	BDB_CON_DB(_h)->cursor(BDB_CON_DB(_h), NULL, &cursorp, DB_WRITECURSOR);
+
+	ret = cursorp->c_get(cursorp, keyp, &data, flags);
+	while (ret == 0) {
+		if ((ret = bdb_get_db_row(_h, &data, &v)) < 0) {
+			if (cursorp != NULL)
+				cursorp->c_close(cursorp);
+			bdb_free_row(r);
+			return -1;
+		};
+
+		/* row content now in v */
+
+		ret = bdb_row_match(_h, v, s_r);
+		if (ret < 0) {
+			if (cursorp != NULL)
+				cursorp->c_close(cursorp);
+			bdb_free_row(r);
+			return -1;
+		} else if (ret) {		/* match */
+			if (bdb_set_row(_h, u_r, v, r) < 0) {
+				if (cursorp != NULL)
+					cursorp->c_close(cursorp);
+				bdb_free_row(r);
+				return -1;
+			};
+
+			ret = cursorp->c_put(cursorp, keyp, &(r->data), DB_CURRENT);
+			if (ret != 0) {
+                		LOG(L_ERR, "BDB:bdb_update_table: c_put(): %s\n", db_strerror(ret));
+				if (cursorp != NULL)
+					cursorp->c_close(cursorp);
+				bdb_free_row(r);
+				return -1;
+			}
+
+			if (r->data.data != NULL) {
+				pkg_free(r->data.data);
+			}
+			if (r->tail.s != NULL) {
+				pkg_free(r->tail.s);
+			}
+			if (r->fields != NULL) {
+				bdb_free_field_list(r->fields);
+			}
+			memset(r, 0, sizeof(*r));
+#ifdef BDB_EXTRA_DEBUG
+		} else {
+			LOG(L_NOTICE, "BDB:bdb_update_table: does not match\n");
+#endif
+		};
+
+		ret = cursorp->c_get(cursorp, keyp, &data, nflags);
+	}
+
+	if (ret != DB_NOTFOUND) {
+                LOG(L_ERR, "BDB:bdb_update_table: %s\n", db_strerror(ret));
+		if (cursorp != NULL)
+			cursorp->c_close(cursorp);
+		bdb_free_row(r);
+		return -1;
+	}
+
+	if (cursorp != NULL)
+		cursorp->c_close(cursorp);
+	bdb_free_row(r);
+
+	return 0;
+};
+
+
+int bdb_query_table(db_con_t* _h, bdb_srow_p s_r, bdb_rrow_p r_r, int _n, db_res_t** _r)
+{
+	bdb_table_p t;
+	bdb_column_p c;
+	int i, j;
+
+	DBC	*cursorp;
+	DBT	key, *keyp, data;
+	u_int32_t flags, nflags;
+	bdb_val_p v;
+	db_res_t *res;
+	int	ret;
+
+	if (s_r->key.size > 0) {
+		keyp = &(s_r->key);
+		flags = DB_SET;
+		nflags = DB_NEXT_DUP;
+	} else {
+		memset(&key, 0, sizeof(DBT));
+		keyp = &key;
+		flags = DB_NEXT;
+		nflags = DB_NEXT;
+	}
+	memset(&data, 0, sizeof(DBT));
+
+	/* prepare result */
+	res = pkg_malloc(sizeof(*res));
+	memset(res, 0, sizeof(*res));
+	*_r = res;
+
+	res->col.n = (_n == 0) ? BDB_CON_COL_NUM(_h) : _n;
+
+	t = bdb_find_table(CON_TABLE(_h));
+	if (_n == 0) {			/* return all columns */
+		res->col.names = pkg_malloc(sizeof(db_key_t) * t->col_num);
+		res->col.types = pkg_malloc(sizeof(db_type_t) * t->col_num);
+		for (c = t->cols, i = 0; c != NULL; c = c->next, i++) {
+			res->col.names[i] = pkg_malloc(c->name.len);
+			memcpy((void *)res->col.names[i], (void *)c->name.s, c->name.len);
+			res->col.types[i] = c->type;
+		}
+	} else {
+		res->col.names = pkg_malloc(sizeof(db_key_t) * _n);
+		res->col.types = pkg_malloc(sizeof(db_type_t) * _n);
+		for (i = 0; i < _n; i++) {
+			for (c = t->cols, j = 0; j < r_r[i]; c = c->next, j++);
+			res->col.names[i] = pkg_malloc(c->name.len);
+			memcpy((void *)res->col.names[i], (void *)c->name.s, c->name.len);
+			res->col.types[i] = c->type;
+		}
+	}
+
+	BDB_CON_DB(_h)->cursor(BDB_CON_DB(_h), NULL, &cursorp, 0);
+
+	ret = cursorp->c_get(cursorp, keyp, &data, flags);
+	while (ret == 0) {
+		if ((ret = bdb_get_db_row(_h, &data, &v)) < 0) {
+			if (cursorp != NULL)
+				cursorp->c_close(cursorp);
+			bdb_free_result(_h, *_r);
+			return -1;
+		};
+
+		/* row content now in v */
+
+		ret = bdb_row_match(_h, v, s_r);
+		if (ret < 0) {
+			if (cursorp != NULL)
+				cursorp->c_close(cursorp);
+			bdb_free_result(_h, *_r);
+			return -1;
+		} else if (ret) {		/* match */
+			if (bdb_push_res_row(_h, _r, r_r, _n, v) < 0) {
+				if (cursorp != NULL)
+					cursorp->c_close(cursorp);
+				bdb_free_result(_h, *_r);
+				return -1;
+			};
+#ifdef BDB_EXTRA_DEBUG
+		} else {
+			LOG(L_NOTICE, "BDB:bdb_query_table: does not match\n");
+#endif
+		};
+
+		ret = cursorp->c_get(cursorp, keyp, &data, nflags);
+	}
+
+	if (ret != DB_NOTFOUND) {
+                LOG(L_ERR, "BDB:bdb_query_table: %s\n", db_strerror(ret));
+		if (cursorp != NULL)
+			cursorp->c_close(cursorp);
+		bdb_free_result(_h, *_r);
+		return -1;
+	}
+
+	if (cursorp != NULL)
+		cursorp->c_close(cursorp);
+
+	return 0;
+}
+
+
+int bdb_row_match(db_con_t* _h, bdb_val_p _v, bdb_srow_p s_r)
+{
+	bdb_sval_p s_v;
+	db_val_t *v, *v2;
+	int op, l;
+
+	s_v = s_r->fields;
+	while (s_v != NULL) {
+		v = &(s_v->v);			/* row field value*/
+		v2 = &(_v[s_v->c_idx].v);	/* compared value */
+		op = s_v->op;			/* expression operator */
+
+		if (VAL_TYPE(v) != VAL_TYPE(v2)) {
+                	LOG(L_ERR, "BDB:bdb_row_match: types mismatch: %d vs %d\n", VAL_TYPE(v), VAL_TYPE(v2));
+			return -1;
+		};
+
+		if (VAL_NULL(v) && VAL_NULL(v) == VAL_NULL(v2)) {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: NULL == NULL\n");
+#endif
+			return 1;
+		};
+
+		if (VAL_NULL(v) != VAL_NULL(v2)) {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: NULL != NULL\n");
+#endif
+			return 0;
+		};
+
+		switch (VAL_TYPE(v)) {
+		case DB_INT:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %d vs %d\n", VAL_INT(v), VAL_INT(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (VAL_INT(v) != VAL_INT(v2)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (VAL_INT(v) >= VAL_INT(v2)) return 0;
+				break;
+			case BDB_OP_GT:
+				if (VAL_INT(v) <= VAL_INT(v2)) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (VAL_INT(v) > VAL_INT(v2)) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (VAL_INT(v) < VAL_INT(v2)) return 0;
+				break;
+			}
+			break;
+		case DB_FLOAT:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %f vs %f\n", VAL_FLOAT(v), VAL_FLOAT(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (VAL_FLOAT(v) != VAL_FLOAT(v2)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (VAL_FLOAT(v) >= VAL_FLOAT(v2)) return 0;
+				break;
+			case BDB_OP_GT:
+				if (VAL_FLOAT(v) <= VAL_FLOAT(v2)) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (VAL_FLOAT(v) > VAL_FLOAT(v2)) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (VAL_FLOAT(v) < VAL_FLOAT(v2)) return 0;
+				break;
+			}
+			break;
+		case DB_DATETIME:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %d vs %d\n", VAL_TIME(v), VAL_TIME(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (VAL_TIME(v) != VAL_TIME(v2)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (VAL_TIME(v) >= VAL_TIME(v2)) return 0;
+				break;
+			case BDB_OP_GT:
+				if (VAL_TIME(v) <= VAL_TIME(v2)) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (VAL_TIME(v) > VAL_TIME(v2)) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (VAL_TIME(v) < VAL_TIME(v2)) return 0;
+				break;
+			}
+			break;
+		case DB_DOUBLE:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %f vs %f\n", VAL_DOUBLE(v), VAL_DOUBLE(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (VAL_DOUBLE(v) != VAL_DOUBLE(v2)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (VAL_DOUBLE(v) >= VAL_DOUBLE(v2)) return 0;
+				break;
+			case BDB_OP_GT:
+				if (VAL_DOUBLE(v) <= VAL_DOUBLE(v2)) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (VAL_DOUBLE(v) > VAL_DOUBLE(v2)) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (VAL_DOUBLE(v) < VAL_DOUBLE(v2)) return 0;
+				break;
+			}
+			break;
+		case DB_BITMAP:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %0X vs %0X\n", VAL_BITMAP(v), VAL_BITMAP(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (VAL_BITMAP(v) != VAL_BITMAP(v2)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (VAL_BITMAP(v) >= VAL_BITMAP(v2)) return 0;
+				break;
+			case BDB_OP_GT:
+				if (VAL_BITMAP(v) <= VAL_BITMAP(v2)) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (VAL_BITMAP(v) > VAL_BITMAP(v2)) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (VAL_BITMAP(v) < VAL_BITMAP(v2)) return 0;
+				break;
+			}
+			break;
+		case DB_STRING:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %s vs %s\n", VAL_STRING(v), VAL_STRING(v2));
+#endif
+			switch (op) {
+			case BDB_OP_EQ:
+				if (strcmp(VAL_STRING(v), VAL_STRING(v2))) return 0;
+				break;
+			case BDB_OP_LT:
+				if (strcmp(VAL_STRING(v), VAL_STRING(v2)) >= 0) return 0;
+				break;
+			case BDB_OP_GT:
+				if (strcmp(VAL_STRING(v), VAL_STRING(v2)) <= 0) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (strcmp(VAL_STRING(v), VAL_STRING(v2)) > 0) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (strcmp(VAL_STRING(v), VAL_STRING(v2)) < 0) return 0;
+				break;
+			}
+			break;
+		case DB_STR:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %.*s vs %.*s\n", VAL_STR(v).len, VAL_STR(v).s, VAL_STR(v2).len, VAL_STR(v2).s);
+#endif
+			l = VAL_STR(v).len > VAL_STR(v2).len ? VAL_STR(v).len : VAL_STR(v2).len;
+			switch (op) {
+			case BDB_OP_EQ:
+				if (strncmp(VAL_STR(v).s, VAL_STR(v2).s, l)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (strncmp(VAL_STR(v).s, VAL_STR(v2).s, l) >= 0) return 0;
+				break;
+			case BDB_OP_GT:
+				if (strncmp(VAL_STR(v).s, VAL_STR(v2).s, l) <= 0) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (strncmp(VAL_STR(v).s, VAL_STR(v2).s, l) > 0) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (strncmp(VAL_STR(v).s, VAL_STR(v2).s, l) < 0) return 0;
+				break;
+			}
+			break;
+		case DB_BLOB:
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_row_match: %.*s (len = %d) vs %.*s (len = %d)\n", VAL_BLOB(v).len, VAL_BLOB(v).s, VAL_BLOB(v).len, VAL_BLOB(v2).len, VAL_BLOB(v2).s, VAL_BLOB(v2).len);
+#endif
+			l = VAL_BLOB(v).len > VAL_BLOB(v2).len ? VAL_BLOB(v).len : VAL_BLOB(v2).len;
+			switch (op) {
+			case BDB_OP_EQ:
+				if (memcmp(VAL_BLOB(v).s, VAL_BLOB(v2).s, l)) return 0;
+				break;
+			case BDB_OP_LT:
+				if (memcmp(VAL_BLOB(v).s, VAL_BLOB(v2).s, l) >= 0) return 0;
+				break;
+			case BDB_OP_GT:
+				if (memcmp(VAL_BLOB(v).s, VAL_BLOB(v2).s, l) <= 0) return 0;
+				break;
+			case BDB_OP_LEQ:
+				if (memcmp(VAL_BLOB(v).s, VAL_BLOB(v2).s, l) > 0) return 0;
+				break;
+			case BDB_OP_GEQ:
+				if (memcmp(VAL_BLOB(v).s, VAL_BLOB(v2).s, l) < 0) return 0;
+				break;
+			}
+			break;
+		default:
+			return -1;		/* is it possible here? */
+			break;
+		}
+
+		s_v = s_v->next;
+	}
+
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:bdb_row_match: match\n");
+#endif
+	return 1;	/* match */
+};
+
+
+int bdb_push_res_row(db_con_t* _h, db_res_t** _r, bdb_rrow_p _r_r, int _n, bdb_val_p _v)
+{
+	db_res_t *r;
+	db_row_t *row;
+	db_val_t *v;
+	int	i, n;
+	char	*s;
+	
+	r = *_r;
+
+	/*
+	 * use system malloc() to allocate memory for RES_ROWS array
+	 * due to pkg_malloc() memory pool fragmentation problem
+	 */
+	if (RES_ROW_N(r) == 0) {
+		if ((row = malloc(sizeof(*(RES_ROWS(r))))) == NULL) {
+                	LOG(L_ERR, "BDB:bdb_push_res_row: unable to allocate %d bytes\n",
+			    sizeof(*(RES_ROWS(r))));
+			return -1;
+		};
+	} else {
+		if ((row = realloc(RES_ROWS(r), sizeof(*(RES_ROWS(r))) * (RES_ROW_N(r) + 1))) == NULL) {
+                	LOG(L_ERR, "BDB:bdb_push_res_row: unable to reallocate %d bytes\n",
+			    sizeof(*(RES_ROWS(r))) * (RES_ROW_N(r) + 1));
+			return -1;
+		};
+	}
+	RES_ROWS(r) = row;
+	row = &RES_ROWS(r)[RES_ROW_N(r)];
+	RES_ROW_N(r)++;
+
+	n = (_n == 0) ? BDB_CON_COL_NUM(_h) : _n;
+	ROW_VALUES(row) = malloc(sizeof(*(ROW_VALUES(row))) * n);
+	if (ROW_VALUES(row) == NULL) {
+		LOG(L_ERR, "BDB:bdb_push_res_row: unable to allocate %d bytes for ROW_VALUES\n",
+		    sizeof(*(ROW_VALUES(row))) * n);
+		return -1;
+	}
+
+	for (i = 0; i < n; i++) {
+		v = &ROW_VALUES(row)[i];
+
+		memcpy(v, &_v[_r_r[i]].v, sizeof(*v));
+		if (VAL_TYPE(v) == DB_STRING || VAL_TYPE(v) == DB_STR || VAL_TYPE(v) == DB_BLOB) {
+			s = malloc(VAL_STR(v).len + 1);
+			if (s == NULL) {
+				LOG(L_ERR, "BDB:bdb_push_res_row: unable to allocate %d bytes for VAL_STR\n",
+				    VAL_STR(v).len);
+				free(ROW_VALUES(row));
+				return -1;
+			}
+			memcpy(s, VAL_STR(v).s, VAL_STR(v).len);
+			/* some code expect STR value to be NULL terminated */
+			s[VAL_STR(v).len] = 0; 
+			VAL_STR(v).s = s;
+		}
+	}
+
+	ROW_N(row) = n;
+
+	return 0;
+};
+
+
+int bdb_delete_table(db_con_t* _h, bdb_srow_p s_r)
+{
+	DBC	*cursorp;
+	DBT	key, *keyp, data;
+	u_int32_t flags, nflags;
+	bdb_val_p v;
+	int	ret;
+
+	if (s_r->key.size > 0) {
+		keyp = &(s_r->key);
+		flags = DB_SET;
+		nflags = DB_NEXT_DUP;
+	} else {
+		memset(&key, 0, sizeof(DBT));
+		keyp = &key;
+		flags = DB_NEXT;
+		nflags = DB_NEXT;
+	}
+	memset(&data, 0, sizeof(DBT));
+
+	BDB_CON_DB(_h)->cursor(BDB_CON_DB(_h), NULL, &cursorp, DB_WRITECURSOR);
+
+	ret = cursorp->c_get(cursorp, keyp, &data, flags);
+	while (ret == 0) {
+		if ((ret = bdb_get_db_row(_h, &data, &v)) < 0) {
+			return -1;
+		};
+
+		/* row content now in v */
+
+		ret = bdb_row_match(_h, v, s_r);
+		if (ret < 0) {
+			if (cursorp != NULL)
+				cursorp->c_close(cursorp);
+			return -1;
+		} else if (ret) {		/* match */
+			ret = cursorp->c_del(cursorp, 0);
+			if (ret != 0) {
+                		LOG(L_ERR, "BDB:bdb_delete_table: c_del(): %s\n", db_strerror(ret));
+				if (cursorp != NULL)
+					cursorp->c_close(cursorp);
+				return -1;
+			}
+#ifdef BDB_EXTRA_DEBUG
+		} else {
+			LOG(L_NOTICE, "BDB:bdb_delete_table: does not match\n");
+#endif
+		};
+
+		ret = cursorp->c_get(cursorp, keyp, &data, nflags);
+	}
+
+	if (ret != DB_NOTFOUND) {
+		if (cursorp != NULL)
+			cursorp->c_close(cursorp);
+                LOG(L_ERR, "BDB:bdb_delete_table: %s\n", db_strerror(ret));
+		return -1;
+	}
+
+	if (cursorp != NULL)
+		cursorp->c_close(cursorp);
+
+	return 0;
+}
+
+

+ 56 - 0
bdb/bdb_api.h

@@ -0,0 +1,56 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifndef _BDB_API_H_
+#define _BDB_API_H_
+
+int bdb_open_table(db_con_t* _h, const char* _t);
+int bdb_use_table(db_con_t* _h, const char* _t);
+int bdb_close_table(db_con_t* _h);
+
+int bdb_describe_table(modparam_t type, void* val);
+
+bdb_table_p bdb_find_table(const char* _t);
+void bdb_push_table(bdb_table_p _t);
+void bdb_free_table(bdb_table_p _t);
+void bdb_free_table_list(bdb_table_p _t);
+
+bdb_column_p bdb_find_column(bdb_table_p _t, const char* _c);
+void bdb_push_column(bdb_table_p _t, bdb_column_p _c);
+void bdb_free_column(bdb_column_p _c);
+void bdb_free_column_list(bdb_column_p _c);
+
+int bdb_query_table(db_con_t* _h, bdb_srow_p s_r, bdb_rrow_p r_r, int _n, db_res_t** _r);
+int bdb_row_match(db_con_t* _h, bdb_val_p _v, bdb_srow_p s_r);
+int bdb_push_res_row(db_con_t* _h, db_res_t** _r, bdb_rrow_p _r_r, int _n, bdb_val_p _v);
+
+int bdb_update_table(db_con_t* _h, bdb_srow_p s_r, bdb_urow_p u_r);
+
+int bdb_delete_table(db_con_t* _h, bdb_srow_p s_r);
+
+#endif

+ 323 - 0
bdb/bdb_base.c

@@ -0,0 +1,323 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+#define BDB_ID          "bdb://"
+#define BDB_ID_LEN      (sizeof(BDB_ID)-1)
+#define BDB_PATH_LEN    MAXPATHLEN
+
+/*
+ * Initialize database connection
+ */
+db_con_t* bdb_init(const char* _sqlurl)
+{
+        db_con_t* _res;
+        char *p;
+        char bdb_path[BDB_PATH_LEN];
+	int ret;
+
+        if (!_sqlurl)
+        {
+#ifdef BDB_EXTRA_DEBUG
+                LOG(L_ERR, "BDB:bdb_init: Invalid parameter value\n");
+#endif
+                return NULL;
+        }
+        p = (char *)_sqlurl;
+        if(strncmp(p, BDB_ID, sizeof(BDB_ID) - 1))
+        {
+                LOG(L_ERR, "BDB:bdb_init: invalid database URL - should be:"
+                        " <%s[/]path/to/directory>\n", BDB_ID);
+                return NULL;
+        }
+        p += BDB_ID_LEN;
+        if(p[0] != '/')
+        {
+                if(sizeof(CFG_DIR) + strlen(p) + 2 > BDB_PATH_LEN)
+                {
+                        LOG(L_ERR, "BDB:bdb_init: path to database is too long\n");
+                        return NULL;
+                }
+                strcpy(bdb_path, CFG_DIR);
+		bdb_path[sizeof(CFG_DIR) - 1] = '/';
+                strcpy(&bdb_path[sizeof(CFG_DIR)], p);
+                p = bdb_path;
+        }
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:bdb_init: bdb_path = %s\n", p);
+#endif
+
+        _res = pkg_malloc(sizeof(db_con_t) + sizeof(bdb_con_t));
+        if (!_res)
+        {
+                LOG(L_ERR, "BDB:bdb_init: No memory left\n");
+                return NULL;
+        }
+        memset(_res, 0, sizeof(db_con_t) + sizeof(bdb_con_t));
+	_res->tail = (unsigned long)((char*)_res + sizeof(bdb_con_t));
+
+	ret = db_env_create(&BDB_CON_DBENV(_res), 0);
+	if (ret != 0) {
+		LOG(L_ERR, "BDB:bdb_init: unable to db_env_create(): %s\n", db_strerror(ret));
+
+		pkg_free(_res);
+		return NULL;
+	}
+
+	ret = BDB_CON_DBENV(_res)->open(BDB_CON_DBENV(_res), p, DB_CREATE | DB_INIT_MPOOL | DB_INIT_CDB, 0);
+	if (ret != 0) {
+		LOG(L_ERR, "BDB:bdb_init: unable to open environment: %s\n", db_strerror(ret));
+
+		pkg_free(_res);
+		return NULL;
+	}
+
+	return _res;
+}
+
+/*
+ * Close a database connection
+ */
+void bdb_close(db_con_t* _h)
+{
+        if (!_h)
+        {
+#ifdef DBT_EXTRA_DEBUG
+                LOG(L_ERR, "BDB:bdb_close: Invalid parameter value\n");
+#endif
+                return;
+        }
+
+	if (BDB_CON_DB(_h) != NULL) {
+		bdb_close_table(_h);
+	}
+
+	if (BDB_CON_DBENV(_h) != NULL) {
+		BDB_CON_DBENV(_h)->close(BDB_CON_DBENV(_h), 0);
+	}
+
+        pkg_free(_h);
+
+	return;
+}
+
+
+/*
+ * Raw SQL query -- is not the case to have this method
+ */
+int bdb_raw_query(db_con_t* _h, char* _s, db_res_t** _r)
+{
+    *_r = NULL;
+    LOG(L_ERR, "BDB:bdb_raw_query: method is not supported.\n");
+    return -1;
+}
+
+
+/*
+ * Query table for specified rows
+ * _h: structure representing database connection
+ * _k: key names
+ * _op: operators
+ * _v: values of the keys that must match
+ * _c: column names to return
+ * _n: number of key=values pairs to compare
+ * _nc: number of columns to return
+ * _o: order by the specified column
+ */
+
+int bdb_query(db_con_t* _h, db_key_t* _k, db_op_t* _op, db_val_t* _v,
+		db_key_t* _c, int _n, int _nc, db_key_t _o, db_res_t** _r)
+{
+	bdb_srow_p	s_r;		/* search keys row */
+	bdb_rrow_p	r_r;		/* return keys row */
+	int		ret;
+
+	*_r = NULL;
+	s_r = NULL;
+	r_r = NULL;
+
+	if (_o) {
+		LOG(L_ERR, "BDB:bdb_query: ORDER BY is not supported.\n");
+		return -1;
+	}
+
+	if ((ret = bdb_srow_db2bdb(_h, _k, _op, _v, _n, &s_r)) < 0) {
+		return -1;
+	};
+
+	if ((ret = bdb_rrow_db2bdb(_h, _c, _nc, &r_r)) < 0) {
+		bdb_free_srow(s_r);
+		return -1;
+	};
+
+	if ((ret = bdb_query_table(_h, s_r, r_r, _nc, _r)) < 0) {
+		bdb_free_rrow(r_r);
+		bdb_free_srow(s_r);
+		return -1;
+	};
+
+	bdb_free_rrow(r_r);
+	bdb_free_srow(s_r);
+
+	return 0;
+}
+
+
+/*
+ * Insert a row into table
+ */
+int bdb_insert(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n)
+{
+	bdb_row_p	r;
+	DBC		*cursorp;
+	int		ret;
+
+	if ((ret = bdb_row_db2bdb(_h, _k, _v, _n, &r)) < 0) {
+		return -1;
+	};
+
+	if (r->key.size == 0) {
+		LOG(L_ERR, "BDB:bdb_insert: no primary key specified\n");
+		bdb_free_row(r);
+		return -1;
+	}
+
+	BDB_CON_DB(_h)->cursor(BDB_CON_DB(_h), NULL, &cursorp, DB_WRITECURSOR);
+	ret = cursorp->c_put(cursorp, &(r->key), &(r->data), DB_KEYLAST);
+
+	if (ret < 0) {
+		LOG(L_ERR, "BDB:bdb_insert: unable to insert record: %s\n", db_strerror(ret));
+	}
+
+	if (cursorp != NULL) {
+		cursorp->c_close(cursorp);
+	}
+
+	bdb_free_row(r);
+
+	return ret;
+}
+
+
+/*
+ * Delete a row from table
+ */
+int bdb_delete(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v, int _n)
+{
+	bdb_srow_p	s_r;		/* search keys row */
+	int		ret;
+
+	s_r = NULL;
+
+	if ((ret = bdb_srow_db2bdb(_h, _k, _o, _v, _n, &s_r)) < 0) {
+		return -1;
+	};
+
+	if ((ret = bdb_delete_table(_h, s_r)) < 0) {
+		bdb_free_srow(s_r);
+		return -1;
+	};
+
+	bdb_free_srow(s_r);
+
+	return 0;
+}
+
+/*
+ * Update a row in table
+ */
+int bdb_update(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v,
+              db_key_t* _uk, db_val_t* _uv, int _n, int _un)
+{
+	bdb_srow_p	s_r;		/* search keys row */
+	bdb_urow_p	u_r;		/* update row */
+	int		ret;
+
+	s_r = NULL;
+	u_r = NULL;
+
+	if ((ret = bdb_srow_db2bdb(_h, _k, _o, _v, _n, &s_r)) < 0) {
+		return -1;
+	};
+
+	if ((ret = bdb_urow_db2bdb(_h, _uk, _uv, _un, &u_r)) < 0) {
+		bdb_free_srow(s_r);
+		return -1;
+	};
+
+	if ((ret = bdb_update_table(_h, s_r, u_r)) < 0) {
+		bdb_free_urow(u_r);
+		bdb_free_srow(s_r);
+		return -1;
+	};
+
+	bdb_free_urow(u_r);
+	bdb_free_srow(s_r);
+
+	return 0;
+}
+
+/*
+ * Free all memory allocated by get_result
+ */
+int bdb_free_result(db_con_t* _h, db_res_t* _r)
+{
+	db_row_t* r;
+	db_val_t* v;
+	int i, j;
+
+	if (!_r) {
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:bdb_free_result: NULL pointer\n");
+#endif
+		return 0;
+	}
+
+	for (i = 0; i < RES_ROW_N(_r); i++) {
+		r = &(RES_ROWS(_r)[i]);
+		for (j = 0; j < RES_COL_N(_r); j++) {
+			v = &(ROW_VALUES(r)[j]);
+			if (VAL_TYPE(v) == DB_STRING || VAL_TYPE(v) == DB_STR || VAL_TYPE(v) == DB_BLOB) {
+				free(VAL_STR(v).s);
+			}
+		}
+		free(ROW_VALUES(r));
+	}
+	free(RES_ROWS(_r));
+
+	for (i = 0; i < RES_COL_N(_r); i++) {
+		pkg_free((void *)RES_NAMES(_r)[i]);
+	}
+	pkg_free(RES_NAMES(_r));
+	pkg_free(RES_TYPES(_r));
+
+	pkg_free(_r);
+
+        return 0;
+}

+ 115 - 0
bdb/bdb_res.h

@@ -0,0 +1,115 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifndef _BDB_RES_H_
+#define _BDB_RES_H_
+
+typedef struct _bdb_con {
+	DB_ENV	*dbenvp;		/* Env structure handle */
+	DB	*dbp;			/* DB structure handle */
+	int	col_num;		/* number of columns */
+} bdb_con_t, *bdb_con_p;
+
+#define BDB_CON_DBENV(db_con) (((bdb_con_p)((db_con)->tail))->dbenvp)
+#define BDB_CON_DB(db_con) (((bdb_con_p)((db_con)->tail))->dbp)
+#define BDB_CON_COL_NUM(db_con) (((bdb_con_p)((db_con)->tail))->col_num)
+
+/* * */
+
+typedef struct _bdb_val_t
+{
+	db_val_t v;
+	struct _bdb_val_t *next;
+} bdb_val_t, *bdb_val_p;
+
+typedef struct _bdb_row
+{
+	DBT	key;
+	DBT	data;
+	str	tail;
+        bdb_val_p fields;
+        struct _bdb_row *next;
+
+} bdb_row_t, *bdb_row_p;
+
+/* * */
+
+typedef struct _bdb_uval_t
+{
+	int c_idx;			/* column index number */
+	db_val_t v;
+	struct _bdb_uval_t *next;
+} bdb_uval_t, *bdb_uval_p;
+
+typedef struct _bdb_urow
+{
+        bdb_uval_p fields;
+} bdb_urow_t, *bdb_urow_p;
+
+/* * */
+
+typedef struct _bdb_sval_t
+{
+	int c_idx;			/* column index number */
+	db_val_t v;
+	int op;
+#define	BDB_OP_EQ	0
+#define	BDB_OP_LT	1
+#define	BDB_OP_GT	2
+#define	BDB_OP_LEQ	3
+#define	BDB_OP_GEQ	4
+	struct _bdb_sval_t *next;
+} bdb_sval_t, *bdb_sval_p;
+
+typedef struct _bdb_srow
+{
+	DBT	key;
+        bdb_sval_p fields;
+} bdb_srow_t, *bdb_srow_p;
+
+/* * */
+
+typedef int bdb_rrow_t, *bdb_rrow_p;
+
+/* * */
+
+typedef struct _bdb_column {
+        str name;
+        int type;
+        struct _bdb_column *next;
+
+} bdb_column_t, *bdb_column_p;
+
+typedef struct _bdb_table {
+	str	name;
+	int	col_num;		/* number of columns */
+        bdb_column_p cols;
+        struct _bdb_table *next;
+} bdb_table_t, *bdb_table_p;
+
+#endif

+ 91 - 0
bdb/bdb_rval.c

@@ -0,0 +1,91 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+int bdb_rrow_db2bdb(db_con_t* _h, db_key_t* _k, int _n, bdb_rrow_p *_r)
+{
+	bdb_rrow_p	r;
+	bdb_table_p	t;
+	bdb_column_p	c;
+
+	int		found;
+	int		i;
+	int		c_idx;
+
+	*_r = NULL;
+
+	if ((t = bdb_find_table(CON_TABLE(_h))) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_rrow_db2bdb: no table in use\n");
+#endif
+		return -1;
+	};
+
+	i = (_n == 0) ? BDB_CON_COL_NUM(_h) : _n;
+	r = pkg_malloc(sizeof(*r) * i);
+	memset(r, 0, sizeof(*r) * i);
+
+	if (_n > 0) {
+		for (i = 0; i < _n; i++) {
+			found = 0;
+			for (c = t->cols, c_idx = 0; c != NULL; c = c->next, c_idx++) {
+				if (!strcmp(_k[i], c->name.s)) {
+#ifdef BDB_EXTRA_DEBUG
+					LOG(L_NOTICE, "BDB:bdb_rrow_db2bdb: filling column '%.*s', c_idx = %0d\n", c->name.len, c->name.s, c_idx);
+#endif
+					r[i] = c_idx;
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				LOG(L_ERR, "BDB:bdb_rrow_db2bdb: column '%s' does not exist\n", _k[i]);
+				bdb_free_rrow(r);
+				return -1;
+			}
+		}
+	} else {		/* return all columns */
+		for (c = t->cols, c_idx = 0; c != NULL; c = c->next, c_idx++) {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_rrow_db2bdb: filling column '%.*s', c_idx = %0d\n", c->name.len, c->name.s, c_idx);
+#endif
+			r[c_idx] = c_idx;
+		}
+	}
+
+	*_r = r;
+
+	return 0;
+};
+
+
+void bdb_free_rrow(bdb_rrow_p _r)
+{
+	pkg_free(_r);
+};

+ 280 - 0
bdb/bdb_sval.c

@@ -0,0 +1,280 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+int bdb_srow_db2bdb(db_con_t* _h, db_key_t* _k, db_op_t* _op, db_val_t* _v, int _n, bdb_srow_p *_r)
+{
+	bdb_srow_p	r;
+	bdb_table_p	t;
+	bdb_column_p	c;
+
+	int		found;
+	int		use_key, found_key, key_idx;
+	int		i;
+	int		c_idx;
+	bdb_sval_p	v;
+
+	*_r = NULL;
+
+	if ((t = bdb_find_table(CON_TABLE(_h))) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_srow_db2bdb: no table in use\n");
+#endif
+		return -1;
+	};
+
+	key_idx = -1;
+	use_key = 0;
+
+	/* check if all columns exist */
+	for (i = 0; i < _n; i++) {
+		found = 0;
+		/* key column is always first one */
+		for (c = t->cols, found_key = 1; c != NULL; c = c->next, found_key = 0) {
+			if (!strcmp(_k[i], c->name.s)) {
+				found = 1;
+				break;
+			}
+		}
+		if (found_key == 1) {
+			/* use key only if it is used in clause and operator is '=' */
+			if (!use_key && (_op == NULL || (_op != NULL && !strcmp(_op[i], OP_EQ)))) {
+				key_idx = i;
+				use_key = 1;
+			}
+		}
+		if (!found) {
+			LOG(L_ERR, "BDB:bdb_srow_db2bdb: column '%s' does not exist\n", _k[i]);
+			return -1;
+		}
+	}
+
+	r = pkg_malloc(sizeof(*r));
+	memset(r, 0, sizeof(*r));
+
+	/* filling data into row */
+	for (c = t->cols, c_idx = 0; c != NULL; c = c->next, c_idx++) {
+		for (i = 0; i < _n; i++) {
+			if (!strcmp(_k[i], c->name.s)) {
+#ifdef BDB_EXTRA_DEBUG
+				LOG(L_NOTICE, "BDB:bdb_srow_db2bdb: filling column '%.*s', c_idx = %0d\n", c->name.len, c->name.s, c_idx);
+#endif
+				v = pkg_malloc(sizeof(*v));
+				memset(v, 0, sizeof(*v));
+
+				v->c_idx = c_idx;
+
+				bdb_push_sfield(r, v);
+
+				if (bdb_sfield_db2bdb(v, &_v[i], (!_op) ? NULL : _op[i]) < 0) {
+					bdb_free_srow(r);
+					return -1;
+				};
+
+				if (use_key && i == key_idx) {
+					bdb_set_skey(r, v);
+				}
+			}
+		}
+	};
+
+	*_r = r;
+
+	return 0;
+};
+
+
+void bdb_free_srow(bdb_srow_p _r)
+{
+	if (_r->fields != NULL) {
+		bdb_free_sfield_list(_r->fields);
+	}
+
+	pkg_free(_r);
+};
+
+
+void bdb_free_sfield(bdb_sval_p _v)
+{
+	if (!VAL_NULL(&(_v->v))) {
+		if (VAL_TYPE(&(_v->v)) == DB_STR || VAL_TYPE(&(_v->v)) == DB_STRING ||
+		    VAL_TYPE(&(_v->v)) == DB_BLOB) {
+			pkg_free(VAL_STR(&(_v->v)).s);
+                }
+	}
+	pkg_free(_v);
+};
+
+
+void bdb_free_sfield_list(bdb_sval_p _v)
+{
+	bdb_sval_p     curr, next;
+
+	for (curr = _v; curr != NULL;) {
+		next = curr->next;
+		bdb_free_sfield(curr);
+		curr = next;
+	}
+};
+
+
+void bdb_push_sfield(bdb_srow_p _r, bdb_sval_p _v)
+{
+	bdb_sval_p	f;
+
+	if (_r->fields == NULL) {
+		_r->fields = _v;
+		return;
+	}
+	f = _r->fields;
+	while (f->next != NULL) {
+		f = f->next;
+	}
+	f->next = _v;
+};
+
+
+void bdb_set_skey(bdb_srow_p _r, bdb_sval_p _v)
+{
+	/* NULL is not allowed for primary key */
+	if (VAL_NULL(&(_v->v)))
+		return;
+
+	switch (VAL_TYPE(&(_v->v))) {
+	case DB_INT:
+		_r->key.data = &VAL_INT(&(_v->v));
+		_r->key.size = sizeof(VAL_INT(&(_v->v)));
+		break;
+	case DB_FLOAT:
+		_r->key.data = &VAL_FLOAT(&(_v->v));
+		_r->key.size = sizeof(VAL_FLOAT(&(_v->v)));
+		break;
+	case DB_DATETIME:
+		_r->key.data = &VAL_TIME(&(_v->v));
+		_r->key.size = sizeof(VAL_TIME(&(_v->v)));
+		break;
+	case DB_BLOB:
+		_r->key.data = VAL_BLOB(&(_v->v)).s;
+		_r->key.size = VAL_BLOB(&(_v->v)).len;
+		break;
+	case DB_DOUBLE:
+		_r->key.data = &VAL_DOUBLE(&(_v->v));
+		_r->key.size = sizeof(VAL_DOUBLE(&(_v->v)));
+		break;
+	case DB_STRING:
+		_r->key.data = (void *)VAL_STRING(&(_v->v));
+		_r->key.size = strlen(VAL_STRING(&(_v->v))) + 1;
+		break;
+	case DB_STR:
+		_r->key.data = VAL_STR(&(_v->v)).s;
+		_r->key.size = VAL_STR(&(_v->v)).len;
+		break;
+	case DB_BITMAP:
+		_r->key.data = &VAL_BITMAP(&(_v->v));
+		_r->key.size = sizeof(VAL_BITMAP(&(_v->v)));
+		break;
+	default:
+		LOG(L_ERR, "BDB:bdb_set_skey: unknown column type: %0X\n", VAL_TYPE(&(_v->v)));
+		break;
+	}
+
+#ifdef BDB_EXTRA_DEBUG
+	LOG(L_NOTICE, "BDB:bdb_set_skey: use key '%.*s' (%d bytes)\n", _r->key.size, (char *)_r->key.data, _r->key.size);
+#endif
+};
+
+
+int bdb_sfield_db2bdb(bdb_sval_p v, db_val_t* _v, db_op_t _op)
+{
+	char	*s;
+
+	VAL_NULL(&(v->v)) = VAL_NULL(_v);
+	VAL_TYPE(&(v->v)) = VAL_TYPE(_v);
+
+	if (!VAL_NULL(&(v->v))) {
+		switch (VAL_TYPE(_v)) {
+		case DB_INT:
+			VAL_INT(&(v->v)) = VAL_INT(_v);
+			break;
+		case DB_FLOAT:
+			VAL_FLOAT(&(v->v)) = VAL_FLOAT(_v);
+			break;
+		case DB_DATETIME:
+			VAL_TIME(&(v->v)) = VAL_TIME(_v);
+			break;
+		case DB_BLOB:
+			s = pkg_malloc(VAL_BLOB(_v).len);
+			memcpy(s, VAL_BLOB(_v).s, VAL_BLOB(_v).len);
+			VAL_BLOB(&(v->v)).s = s;
+			VAL_BLOB(&(v->v)).len = VAL_BLOB(_v).len;
+			break;
+		case DB_DOUBLE:
+			VAL_DOUBLE(&(v->v)) = VAL_DOUBLE(_v);
+			break;
+		case DB_STRING:
+			VAL_STR(&(v->v)).len = strlen(VAL_STRING(_v)) + 1;
+			s = pkg_malloc(VAL_STR(&(v->v)).len);
+			strcpy(s, VAL_STRING(_v));
+			VAL_STRING(&(v->v)) = s;
+			break;
+		case DB_STR:
+			s = pkg_malloc(VAL_STR(_v).len);
+			memcpy(s, VAL_STR(_v).s, VAL_STR(_v).len);
+			VAL_STR(&(v->v)).s = s;
+			VAL_STR(&(v->v)).len = VAL_STR(_v).len;
+			break;
+		case DB_BITMAP:
+			VAL_BITMAP(&(v->v)) = VAL_BITMAP(_v);
+			break;
+		default:
+			LOG(L_ERR, "BDB:bdb_sfield_db2bdb: unknown column type: %0X\n", VAL_TYPE(_v));
+			return -1;
+			break;
+		}
+
+		if (!_op || !strcmp(_op, OP_EQ)) {
+			v->op = BDB_OP_EQ;
+		} else if (!strcmp(_op, OP_LT)) {
+			v->op = BDB_OP_LT;
+		} else if (!strcmp(_op, OP_GT)) {
+			v->op = BDB_OP_GT;
+		} else if (!strcmp(_op, OP_LEQ)) {
+			v->op = BDB_OP_LEQ;
+		} else if (!strcmp(_op, OP_GEQ)) {
+			v->op = BDB_OP_GEQ;
+		} else {
+			LOG(L_ERR, "BDB:sbdb_field_db2bdb: unknown operator: %s\n", _op);
+			return -1;
+		}
+	}
+
+	return 0;
+};
+
+

+ 223 - 0
bdb/bdb_uval.c

@@ -0,0 +1,223 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+int bdb_urow_db2bdb(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n, bdb_urow_p *_r)
+{
+	bdb_urow_p	r;
+	bdb_table_p	t;
+	bdb_column_p	c;
+
+	int		found, found_key;
+	int		i, j;
+	int		c_idx;
+	bdb_uval_p	v;
+
+	*_r = NULL;
+
+	if (_n <= 0) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_urow_db2bdb: no defined keys to be updated\n");
+#endif
+		return -1;
+	}
+
+	if ((t = bdb_find_table(CON_TABLE(_h))) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_urow_db2bdb: no table in use\n");
+#endif
+		return -1;
+	};
+
+	/* check for dublicates in update set */
+	for (i = 1; i < _n; i++) {
+		for (j = 0; j < i; j++) {
+			if (!strcmp(_k[i], _k[j])) {
+#ifdef BDB_EXTRA_DEBUG
+				LOG(L_ERR, "BDB:bdb_urow_db2bdb: dublicates keys in update set: '%s' and '%s'\n", _k[i], _k[j]);
+#endif
+				return -1;
+			}
+		}
+	}
+
+	/* check if all columns exist */
+	for (i = 0; i < _n; i++) {
+		found = 0;
+		/* key column is always first one */
+		for (c = t->cols, found_key = 1; c != NULL; c = c->next, found_key = 0) {
+			if (!strcmp(_k[i], c->name.s)) {
+				found = 1;
+				break;
+			}
+		}
+		if (found_key == 1) {
+			LOG(L_ERR, "BDB:bdb_urow_db2bdb: unable to update primary key value\n");
+			return -1;
+		}
+		if (!found) {
+			LOG(L_ERR, "BDB:bdb_urow_db2bdb: column '%s' does not exist\n", _k[i]);
+			return -1;
+		}
+	}
+
+	r = pkg_malloc(sizeof(*r));
+	memset(r, 0, sizeof(*r));
+
+	/* filling data into row */
+	for (c = t->cols, c_idx = 0; c != NULL; c = c->next, c_idx++) {
+		for (i = 0; i < _n; i++) {
+			if (!strcmp(_k[i], c->name.s)) {
+#ifdef BDB_EXTRA_DEBUG
+				LOG(L_NOTICE, "BDB:bdb_urow_db2bdb: filling column '%.*s', c_idx = %0d\n", c->name.len, c->name.s, c_idx);
+#endif
+				v = pkg_malloc(sizeof(*v));
+				memset(v, 0, sizeof(*v));
+
+				v->c_idx = c_idx;
+
+				bdb_push_ufield(r, v);
+
+				if (bdb_ufield_db2bdb(v, &_v[i]) < 0) {
+					bdb_free_urow(r);
+					return -1;
+				};
+			}
+		}
+	};
+
+	*_r = r;
+
+	return 0;
+};
+
+
+void bdb_free_urow(bdb_urow_p _r)
+{
+	if (_r->fields != NULL) {
+		bdb_free_ufield_list(_r->fields);
+	}
+
+	pkg_free(_r);
+};
+
+
+void bdb_free_ufield(bdb_uval_p _v)
+{
+	if (!VAL_NULL(&(_v->v))) {
+		if (VAL_TYPE(&(_v->v)) == DB_STR || VAL_TYPE(&(_v->v)) == DB_STRING ||
+		    VAL_TYPE(&(_v->v)) == DB_BLOB) {
+			pkg_free(VAL_STR(&(_v->v)).s);
+                }
+	}
+	pkg_free(_v);
+};
+
+
+void bdb_free_ufield_list(bdb_uval_p _v)
+{
+	bdb_uval_p     curr, next;
+
+	for (curr = _v; curr != NULL;) {
+		next = curr->next;
+		bdb_free_ufield(curr);
+		curr = next;
+	}
+};
+
+
+void bdb_push_ufield(bdb_urow_p _r, bdb_uval_p _v)
+{
+	bdb_uval_p	f;
+
+	if (_r->fields == NULL) {
+		_r->fields = _v;
+		return;
+	}
+	f = _r->fields;
+	while (f->next != NULL) {
+		f = f->next;
+	}
+	f->next = _v;
+};
+
+
+int bdb_ufield_db2bdb(bdb_uval_p v, db_val_t* _v)
+{
+	char	*s;
+
+	VAL_NULL(&(v->v)) = VAL_NULL(_v);
+	VAL_TYPE(&(v->v)) = VAL_TYPE(_v);
+
+	if (!VAL_NULL(&(v->v))) {
+		switch (VAL_TYPE(_v)) {
+		case DB_INT:
+			VAL_INT(&(v->v)) = VAL_INT(_v);
+			break;
+		case DB_FLOAT:
+			VAL_FLOAT(&(v->v)) = VAL_FLOAT(_v);
+			break;
+		case DB_DATETIME:
+			VAL_TIME(&(v->v)) = VAL_TIME(_v);
+			break;
+		case DB_BLOB:
+			s = pkg_malloc(VAL_BLOB(_v).len);
+			memcpy(s, VAL_BLOB(_v).s, VAL_BLOB(_v).len);
+			VAL_BLOB(&(v->v)).s = s;
+			VAL_BLOB(&(v->v)).len = VAL_BLOB(_v).len;
+			break;
+		case DB_DOUBLE:
+			VAL_DOUBLE(&(v->v)) = VAL_DOUBLE(_v);
+			break;
+		case DB_STRING:
+			VAL_STR(&(v->v)).len = strlen(VAL_STRING(_v)) + 1;
+			s = pkg_malloc(VAL_STR(&(v->v)).len);
+			strcpy(s, VAL_STRING(_v));
+			VAL_STRING(&(v->v)) = s;
+			break;
+		case DB_STR:
+			s = pkg_malloc(VAL_STR(_v).len);
+			memcpy(s, VAL_STR(_v).s, VAL_STR(_v).len);
+			VAL_STR(&(v->v)).s = s;
+			VAL_STR(&(v->v)).len = VAL_STR(_v).len;
+			break;
+		case DB_BITMAP:
+			VAL_BITMAP(&(v->v)) = VAL_BITMAP(_v);
+			break;
+		default:
+			LOG(L_ERR, "BDB:bdb_ufield_db2bdb: unknown column type: %0X\n", VAL_TYPE(_v));
+			return -1;
+			break;
+		}
+	}
+
+	return 0;
+};
+
+

+ 436 - 0
bdb/bdb_val.c

@@ -0,0 +1,436 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#include "bdb.h"
+
+
+int bdb_set_row(db_con_t* _h, bdb_urow_p u_r, bdb_val_p _v, bdb_row_p _r)
+{
+	bdb_val_p	v, nv;
+	bdb_uval_p	uv;
+	int		c_idx;
+	int		found;
+
+	/* filling data into row */
+	for (v = _v, c_idx = 0; v != NULL; v = v->next, c_idx++) {
+		nv = pkg_malloc(sizeof(*nv));
+		memset(nv, 0, sizeof(*nv));
+		bdb_push_field(_r, nv);
+
+		found = 0;
+		for (uv = u_r->fields; uv != NULL; uv = uv->next) {
+			if (uv->c_idx == c_idx) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (found) {
+#ifdef BDB_EXTRA_DEBUG
+				LOG(L_NOTICE, "BDB:bdb_set_row: need to update column #%0d\n", c_idx);
+#endif
+			if (bdb_field_db2bdb(nv, &(uv->v)) < 0) {
+				return -1;
+			}
+		} else {
+#ifdef BDB_EXTRA_DEBUG
+			LOG(L_NOTICE, "BDB:bdb_set_row: no need to update column #%0d\n", c_idx);
+#endif
+			if (bdb_field_db2bdb(nv, &(v->v)) < 0) {
+				return -1;
+			}
+		}
+
+		bdb_push_data(_r, nv);
+	};
+
+	bdb_merge_tail(_r);
+
+	return 0;
+};
+
+
+int bdb_row_db2bdb(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n, bdb_row_p *_r)
+{
+	bdb_row_p	r;
+	bdb_table_p	t;
+	bdb_column_p	c;
+
+	int		found;
+	int		use_key, found_key, key_idx;
+	int		i;
+	bdb_val_p	v;
+
+	*_r = NULL;
+
+	if ((t = bdb_find_table(CON_TABLE(_h))) == NULL) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_row_db2bdb: table: no table in use\n");
+#endif
+		return -1;
+	};
+
+	key_idx = -1;
+	use_key = -1;
+
+	/* check if all columns exist */
+	for (i = 0; i < _n; i++) {
+		found = 0;
+		/* key column is always first one */
+		for (c = t->cols, found_key = 1; c != NULL; c = c->next, found_key = 0) {
+			if (!strcmp(_k[i], c->name.s)) {
+				found = 1;
+				break;
+			}
+		}
+		if (found_key == 1) {
+			key_idx = i;
+			use_key++;		/* set to 0 if used in clause only once */
+                }
+		if (!found) {
+			LOG(L_ERR, "BDB:bdb_row_db2bdb: column '%s' does not exist\n", _k[i]);
+			return -1;
+		}
+	}
+
+	if (use_key < 0) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_row_db2bdb: primary key value must be supplied\n");
+#endif
+		return -1;
+	}
+
+	if (use_key > 0) {
+#ifdef BDB_EXTRA_DEBUG
+		LOG(L_ERR, "BDB:bdb_row_db2bdb: primary key value must be supplied only once\n");
+#endif
+		return -1;
+	}
+
+	r = pkg_malloc(sizeof(*r));
+	memset(r, 0, sizeof(*r));
+
+	/* filling data into row */
+	for (c = t->cols; c != NULL; c = c->next) {
+		v = pkg_malloc(sizeof(*v));
+		memset(v, 0, sizeof(*v));
+		VAL_NULL(&(v->v)) = 1;			/* default value is NULL */
+
+		bdb_push_field(r, v);
+
+		for (i = 0; i < _n; i++) {
+			if (!strcmp(_k[i], c->name.s)) {
+#ifdef BDB_EXTRA_DEBUG
+				LOG(L_NOTICE, "BDB:bdb_row_db2bdb: filling column '%.*s'\n", c->name.len, c->name.s);
+#endif
+				if (bdb_field_db2bdb(v, &_v[i]) < 0) {
+					bdb_free_row(r);
+					return -1;
+				};
+
+				if (i == key_idx) {
+					if (bdb_set_key(r, v) < 0) {
+						bdb_free_row(r);
+						return -1;
+					};
+				}
+
+				break;
+			}
+		}
+
+		bdb_push_data(r, v);
+	};
+
+	bdb_merge_tail(r);
+
+	*_r = r;
+
+	return 0;
+};
+
+
+void bdb_merge_tail(bdb_row_p _r)
+{
+	if (_r->tail.len > 0) {
+		_r->data.data = pkg_realloc(_r->data.data, _r->data.size + _r->tail.len);
+		memcpy(_r->data.data + _r->data.size, _r->tail.s, _r->tail.len);
+		_r->data.size += _r->tail.len;
+	}
+};
+
+
+void bdb_push_data(bdb_row_p _r, bdb_val_p _v)
+{
+	if (_r->data.size == 0) {
+		_r->data.data = pkg_malloc(sizeof(*_v));
+	} else {
+		_r->data.data = pkg_realloc(_r->data.data, _r->data.size + sizeof(*_v));
+	}
+
+	memcpy(_r->data.data + _r->data.size, _v, sizeof(*_v));
+
+	_r->data.size += sizeof(*_v);
+
+	if (!VAL_NULL(&(_v->v))) {
+		switch (VAL_TYPE(&(_v->v))) {
+		case DB_STRING:
+		case DB_STR:
+		case DB_BLOB:
+			if (_r->tail.len == 0) {
+				_r->tail.s = pkg_malloc(VAL_STR(&(_v->v)).len);
+			} else {
+				_r->tail.s = pkg_realloc(_r->tail.s, _r->tail.len + VAL_STR(&(_v->v)).len);
+			}
+			memcpy(_r->tail.s + _r->tail.len, VAL_STR(&(_v->v)).s, VAL_STR(&(_v->v)).len);
+			_r->tail.len += VAL_STR(&(_v->v)).len;
+			break;
+		default:
+			break;
+		}
+	}
+};
+
+
+void bdb_push_field(bdb_row_p _r, bdb_val_p _v)
+{
+	bdb_val_p	f;
+
+	if (_r->fields == NULL) {
+		_r->fields = _v;
+		return;
+	}
+	f = _r->fields;
+	while (f->next != NULL) {
+		f = f->next;
+	}
+	f->next = _v;
+};
+
+
+void bdb_free_field(bdb_val_p _v)
+{
+	if (!VAL_NULL(&(_v->v))) {
+		if (VAL_TYPE(&(_v->v)) == DB_STR || VAL_TYPE(&(_v->v)) == DB_STRING ||
+		    VAL_TYPE(&(_v->v)) == DB_BLOB) {
+			pkg_free(VAL_STR(&(_v->v)).s);
+		}
+	}
+	pkg_free(_v);
+};
+
+
+void bdb_free_field_list(bdb_val_p _v)
+{
+	bdb_val_p     curr, next;
+
+	for (curr = _v; curr != NULL;) {
+		next = curr->next;
+		bdb_free_field(curr);
+		curr = next;
+	}
+};
+
+
+void bdb_free_row(bdb_row_p _r)
+{
+	if (_r->fields != NULL) {
+		bdb_free_field_list(_r->fields);
+	}
+
+	if (_r->data.size > 0) {
+		pkg_free(_r->data.data);
+	}
+
+	if (_r->tail.len > 0) {
+		pkg_free(_r->tail.s);
+	}
+
+	pkg_free(_r);
+};
+
+
+void bdb_free_row_list(bdb_row_p _r)
+{
+	bdb_row_p     curr, next;
+
+	for (curr = _r; curr != NULL;) {
+		next = curr->next;
+		bdb_free_row(curr);
+		curr = next;
+	}
+};
+
+
+int bdb_field_db2bdb(bdb_val_p v, db_val_t* _v)
+{
+	char	*s;
+
+	VAL_NULL(&(v->v)) = VAL_NULL(_v);
+	VAL_TYPE(&(v->v)) = VAL_TYPE(_v);
+
+	if (!VAL_NULL(&(v->v))) {
+		switch (VAL_TYPE(_v)) {
+		case DB_INT:
+			VAL_INT(&(v->v)) = VAL_INT(_v);
+			break;
+		case DB_FLOAT:
+			VAL_FLOAT(&(v->v)) = VAL_FLOAT(_v);
+			break;
+		case DB_DATETIME:
+			VAL_TIME(&(v->v)) = VAL_TIME(_v);
+			break;
+		case DB_BLOB:
+			s = pkg_malloc(VAL_BLOB(_v).len);
+			memcpy(s, VAL_BLOB(_v).s, VAL_BLOB(_v).len);
+			VAL_BLOB(&(v->v)).s = s;
+			VAL_BLOB(&(v->v)).len = VAL_BLOB(_v).len;
+			break;
+		case DB_DOUBLE:
+			VAL_DOUBLE(&(v->v)) = VAL_DOUBLE(_v);
+			break;
+		case DB_STRING:
+			VAL_STR(&(v->v)).len = strlen(VAL_STRING(_v)) + 1;
+			s = pkg_malloc(VAL_STR(&(v->v)).len);
+			strcpy(s, VAL_STRING(_v));
+			VAL_STRING(&(v->v)) = s;
+			break;
+		case DB_STR:
+			s = pkg_malloc(VAL_STR(_v).len);
+			memcpy(s, VAL_STR(_v).s, VAL_STR(_v).len);
+			VAL_STR(&(v->v)).s = s;
+			VAL_STR(&(v->v)).len = VAL_STR(_v).len;
+			break;
+		case DB_BITMAP:
+			VAL_BITMAP(&(v->v)) = VAL_BITMAP(_v);
+			break;
+		default:
+			LOG(L_ERR, "BDB:bdb_field_db2bdb: unknown column type: %0X\n", VAL_TYPE(_v));
+			return -1;
+			break;
+		}
+	}
+	return 0;
+};
+
+
+int bdb_get_db_row(db_con_t* _h, DBT* _data, bdb_val_p* _v)
+{
+	bdb_val_p v, prev;
+	void *p, *tail;
+	int l;
+
+	if (!_data || !_data->size) {
+		LOG(L_ERR, "BDB:bdb_get_db_row: invalid data\n");
+		*_v = NULL;
+		return -1;
+	}
+
+	*_v = (bdb_val_p)_data->data;
+	prev = NULL;
+	p = _data->data;
+	l = 0;
+	tail = p + sizeof(*v) * BDB_CON_COL_NUM(_h);
+
+	while (l < sizeof(*v) * BDB_CON_COL_NUM(_h)) {
+		v = (bdb_val_p)p;
+		p += sizeof(*v);
+		l += sizeof(*v);
+		v->next = NULL;
+		if (prev) {
+			prev->next = v;
+			prev = v;
+		} else {
+			prev = v;
+		}
+		if (!VAL_NULL(&(v->v))) {
+			switch (VAL_TYPE(&(v->v))) {
+			case DB_BLOB:
+			case DB_STRING:
+			case DB_STR:
+				VAL_STR(&(v->v)).s = tail;
+				tail += VAL_STR(&(v->v)).len;
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return 0;
+};
+
+int bdb_set_key(bdb_row_p _r, bdb_val_p _v)
+{
+	/* NULL is not allowed for primary key */
+	if (VAL_NULL(&(_v->v))) {
+		LOG(L_ERR, "BDB:bdb_set_key: NULL is not allowed for primary key\n");
+		return -1;
+	}
+
+	switch (VAL_TYPE(&(_v->v))) {
+	case DB_INT:
+		_r->key.data = &VAL_INT(&(_v->v));
+		_r->key.size = sizeof(VAL_INT(&(_v->v)));
+		break;
+	case DB_FLOAT:
+		_r->key.data = &VAL_FLOAT(&(_v->v));
+		_r->key.size = sizeof(VAL_FLOAT(&(_v->v)));
+		break;
+	case DB_DATETIME:
+		_r->key.data = &VAL_TIME(&(_v->v));
+		_r->key.size = sizeof(VAL_TIME(&(_v->v)));
+		break;
+	case DB_BLOB:
+		_r->key.data = VAL_BLOB(&(_v->v)).s;
+		_r->key.size = VAL_BLOB(&(_v->v)).len;
+		break;
+	case DB_DOUBLE:
+		_r->key.data = &VAL_DOUBLE(&(_v->v));
+		_r->key.size = sizeof(VAL_DOUBLE(&(_v->v)));
+		break;
+	case DB_STRING:
+		_r->key.data = (void *)VAL_STRING(&(_v->v));
+		_r->key.size = strlen(VAL_STRING(&(_v->v))) + 1;
+		break;
+	case DB_STR:
+		_r->key.data = VAL_STR(&(_v->v)).s;
+		_r->key.size = VAL_STR(&(_v->v)).len;
+		break;
+	case DB_BITMAP:
+		_r->key.data = &VAL_BITMAP(&(_v->v));
+		_r->key.size = sizeof(VAL_BITMAP(&(_v->v)));
+		break;
+	default:
+		LOG(L_ERR, "BDB:bdb_set_skey: unknown column type: %0X\n", VAL_TYPE(&(_v->v)));
+		return -1;
+		break;
+	}
+
+	return 0;
+};

+ 81 - 0
bdb/bdb_vals.h

@@ -0,0 +1,81 @@
+/* $Id$
+ *
+ * Copyright (C) 2006-2007 Sippy Software, Inc. <[email protected]>
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+
+#ifndef _BDB_VALS_H_
+#define _BDB_VALS_H_
+
+/* table row */
+int bdb_row_db2bdb(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n, bdb_row_p *_r);
+
+void bdb_free_row(bdb_row_p _r);
+void bdb_free_row_list(bdb_row_p _r);
+
+int bdb_set_key(bdb_row_p _r, bdb_val_p _v);
+
+void bdb_push_field(bdb_row_p _r, bdb_val_p _v);
+void bdb_free_field(bdb_val_p _v);
+void bdb_free_field_list(bdb_val_p _v);
+
+void bdb_push_data(bdb_row_p _r, bdb_val_p _v);
+void bdb_merge_tail(bdb_row_p _r);
+
+int bdb_field_db2bdb(bdb_val_p v, db_val_t* _v);
+
+
+/* search row */
+int bdb_srow_db2bdb(db_con_t* _h, db_key_t* _k, db_op_t* _op, db_val_t* _v, int _n, bdb_srow_p *_r);
+void bdb_free_srow(bdb_srow_p _r);
+
+void bdb_set_skey(bdb_srow_p _r, bdb_sval_p _v);
+
+void bdb_push_sfield(bdb_srow_p _r, bdb_sval_p _v);
+void bdb_free_sfield(bdb_sval_p _v);
+void bdb_free_sfield_list(bdb_sval_p _v);
+
+int bdb_sfield_db2bdb(bdb_sval_p v, db_val_t* _v, db_op_t _op);
+
+/* result row */
+int bdb_rrow_db2bdb(db_con_t* _h, db_key_t* _k, int _n, bdb_rrow_p *_r);
+void bdb_free_rrow(bdb_rrow_p _r);
+
+/* update row */
+int bdb_urow_db2bdb(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n, bdb_urow_p *_r);
+void bdb_free_urow(bdb_urow_p _r);
+
+void bdb_push_ufield(bdb_urow_p _r, bdb_uval_p _v);
+void bdb_free_ufield(bdb_uval_p _v);
+void bdb_free_ufield_list(bdb_uval_p _v);
+
+int bdb_ufield_db2bdb(bdb_uval_p v, db_val_t* _v);
+
+int bdb_set_row(db_con_t* _h, bdb_urow_p u_r, bdb_val_p _v, bdb_row_p _r);
+
+/* query */
+int bdb_get_db_row(db_con_t* _h, DBT* _data, bdb_val_p *_v);
+
+#endif

+ 4 - 0
bdb/doc/Makefile

@@ -0,0 +1,4 @@
+docs = bdb.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 330 - 0
bdb/doc/bdb.xml

@@ -0,0 +1,330 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="bdb" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<!--
+		<firstname></firstname>
+		<surname></surname>
+		-->
+		<affiliation><orgname>Sippy Software, Inc.</orgname></affiliation>
+		<address>
+		<email>[email protected]</email>
+                <otheraddr>
+                <ulink url="http://www.sippysoft.com">http://www.sippysoft.com</ulink>
+                </otheraddr>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2006</year>
+	    <holder>Sippy Software, Inc.</holder>
+	</copyright>
+    </sectioninfo>
+
+    <title>BDB Module</title>
+
+    <section id="bdb.overview">
+	<title>Overview</title>
+	<para>
+		The SER (SIP Express Router) supports several different persistent
+		storage backends (flatfile, dbtext and several SQL servers). However, in
+		certain cases those existing backends don't satisfy conditions.
+		Particularly, SQL server-based backends typically provide good
+		performance and scalability, but at the same time require considerable
+		efforts to configure and manage and also have significant memory and
+		on-disk footprint, while simpler storage backends (flatfile, dbtext) are
+		lighter and simpler to setup and manage, but scale poorly and don't
+		provide sufficient performance. For certain types of applications (i.e.
+		embedded SIP applications, SIP load balancing farms etc), different
+		solution that would combine some of the non-overlapping properties of
+		those two existing classes of backends is necessary.
+	</para>
+	<para>
+		Berkeley DB is widely regarded as industry-leading open source, embeddable
+		database engine that provides developers with fast, reliable, local
+		persistence with almost zero administration.
+	</para>
+
+	<section id="design">
+	    <title>Design of DBD Engine</title>
+	    <para>
+		The dbtext database system architecture: 
+		<itemizedlist>
+		    <listitem>
+			<para>
+				A database is represented by a directory in the local file system
+				where BDB environment is located.
+			</para>
+			<note>
+			    <para>
+				When using BDB driver in SER, the database URL for modules must be
+				the path to the directory where the BDB environment is located,
+				prefixed by "bdb://", e.g., "bdb:///var/db/ser". If there is no
+				"/" after "bdb://" then "CFG_DIR/" (the OS-specific path defined
+				at SER's compile time) is inserted at the beginning of the database
+				path automatically. So that, either an absolute path to database
+				directory, or one relative to "CFG_DIR" directory should be provided
+				in the URL.
+			    </para>
+			</note>
+		    </listitem>
+		    <listitem>
+			<para>
+				The individual tables internaly are represented by binary files
+				inside environment directory.
+			</para>
+			<note>
+			    <para>
+				The BDB driver uses BTree access method.
+			    </para>
+			    <para>
+				On-disk storage format has been developed to be as simple as
+				possible, while meeting performance requirements set forth above.
+				It is not compatible with on-disk format of any other BDB-based DB
+				engine (e.g. MySQL's BDB table handler). 
+			    </para>
+			</note>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+    </section>
+
+    <section id="bdb.dep">
+	<title>Dependencies</title>
+	<section id="external_libs">
+	    <title>External libraries or applications</title>
+	    <para>
+		The next libraries or applications must be installed before running
+		SER with this module:
+		<itemizedlist>
+		    <listitem>
+			<para>
+				<ulink url="http://www.oracle.com/technology/software/products/berkeley-db/index.html">Berkeley DB 4.X</ulink>
+			</para>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+    </section>
+
+    <section id="bdb.parameters">
+	<title>Exported parameters</title>
+	<section id="bdb.describe_table">
+	    <title><varname>describe_table</varname> (string)</title>
+	    <para>
+		Define the table and its structure. Each table that will be used
+		by other modules has to be defined. The format of the parameter is:
+		table_name:column_name_1(column_type_1)[column_name_2(column_type_2)[... column_name_N(column_type_N)]] 
+	    </para>
+	    <para>
+		The names of table and columns must not include white spaces.
+	    </para>
+	    <para>
+		The first column in definition is used as index.
+	    </para>
+	    <para>
+		Between name of table and name of first column must be ":".
+	    </para>
+	    <para>
+		Between two columns definitions must be exactly one white space.
+	    </para>
+	    <para>
+		Type of column has to be enclosed into parentheses.
+	    </para>
+	    <para>
+		Supported column types are:
+		<itemizedlist>
+		    <listitem>
+			<para>
+				int
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				float
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				double
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				string
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				str
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				datetime
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				blob
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				bitmap
+			</para>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+    </section>
+
+    <section id="bdb.constrains">
+	<title>Constrains and limitations</title>
+	<para>
+		Use of indexes:
+		<itemizedlist>
+		    <listitem>
+			<para>
+				Effective SELECT, UPDATE and DELETE operations on a
+				structured storage that contains any more or less decent
+				number of records are impossible without using some kind of
+				indexing scheme. Since Berkley DB is relatively simple
+				storage engine it provides only basic support for indexing,
+				nearly not as rich as usually expected by an average SQL
+				user. Therefore, it has been decided to limit number of
+				indexes supported to one per table. This is sufficient for
+				most of the uses in the SER (for example: usrloc, auth_db etc).
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				SELECT/UPDATE/DELETE records matching criteria. Due to its
+				simplicity, Berkley DB only supports exact match on indexed
+				field. In order to support &lt;, &gt;, &lt;= and &gt;= operations
+				mandated by the SIP DB API it is necessary to fall back to
+				sequental scan of the index values, which obviously has
+				significant negative impact on performance. Fortunately
+				those advanced records matching criterias are not required
+				neither by the usrloc module nor by auth_db module.
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				BDB driver uses index only if key column appears in search
+				clause at least once and records matching operator
+				associated with it is '='.
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				It is not allowed to set index value to NULL or an empty string.
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+				It is not allowed to update index value. The DELETE followed
+				by INSERT should be used instead. 
+			</para>
+		    </listitem>
+		</itemizedlist>
+	</para>
+	<para>
+		BDB driver does not support db_raw_query() method.
+	</para>
+	<para>
+		BDB driver does not support ORDER BY clause of db_query() method. 
+	</para>
+    </section>
+
+    <section id="install_run">
+	<title>Installation and running</title>
+	<para>
+	    Compile the module and load it instead of mysql or other DB
+	    modules.
+	</para>
+
+	<example id="exmple1">
+	    <title>Load the bdb module</title>
+	    <programlisting>
+...
+loadmodule "/path/to/ser/modules/dbb.so"
+...
+modparam("module_name", "db_url", "bdb:///path/to/bdb/database")
+...
+	    </programlisting>
+	</example>
+
+	<example id="exmple2">
+	    <title>definition of the standard version table</title>
+	    <programlisting>
+...
+modparam("bdb", "describe_table", "version:table_name(str) table_version(int)")
+...
+	    </programlisting>
+	</example>
+
+	<section id="using">
+	    <title>Using BDB With Basic SER Configuration</title>
+	    <para>
+		Here are the definitions for tables used by usrloc module as well
+		as a part of basic configuration file to use BDB driver with SER.
+		The table structures may change in future releases, so that some
+		adjustment to example below may be necessary. That example
+		corresponds to SER v0.9.4
+	    </para>
+	    <para>
+		According to the configuration file below, the table files will be
+		placed in the //var/db/ser/ directory.
+	    </para>
+	    <para>
+		The table version should be populated manually before the SER is
+		started. To do this version.dump file located in the directotry of
+		BDB driver sources and db_load utility from Berkeley BD
+		distribution should be used as follows:
+		<example id="exmple3">
+			<title>Population of version table</title>
+			<programlisting>
+$ db_load -h /var/db/ser -f version.dump version
+			</programlisting>
+		</example>
+	    </para>
+
+	    <example id="exmple4">
+		<title>Configuration file</title>
+		<programlisting>
+# ---------- global configuration parameters ------------------------
+
+# [skip]
+
+# ---------- module loading -----------------------------------------
+
+loadmodule "/usr/local/lib/ser/modules/usrloc.so"
+loadmodule "/usr/local/lib/ser/modules/bdb.so"
+
+# ---------- module-specific configuration parameteres --------------
+
+modparam("usrloc", "db_mode", 2)
+modparam("usrloc", "timer_interval", 3)
+modparam("usrloc", "db_url", "bdb:///var/db/ser")
+
+modparam("bdb", "describe_table", "version:table_name(str) table_version(int)")
+
+modparam("bdb", "describe_table", "location:username(str) domain(str) contact(str) i_env(int) expires(datetime) q(double) callid(str) cseq(int) method(str) flags(int) user_agent(str) received(str)")
+modparam("bdb", "describe_table", "aliases:username(str) domain(str) contact(str) i_env(int) expires(datetime) q(double) callid(str) cseq(int) method(str) flags(int) user_agent(str) received(str)")
+
+# ---------- request routing logic ----------------------------------
+
+# [skip]
+
+		</programlisting>
+	    </example>
+	</section>
+    </section>
+
+</section>

+ 11 - 0
bdb/version.dump

@@ -0,0 +1,11 @@
+VERSION=3
+format=print
+type=btree
+duplicates=1
+db_pagesize=16384
+HEADER=END
+ aliases
+ \04\00\00\00\00\00\00\00\1cB\13\08\07\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\09\00\00\00\00\00\00\00\00\00\00\00aliases
+ location
+ \04\00\00\00\00\00\00\00\d0A\13\08\08\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\09\00\00\00\00\00\00\00\00\00\00\00location
+DATA=END

+ 225 - 0
cpl-c/CPL_tree.h

@@ -0,0 +1,225 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _CPL_TREE_DEFINITION_H
+#define _CPL_TREE_DEFINITION_H
+
+
+
+#define              CPL_NODE   1
+#define         INCOMING_NODE   2
+#define         OUTGOING_NODE   3
+#define        ANCILLARY_NODE   4
+#define        SUBACTION_NODE   5
+#define   ADDRESS_SWITCH_NODE   6
+#define          ADDRESS_NODE   7
+#define             BUSY_NODE   8
+#define          DEFAULT_NODE   9
+#define          FAILURE_NODE  10
+#define              LOG_NODE  11
+#define           LOOKUP_NODE  12
+#define         LOCATION_NODE  13
+#define         LANGUAGE_NODE  14
+#define  LANGUAGE_SWITCH_NODE  15
+#define             MAIL_NODE  16
+#define         NOTFOUND_NODE  17
+#define         NOANSWER_NODE  18
+#define            PROXY_NODE  19
+#define         PRIORITY_NODE  20
+#define  PRIORITY_SWITCH_NODE  21
+#define           REJECT_NODE  22
+#define         REDIRECT_NODE  23
+#define      REDIRECTION_NODE  24
+#define  REMOVE_LOCATION_NODE  25
+#define              SUB_NODE  26
+#define          SUCCESS_NODE  27
+#define           STRING_NODE  28
+#define    STRING_SWITCH_NODE  29
+#define             TIME_NODE  30
+#define      TIME_SWITCH_NODE  31
+#define        OTHERWISE_NODE  32
+#define      NOT_PRESENT_NODE  33
+
+
+
+/* attributes and values fro ADDRESS-SWITCH node */
+#define  FIELD_ATTR                  0       /*shared with STRING_SWITCH*/
+#define  SUBFIELD_ATTR               1
+#define  ORIGIN_VAL                  0
+#define  DESTINATION_VAL             1
+#define  ORIGINAL_DESTINATION_VAL    2
+#define  ADDRESS_TYPE_VAL            0
+#define  USER_VAL                    1
+#define  HOST_VAL                    2
+#define  PORT_VAL                    3
+#define  TEL_VAL                     4
+#define  DISPLAY_VAL                 5
+
+/* attributes and values for ADDRESS node */
+#define  IS_ATTR                     0    /*shared with STRING*/
+#define  CONTAINS_ATTR               1    /*shared with STRING*/
+#define  SUBDOMAIN_OF_ATTR           2
+
+/* attributes and values for STRING-SWITCH node */
+#define  SUBJECT_VAL                 0
+#define  ORGANIZATION_VAL            1
+#define  USER_AGENT_VAL              2
+#define  DISPALY_VAL                 3
+
+/* attributes and values for LANGUAGE node */
+#define  MATCHES_TAG_ATTR            0
+#define  MATCHES_SUBTAG_ATTR         1
+
+/* attributes and values for TIME-SWITCH node */
+#define  TZID_ATTR                   0
+#define  TZURL_ATTR                  1
+
+/* attributes and values for TIME node */
+#define  DTSTART_ATTR                0
+#define  DTEND_ATTR                  1
+#define  DURATION_ATTR               2
+#define  FREQ_ATTR                   3
+#define  INTERVAL_ATTR               4
+#define  UNTIL_ATTR                  5
+#define  COUNT_ATTR                  6
+#define  BYSECOND_ATTR               7
+#define  BYMINUTE_ATTR               8
+#define  BYHOUR_ATTR                 9
+#define  BYDAY_ATTR                 10
+#define  BYMONTHDAY_ATTR            11
+#define  BYYEARDAY_ATTR             12
+#define  BYWEEKNO_ATTR              13
+#define  BYMONTH_ATTR               14
+#define  WKST_ATTR                  15
+#define  BYSETPOS_ATTR              16
+
+/* attributes and values for PRIORITY node */
+#define  LESS_ATTR                   0
+#define  GREATER_ATTR                1
+#define  EQUAL_ATTR                  2
+#define  PRIOSTR_ATTR                3
+#define  EMERGENCY_VAL               0
+#define  EMERGENCY_STR               "emergency"
+#define  EMERGENCY_STR_LEN           (sizeof(EMERGENCY_STR)-1)
+#define  URGENT_VAL                  1
+#define  URGENT_STR                  "urgent"
+#define  URGENT_STR_LEN              (sizeof(URGENT_STR)-1)
+#define  NORMAL_VAL                  2
+#define  NORMAL_STR                  "normal"
+#define  NORMAL_STR_LEN              (sizeof(NORMAL_STR)-1)
+#define  NON_URGENT_VAL              3
+#define  NON_URGENT_STR              "non-urgent"
+#define  NON_URGENT_STR_LEN          (sizeof(NON_URGENT_STR)-1)
+#define  UNKNOWN_PRIO_VAL            4
+
+/* attributes and values for LOCATION node */
+#define  URL_ATTR                    0
+#define  PRIORITY_ATTR               1
+#define  CLEAR_ATTR                  2    /*shared with LOOKUP node*/
+#define  NO_VAL                      0    /*shared with LOOKUP node*/
+#define  YES_VAL                     1    /*shared with LOOKUP node*/
+
+/* attributes and values for LOOKUP node */
+#define  SOURCE_ATTR                 0
+#define  TIMEOUT_ATTR                1    /*shared with PROXY node*/
+#define  SOURCE_REG_STR              "registration"
+#define  SOURCE_REG_STR_LEN          (sizeof("registration")-1)
+
+/* attributes and values for REMOVE_LOCATION node */
+#define  LOCATION_ATTR               0
+#define  PARAM_ATTR                  1
+#define  VALUE_ATTR                  2
+
+/* attributes and values for PROXY node */
+#define  RECURSE_ATTR                2
+#define  ORDERING_ATTR               3
+#define  PARALLEL_VAL                0
+#define  SEQUENTIAL_VAL              1
+#define  FIRSTONLY_VAL               2
+
+/* attributes and values for REDIRECT node */
+#define  PERMANENT_ATTR              0
+
+/* attributes and values for REJECT node */
+#define  STATUS_ATTR                 0
+#define  REASON_ATTR                 1
+#define  BUSY_VAL                    486
+#define  BUSY_STR                    "busy"
+#define  BUSY_STR_LEN                (sizeof(BUSY_STR)-1)
+#define  NOTFOUND_VAL                404
+#define  NOTFOUND_STR                "notfound"
+#define  NOTFOUND_STR_LEN            (sizeof(NOTFOUND_STR)-1)
+#define  REJECT_VAL                  603
+#define  REJECT_STR                  "reject"
+#define  REJECT_STR_LEN              (sizeof(REJECT_STR)-1)
+#define  ERROR_VAL                   500
+#define  ERROR_STR                   "error"
+#define  ERROR_STR_LEN               (sizeof(ERROR_STR)-1)
+
+/* attributes and values for LOG node */
+#define  NAME_ATTR                   0
+#define  MAX_NAME_SIZE               32
+#define  COMMENT_ATTR                1
+#define  MAX_COMMENT_SIZE            128
+
+/* attributes and values for EMAIL node */
+#define  TO_ATTR                     0
+#define  SUBJECT_ATTR                1
+#define  SUBJECT_EMAILHDR_STR        "subject"
+#define  SUBJECT_EMAILHDR_LEN        (sizeof(SUBJECT_EMAILHDR_STR)-1)
+#define  BODY_ATTR                   2
+#define  BODY_EMAILHDR_STR           "body"
+#define  BODY_EMAILHDR_LEN           (sizeof(BODY_EMAILHDR_STR)-1)
+#define  URL_MAILTO_STR              "mailto:"
+#define  URL_MAILTO_LEN              (sizeof(URL_MAILTO_STR)-1)
+
+/* attributes and values for SUB node */
+#define  REF_ATTR                    0
+
+
+
+/* node = | type(1) | nr_kids(1) | nr_attrs(1) | unused(1) |
+ *        | x*kids_offset(2) | y*attrs(2*n) |
+ */
+
+#define      NODE_TYPE(_p)            ( *((unsigned char*)(_p)) )
+#define      NR_OF_KIDS(_p)           ( *((unsigned char* )((_p)+1)) )
+#define      NR_OF_ATTR(_p)           ( *((unsigned char* )((_p)+1+1)) )
+#define      KID_OFFSET_PTR(_p,_n)    ( (unsigned short*)((_p)+4+2*(_n)) )
+#define      ATTR_PTR(_p)             ( (_p)+4+2*NR_OF_KIDS(_p) )
+#define      SIMPLE_NODE_SIZE(_p)     ( 4+2*NR_OF_KIDS(_p) )
+#define      GET_NODE_SIZE(_n)        ( 4+2*(_n) )
+#define      BASIC_ATTR_SIZE          4
+
+#define      SET_KID_OFFSET(_p,_n,_o) *KID_OFFSET_PTR(_p,_n)=htons(_o)
+#define      KID_OFFSET(_p,_n)        ntohs(*KID_OFFSET_PTR(_p,_n))
+
+
+
+
+#endif
+

+ 21 - 0
cpl-c/Makefile

@@ -0,0 +1,21 @@
+# $Id$
+#
+# example module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=cpl-c.so
+
+DEFS +=-I/usr/include/libxml2 -I$(LOCALBASE)/include/libxml2 \
+	-I$(LOCALBASE)/include  # iconv.h
+
+LIBS= -L$(LOCALBASE)/lib -lxml2
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 250 - 0
cpl-c/README

@@ -0,0 +1,250 @@
+1. CPL Module
+
+Bogdan-Andrei Iancu
+
+   FhG FOKUS
+
+   Copyright © 2003 FhG FOKUS
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Dependencies
+
+        1.2.1. SER Modules
+        1.2.2. External Libraries or Applications
+
+   1.3. Parameters
+
+        1.3.1. cpl_db (string)
+        1.3.2. cpl_table (string)
+        1.3.3. cpl_dtd_file (string)
+        1.3.4. log_dir (string)
+        1.3.5. proxy_recurse (int)
+        1.3.6. proxy_route (int)
+        1.3.7. nat_flag (int)
+        1.3.8. lookup_domain (int)
+
+   1.4. Functions
+
+        1.4.1. cpl_run_script(type,mode)
+        1.4.2. cpl_process_register()
+
+1.1. Overview
+
+   cpl-c modules implements a CPL (Call Processing Language) interpreter.
+   Support for uploading/downloading/removing scripts via SIP REGISTER
+   method is implemented.
+
+1.2. Dependencies
+
+1.2.1. SER Modules
+
+   The following modules must be loaded before this module:
+     * tm.  Transaction Manager, used for proxying/forking requests.
+     * sl.  StateLess module - used for sending stateless reply when
+       responding to REGISTER request or for sending back error responses.
+     * usrloc.  User location module - used for implementing
+       lookup("registration") (adding into location set of the users'
+       contact)
+
+1.2.2. External Libraries or Applications
+
+   The following libraries or applications must be installed before
+   running SER with this module loaded:
+     * libxml2.  This library contains an engine for XML parsing, DTD
+       validation and DOM manipulation.
+
+1.3. Parameters
+
+1.3.1. cpl_db (string)
+
+   A SQL URL have to be given to the module for knowing where the database
+   containing the table with CPL scripts is locates. If required a user
+   name and password can be specified for allowing the module to connect
+   to the database server.
+
+Warning
+
+   This parameter is mandatory.
+
+   Example 1. Set cpl_db parameter
+...
+modparam("cpl_c","cpl_db","mysql://user:passwd@host/database")
+...
+
+1.3.2. cpl_table (string)
+
+   Indicates the name of the table that store the CPL scripts. This table
+   must be locate into the database specified by "cpl_db" parameter. For
+   more about the format of the CPL table please see
+   modules/cpl-c/init.mysql.
+
+Warning
+
+   This parameter is mandatory.
+
+   Example 2. Set cpl_table parameter
+...
+modparam("cpl_c","cpl_table","cpltable")
+...
+
+1.3.3. cpl_dtd_file (string)
+
+   Points to the DTD file describing the CPL grammar. The file name may
+   include also the path to the file. This path can be absolute or
+   relative (be careful the path will be relative to the starting
+   directory of SER).
+
+Warning
+
+   This parameter is mandatory.
+
+   Example 3. Set cpl_dtd_file parameter
+...
+modparam("cpl_c","cpl_dtd_file","/etc/ser/cpl-06.dtd")
+...
+
+1.3.4. log_dir (string)
+
+   Points to a directory where should be created all the log file
+   generated by the LOG CPL node. A log file per user will be created (on
+   demand) having the name username.log.
+
+Note
+
+   If this parameter is absent, the logging will be disabled without
+   generating error on execution.
+
+   Example 4. Set log_dir parameter
+...
+modparam("cpl_c","log_dir","/var/log/ser/cpl")
+...
+
+1.3.5. proxy_recurse (int)
+
+   Tells for how many time is allow to have recurse for PROXY CPL node If
+   it has value 2, when doing proxy, only twice the proxy action will be
+   re-triggered by a redirect response; the third time, the proxy
+   execution will end by going on REDIRECTION branch. The recurse feature
+   can be disable by setting this parameter to 0
+
+   Default value of this parameter is 0.
+
+   Example 5. Set proxy_recurse parameter
+...
+modparam("cpl_c","proxy_recurse",2)
+...
+
+1.3.6. proxy_route (int)
+
+   Before doing proxy (forward), a script route can be executed. All
+   modifications made by that route will be reflected only for the current
+   branch.
+
+   Default value of this parameter is 0 (none).
+
+   Example 6. Set proxy_route parameter
+...
+modparam("cpl_c","proxy_route",1)
+...
+
+1.3.7. nat_flag (int)
+
+   Sets the flag used for marking calls via NAT. Used by lookup tag when
+   retrieving a contact behind a NAT (this flag will be set).
+
+   Default value of this parameter is 6.
+
+   Example 7. Set nat_flag parameter
+...
+modparam("cpl_c","nat_flag",4)
+...
+
+1.3.8. lookup_domain (int)
+
+   Tells if the lookup tag should use or not the domain part when doing
+   user location search. Set it to a non zero value to force also domain
+   matching.
+
+   Default value of this parameter is 0.
+
+   Example 8. Set lookup_domain parameter
+...
+modparam("cpl_c","lookup_domain",1)
+...
+
+1.4. Functions
+
+1.4.1.  cpl_run_script(type,mode)
+
+   Starts the execution of the CPL script. The user name is fetched from
+   new_uri or requested uri or from To header -in this order- (for
+   incoming execution) or from FROM header (for outgoing execution).
+   Regarding the stateful/stateless message processing, the function is
+   very flexible, being able to run in different modes (see below
+   the"mode" parameter). Normally this function will end script execution.
+   There is no guaranty that the CPL script interpretation ended when ser
+   script ended also (for the same INVITE ;-)) - this can happen when the
+   CPL script does a PROXY and the script interpretation pause after
+   proxying and it will be resume when some reply is received (this can
+   happen in a different process of SER). If the function returns to
+   script, the SIP server should continue with the normal behavior as if
+   no script existed. When some error is returned, the function itself
+   haven't sent any SIP error reply (this can be done from script).
+
+   Meaning of the parameters is as follows:
+     * type - which part of the script should be run; set it to "incoming"
+       for having the incoming part of script executed (when an INVITE is
+       received) or to "outgoing" for running the outgoing part of script
+       (when a user is generating an INVITE - call).
+     * mode - sets the interpreter mode as stateless/stateful behavior.
+       The following modes are accepted:
+          + IS_STATELESS - the current INVITE has no transaction created
+            yet. All replies (redirection or deny) will be done is a
+            stateless way. The execution will switch to stateful only when
+            proxy is done. So, if the function returns, will be in
+            stateless mode.
+          + IS_STATEFUL - the current INVITE has already a transaction
+            associated. All signaling operations (replies or proxy) will
+            be done in stateful way.So, if the function returns, will be
+            in stateful mode.
+          + FORCE_STATEFUL - the current INVITE has no transaction created
+            yet. All signaling operations will be done is a stateful way
+            (on signaling, the transaction will be created from within the
+            interpreter). So, if the function returns, will be in
+            stateless mode.
+
+Note
+       is_stateful is very difficult to manage from the routing script
+       (script processing can continue in stateful mode); is_stateless is
+       the fastest and consumes less resources (transaction is created
+       only if proxying is done), but there is only a minimal protection
+       against retransmissions (since replies are send statelessly);
+       force_stateful is a good compromise - all signaling is done
+       stateful (retransmission protection) and in the same time, if
+       returning to script, it will be in stateless mode (easy to continue
+       the routing script execution)
+
+   Example 9. cpl_run_script usage
+...
+cpl_run_script("incoming","force_stateful");
+...
+
+1.4.2.  cpl_process_register()
+
+   This function MUST be called only for REGISTER requests. It checks if
+   the current REGISTER request is related or not with CPL script
+   upload/download/ remove. If it is, all the needed operation will be
+   done. For checking if the REGISTER is CPL related, the function looks
+   fist to "Content-Type" header. If it exists and has a the mime type set
+   to "application/cpl+xml" means this is a CPL script upload/remove
+   operation. The distinction between to case is made by looking at
+   "Content-Disposition" header; id its value is "script;action=store",
+   means it's an upload; if it's "script;action=remove", means it's a
+   remove operation; other values are considered to be errors. If no
+   "Content-Type" header is present, the function looks to "Accept" header
+   and if it contains the "*" or "application/cpl-xml" the request it will
+   be consider one for downloading CPL scripts. The functions returns to
+   script only if the REGISTER is not related to CPL. In other case, the
+   function will send by itself the necessary replies (stateless - using
+   sl), including for errors.

+ 266 - 0
cpl-c/cpl-06.dtd

@@ -0,0 +1,266 @@
+<?xml version="1.0" encoding="US-ASCII" ?>
+
+   <!--
+       Draft DTD for CPL, corresponding to
+       draft-ietf-iptel-cpl-06.
+   -->
+
+   <!-- Nodes. -->
+   <!-- Switch nodes -->
+   <!ENTITY % Switch 'address-switch|string-switch|language-switch|
+                      time-switch|priority-switch' >
+
+   <!-- Location nodes -->
+   <!ENTITY % Location 'location|lookup|remove-location' >
+
+   <!-- Signalling action nodes -->
+   <!ENTITY % SignallingAction 'proxy|redirect|reject' >
+
+   <!-- Other actions -->
+   <!ENTITY % OtherAction 'mail|log' >
+
+   <!-- Links to subactions -->
+   <!ENTITY % Sub 'sub' >
+
+   <!-- Nodes are one of the above four categories, or a subaction.
+        This entity (macro) describes the contents of an output.
+        Note that a node can be empty, implying default action. -->
+   <!ENTITY % Node     '(%Location;|%Switch;|%SignallingAction;|
+                        %OtherAction;|%Sub;)?' >
+
+
+   <!-- Switches: choices a CPL script can make. -->
+
+   <!-- All switches can have an 'otherwise' output. -->
+   <!ELEMENT otherwise ( %Node; ) >
+
+   <!-- All switches can have a 'not-present' output. -->
+   <!ELEMENT not-present ( %Node; ) >
+
+   <!-- Address-switch makes choices based on addresses. -->
+   <!ELEMENT address-switch ( address*, (not-present, address*)?,
+                              otherwise? ) >
+   <!-- <not-present> must appear at most once -->
+   <!ATTLIST address-switch
+      field         CDATA    #REQUIRED
+      subfield      CDATA    #IMPLIED
+   >
+
+   <!ELEMENT address ( %Node; ) >
+
+   <!ATTLIST address
+      is            CDATA    #IMPLIED
+      contains      CDATA    #IMPLIED
+      subdomain-of  CDATA    #IMPLIED
+   > <!-- Exactly one of these three attributes must appear -->
+
+
+   <!-- String-switch makes choices based on strings. -->
+
+   <!ELEMENT string-switch ( string*, (not-present, string*)?,
+                             otherwise? ) >
+   <!-- <not-present> must appear at most once -->
+   <!ATTLIST string-switch
+      field         CDATA    #REQUIRED
+   >
+
+   <!ELEMENT string ( %Node; ) >
+   <!ATTLIST string
+      is            CDATA    #IMPLIED
+      contains      CDATA    #IMPLIED
+   >  <!-- Exactly one of these two attributes must appear -->
+
+
+   <!-- Language-switch makes choices based on the originator's preferred
+        languages. -->
+
+   <!ELEMENT language-switch ( language*, (not-present, language*)?,
+                               otherwise? ) >
+   <!-- <not-present> must appear at most once -->
+
+   <!ELEMENT language ( %Node; ) >
+   <!ATTLIST language
+      matches      CDATA     #REQUIRED
+   >
+
+
+   <!-- Time-switch makes choices based on the current time. -->
+
+   <!ELEMENT time-switch ( time*, (not-present, time*)?, otherwise? ) >
+   <!ATTLIST time-switch
+      tzid          CDATA    #IMPLIED
+      tzurl         CDATA    #IMPLIED
+   >
+
+   <!ELEMENT time ( %Node; ) >
+
+   <!-- Exactly one of the two attributes "dtend" and "duration"
+        must occur. -->
+   <!-- The value of "freq" is (daily|weekly|monthly|yearly).  It is
+           case-insensitive, so it is not given as a DTD switch. -->
+   <!-- None of the attributes following freq are meaningful unless freq
+            appears. -->
+   <!-- The value of "wkst" is (MO|TU|WE|TH|FR|SA|SU).  It is
+           case-insensitive, so it is not given as a DTD switch. -->
+   <!ATTLIST time
+      dtstart       CDATA  #REQUIRED
+      dtend         CDATA  #IMPLIED
+      duration      CDATA  #IMPLIED
+      freq          CDATA  #IMPLIED
+      until         CDATA  #IMPLIED
+      count         CDATA  #IMPLIED
+      interval      CDATA  "1"
+      bysecond      CDATA  #IMPLIED
+      byminute      CDATA  #IMPLIED
+      byhour        CDATA  #IMPLIED
+      byday         CDATA  #IMPLIED
+      bymonthday    CDATA  #IMPLIED
+      byyearday     CDATA  #IMPLIED
+      byweekno      CDATA  #IMPLIED
+      bymonth       CDATA  #IMPLIED
+      wkst          CDATA  "MO"
+      bysetpos      CDATA  #IMPLIED
+   >
+
+
+   <!-- Priority-switch makes choices based on message priority. -->
+
+   <!ELEMENT priority-switch ( priority*, (not-present, priority*)?,
+                               otherwise? ) >
+   <!-- <not-present> must appear at most once -->
+
+   <!ENTITY % PriorityVal '(emergency|urgent|normal|non-urgent)' >
+
+   <!ELEMENT priority ( %Node; ) >
+
+   <!-- Exactly one of these three attributes must appear -->
+   <!ATTLIST priority
+      less          %PriorityVal;  #IMPLIED
+      greater       %PriorityVal;  #IMPLIED
+      equal         CDATA          #IMPLIED
+   >
+
+
+   <!-- Locations: ways to specify the location a subsequent action
+        (proxy, redirect) will attempt to contact. -->
+
+   <!ENTITY % Clear  'clear (yes|no) "no"' >
+
+
+   <!ELEMENT location ( %Node; ) >
+   <!ATTLIST location
+      url           CDATA    #REQUIRED
+      priority      CDATA    #IMPLIED
+      %Clear;
+   >
+   <!-- priority is in the range  0.0 - 1.0.  Its default value SHOULD
+         be 1.0 -->
+
+   <!ELEMENT lookup ( success?,notfound?,failure? ) >
+   <!ATTLIST lookup
+     source         CDATA     #REQUIRED
+     timeout        CDATA     "30"
+     use            CDATA     #IMPLIED
+     ignore         CDATA     #IMPLIED
+     %Clear;
+   >
+
+   <!ELEMENT success  ( %Node; ) >
+   <!ELEMENT notfound ( %Node; ) >
+   <!ELEMENT failure ( %Node; ) >
+
+   <!ELEMENT remove-location ( %Node; ) >
+   <!ATTLIST remove-location
+      param         CDATA    #IMPLIED
+      value         CDATA    #IMPLIED
+      location      CDATA    #IMPLIED
+   >
+
+
+   <!-- Signalling Actions: call-signalling actions the script can
+        take. -->
+
+   <!ELEMENT proxy ( busy?,noanswer?,redirection?,failure?,default? ) >
+
+   <!-- The default value of timeout is "20" if the <noanswer> output
+        exists. -->
+   <!ATTLIST proxy
+      timeout       CDATA    #IMPLIED
+      recurse       (yes|no) "yes"
+      ordering      (parallel|sequential|first-only) "parallel"
+   >
+
+   <!ELEMENT busy ( %Node; ) >
+   <!ELEMENT noanswer ( %Node; ) >
+   <!ELEMENT redirection ( %Node; ) >
+   <!-- "failure" repeats from lookup, above. -->
+   <!ELEMENT default ( %Node; ) >
+
+   <!ELEMENT redirect EMPTY >
+   <!ATTLIST redirect
+      permanent     (yes|no) "no"
+   >
+
+
+   <!-- Statuses we can return -->
+
+   <!ELEMENT reject EMPTY >
+   <!-- The value of "status" is (busy|notfound|reject|error), or a SIP
+        4xx-6xx status. -->
+   <!ATTLIST reject
+      status        CDATA    #REQUIRED
+      reason        CDATA    #IMPLIED
+   >
+
+   <!-- Non-signalling actions: actions that don't affect the call -->
+
+   <!ELEMENT mail ( %Node; ) >
+   <!ATTLIST mail
+      url           CDATA    #REQUIRED
+   >
+
+   <!ELEMENT log ( %Node; ) >
+   <!ATTLIST log
+      name          CDATA    #IMPLIED
+      comment       CDATA    #IMPLIED
+   >
+
+
+   <!-- Calls to subactions. -->
+
+   <!ELEMENT sub EMPTY >
+   <!ATTLIST sub
+      ref           IDREF    #REQUIRED
+   >
+
+
+   <!-- Ancillary data -->
+
+   <!ENTITY % Ancillary 'ancillary?' >
+
+   <!ELEMENT ancillary EMPTY >
+
+
+   <!-- Subactions -->
+
+   <!ENTITY % Subactions 'subaction*' >
+
+
+   <!ELEMENT subaction ( %Node; )>
+   <!ATTLIST subaction
+      id            ID       #REQUIRED
+   >
+
+
+   <!-- Top-level actions -->
+
+   <!ENTITY % TopLevelActions 'outgoing?,incoming?' >
+
+   <!ELEMENT outgoing ( %Node; )>
+
+   <!ELEMENT incoming ( %Node; )>
+
+   <!-- The top-level element of the script. -->
+
+   <!ELEMENT cpl  ( %Ancillary;,%Subactions;,%TopLevelActions; ) >
+

+ 949 - 0
cpl-c/cpl.c

@@ -0,0 +1,949 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-03-11: New module interface (janakj)
+ * 2003-03-16: flags export parameter added (janakj)
+ * 2003-11-11: build_lump_rpl() removed, add_lump_rpl() has flags (bogdan)
+ * 2004-06-06  updated to the new DB api (andrei)
+ * 2004-06-14: all global variables merged into cpl_env and cpl_fct;
+ *             case_sensitive and realm_prefix added for building AORs - see
+ *             build_userhost (bogdan)
+ * 2004-10-09: added process_register_norpl to allow register processing
+ *             without sending the reply(bogdan) - based on a patch sent by
+ *             Christopher Crawford
+ */
+
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <signal.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../mem/mem.h"
+#include "../../sr_module.h"
+#include "../../str.h"
+#include "../../ut.h"
+#include "../../dprint.h"
+#include "../../data_lump_rpl.h"
+#include "../../usr_avp.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_content.h"
+#include "../../parser/parse_disposition.h"
+#include "../../lib/srdb2/db.h"
+#include "../../cfg/cfg_struct.h"
+#include "cpl_run.h"
+#include "cpl_env.h"
+#include "cpl_db.h"
+#include "cpl_loader.h"
+#include "cpl_parser.h"
+#include "cpl_nonsig.h"
+#include "cpl_rpc.h"
+#include "loc_set.h"
+
+
+#define MAX_PROXY_RECURSE  10
+#define MAX_USERHOST_LEN    256
+
+
+/* modules param variables */
+static char *DB_URL        = 0;  /* database url */
+static char *DB_TABLE      = 0;  /* */
+static char *dtd_file      = 0;  /* name of the DTD file for CPL parser */
+static char *lookup_domain = 0;
+static pid_t aux_process   = 0;  /* pid of the private aux. process */
+static char *timer_avp     = 0;  /* name of variable timer AVP */
+
+
+struct cpl_enviroment    cpl_env = {
+		0, /* no cpl logging */
+		0, /* recurse proxy level is 0 */
+		0, /* no script route to be run before proxy */
+		6, /* nat flag */
+		0, /* user part is not case sensitive */
+		{0,0},   /* no domain prefix to be ignored */
+		{-1,-1}, /* communication pipe to aux_process */
+		{0,0},   /* original TZ \0 terminated "TZ=value" format */
+		0, /* udomain */
+		0, /* no branches on lookup */
+		0, /* timer avp type */
+		/*(int_str)*/{ 0 } /* timer avp name/ID */
+};
+
+struct cpl_functions  cpl_fct;
+
+
+MODULE_VERSION
+
+
+static int cpl_invoke_script (struct sip_msg* msg, char* str, char* str2);
+static int w_process_register(struct sip_msg* msg, char* str, char* str2);
+static int w_process_register_norpl(struct sip_msg* msg, char* str,char* str2);
+static int cpl_process_register(struct sip_msg* msg, int no_rpl);
+static int fixup_cpl_run_script(void** param, int param_no);
+static int cpl_init(void);
+static int cpl_child_init(int rank);
+static int cpl_exit(void);
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+	{"cpl_run_script",cpl_invoke_script,2,fixup_cpl_run_script,REQUEST_ROUTE},
+	{"cpl_process_register",w_process_register,0,0,REQUEST_ROUTE},
+	{"cpl_process_register_norpl",w_process_register_norpl,0,0,REQUEST_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+	{"cpl_db",         PARAM_STRING, &DB_URL      },
+	{"cpl_table",      PARAM_STRING, &DB_TABLE    },
+	{"cpl_dtd_file",   PARAM_STRING, &dtd_file    },
+	{"proxy_recurse",  PARAM_INT,    &cpl_env.proxy_recurse  },
+	{"proxy_route",    PARAM_INT,    &cpl_env.proxy_route    },
+	{"nat_flag",       PARAM_INT,    &cpl_env.nat_flag       },
+	{"log_dir",        PARAM_STRING, &cpl_env.log_dir        },
+	{"case_sensitive", PARAM_INT,    &cpl_env.case_sensitive },
+	{"realm_prefix",   PARAM_STR,    &cpl_env.realm_prefix   },
+	{"lookup_domain",  PARAM_STRING, &lookup_domain          },
+	{"lookup_append_branches", PARAM_INT, &cpl_env.lu_append_branches},
+	{"timer_avp",      PARAM_STRING, &timer_avp   },
+	{0, 0, 0}
+};
+
+
+struct module_exports exports = {
+	"cpl-c",
+	cmds,             /* Exported functions */
+	cpl_rpc_methods,  /* RPC methods */
+	params,           /* Exported parameters */
+	cpl_init,         /* Module initialization function */
+	(response_function) 0,
+	(destroy_function) cpl_exit,
+	0,
+	(child_init_function) cpl_child_init /* per-child init function */
+};
+
+
+
+static int fixup_cpl_run_script(void** param, int param_no)
+{
+	long flag;
+
+	if (param_no==1) {
+		if (!strcasecmp( "incoming", *param))
+			flag = CPL_RUN_INCOMING;
+		else if (!strcasecmp( "outgoing", *param))
+			flag = CPL_RUN_OUTGOING;
+		else {
+			LOG(L_ERR,"ERROR:fixup_cpl_run_script: script directive \"%s\""
+				" unknown!\n",(char*)*param);
+			return E_UNSPEC;
+		}
+		pkg_free(*param);
+		*param=(void*)flag;
+		return 0;
+	} else if (param_no==2) {
+		if ( !strcasecmp("is_stateless", *param) ) {
+			flag = 0;
+		} else if ( !strcasecmp("is_stateful", *param) ) {
+			flag = CPL_IS_STATEFUL;
+		} else if ( !strcasecmp("force_stateful", *param) ) {
+			flag = CPL_FORCE_STATEFUL;
+		} else {
+			LOG(L_ERR,"ERROR:fixup_cpl_run_script: flag \"%s\" (second param)"
+				" unknown!\n",(char*)*param);
+			return E_UNSPEC;
+		}
+		pkg_free(*param);
+		*param=(void*)flag;
+	}
+	return 0;
+}
+
+
+
+static int cpl_init(void)
+{
+	bind_usrloc_t bind_usrloc;
+	load_tm_f     load_tm;
+	struct stat   stat_t;
+	char *ptr;
+	int val;
+	str foo;
+
+	LOG(L_INFO,"CPL - initializing\n");
+
+	/* check the module params */
+	if (DB_URL==0) {
+		LOG(L_CRIT,"ERROR:cpl_init: mandatory parameter \"cpl_db\" "
+			"found empty\n");
+		goto error;
+	}
+
+	if (DB_TABLE==0) {
+		LOG(L_CRIT,"ERROR:cpl_init: mandatory parameter \"cpl_table\" "
+			"found empty\n");
+		goto error;
+	}
+
+	if (cpl_env.proxy_recurse>MAX_PROXY_RECURSE) {
+		LOG(L_CRIT,"ERROR:cpl_init: value of proxy_recurse param (%d) exceeds "
+			"the maximum safety value (%d)\n",
+			cpl_env.proxy_recurse,MAX_PROXY_RECURSE);
+		goto error;
+	}
+
+	/* fix the timer_avp name */
+	if (timer_avp) {
+		foo.s = timer_avp;
+		foo.len = strlen(foo.s);
+		if (parse_avp_spec(&foo,&cpl_env.timer_avp_type,&cpl_env.timer_avp,0)<0){
+			LOG(L_CRIT,"ERROR:cpl_init: invalid timer AVP specs \"%s\"\n",
+				timer_avp);
+			goto error;
+		}
+		if (cpl_env.timer_avp_type&AVP_NAME_STR && cpl_env.timer_avp.s.s==foo.s) {
+			cpl_env.timer_avp.s = foo;
+		}
+	}
+
+	if (dtd_file==0) {
+		LOG(L_CRIT,"ERROR:cpl_init: mandatory parameter \"cpl_dtd_file\" "
+			"found empty\n");
+		goto error;
+	} else {
+		/* check if the dtd file exists */
+		if (stat( dtd_file, &stat_t)==-1) {
+			LOG(L_ERR,"ERROR:cpl_init: checking file \"%s\" status failed;"
+				" stat returned %s\n",dtd_file,strerror(errno));
+			goto error;
+		}
+		if ( !S_ISREG( stat_t.st_mode ) ) {
+			LOG(L_ERR,"ERROR:cpl_init: dir \"%s\" is not a regular file!\n",
+				dtd_file);
+			goto error;
+		}
+		if (access( dtd_file, R_OK )==-1) {
+			LOG(L_ERR,"ERROR:cpl_init: checking file \"%s\" for permissions "
+				"failed; access returned %s\n",dtd_file,strerror(errno));
+			goto error;
+		}
+	}
+
+	if (cpl_env.log_dir==0) {
+		LOG(L_INFO,"INFO:cpl_init: log_dir param found void -> logging "
+			" disabled!\n");
+	} else {
+		if ( strlen(cpl_env.log_dir)>MAX_LOG_DIR_SIZE ) {
+			LOG(L_ERR,"ERROR:cpl_init: dir \"%s\" has a too long name :-(!\n",
+				cpl_env.log_dir);
+			goto error;
+		}
+		/* check if the dir exists */
+		if (stat( cpl_env.log_dir, &stat_t)==-1) {
+			LOG(L_ERR,"ERROR:cpl_init: checking dir \"%s\" status failed;"
+				" stat returned %s\n",cpl_env.log_dir,strerror(errno));
+			goto error;
+		}
+		if ( !S_ISDIR( stat_t.st_mode ) ) {
+			LOG(L_ERR,"ERROR:cpl_init: dir \"%s\" is not a directory!\n",
+				cpl_env.log_dir);
+			goto error;
+		}
+		if (access( cpl_env.log_dir, R_OK|W_OK )==-1) {
+			LOG(L_ERR,"ERROR:cpl_init: checking dir \"%s\" for permissions "
+				"failed; access returned %s\n",
+				cpl_env.log_dir, strerror(errno));
+			goto error;
+		}
+	}
+
+	/* import the TM auto-loading function */
+	if ( !(load_tm=(load_tm_f)find_export("load_tm", NO_SCRIPT, 0))) {
+		LOG(L_ERR, "ERROR:cpl_c:cpl_init: cannot import load_tm\n");
+		goto error;
+	}
+	/* let the auto-loading function load all TM stuff */
+	if (load_tm( &(cpl_fct.tmb) )==-1)
+		goto error;
+
+	/* bind the SL API */
+	if (sl_load_api(&cpl_fct.slb)!=0) {
+		LM_ERR("cannot bind to SL API\n");
+		return -1;
+	}
+
+	/* bind to usrloc module if requested */
+	if (lookup_domain) {
+		/* import all usrloc functions */
+		bind_usrloc = (bind_usrloc_t)find_export("ul_bind_usrloc", 1, 0);
+		if (!bind_usrloc) {
+			LOG(L_ERR, "ERROR:cpl_c:cpl_init: Can't bind usrloc\n");
+			goto error;
+		}
+		if (bind_usrloc( &(cpl_fct.ulb) ) < 0) {
+			LOG(L_ERR, "ERROR:cpl_c:cpl_init: importing usrloc failed\n");
+			goto error;
+		}
+		/* convert lookup_domain from char* to udomain_t* pointer */
+		if (cpl_fct.ulb.register_udomain( lookup_domain, &cpl_env.lu_domain)
+		< 0) {
+			LOG(L_ERR, "ERROR:cpl_c:cpl_init: Error while registering domain "
+				"<%s>\n",lookup_domain);
+			goto error;
+		}
+	} else {
+		LOG(L_NOTICE,"NOTICE:cpl_init: no lookup_domain given -> disable "
+			" lookup node\n");
+	}
+
+	/* build a pipe for sending commands to aux process */
+	if ( pipe( cpl_env.cmd_pipe )==-1 ) {
+		LOG(L_CRIT,"ERROR:cpl_init: cannot create command pipe: %s!\n",
+			strerror(errno) );
+		goto error;
+	}
+	/* set the writing non blocking */
+	if ( (val=fcntl(cpl_env.cmd_pipe[1], F_GETFL, 0))<0 ) {
+		LOG(L_ERR,"ERROR:cpl_init: getting flags from pipe[1] failed: fcntl "
+			"said %s!\n",strerror(errno));
+		goto error;
+	}
+	if ( fcntl(cpl_env.cmd_pipe[1], F_SETFL, val|O_NONBLOCK) ) {
+		LOG(L_ERR,"ERROR:cpl_init: setting flags to pipe[1] failed: fcntl "
+			"said %s!\n",strerror(errno));
+		goto error;
+	}
+
+	/* init the CPL parser */
+	if (init_CPL_parser( dtd_file )!=1 ) {
+		LOG(L_ERR,"ERROR:cpl_init: init_CPL_parser failed!\n");
+		goto error;
+	}
+
+	/* make a copy of the original TZ env. variable */
+	ptr = getenv("TZ");
+	cpl_env.orig_tz.len = 3/*"TZ="*/ + (ptr?(strlen(ptr)+1):0);
+	if ( (cpl_env.orig_tz.s=shm_malloc( cpl_env.orig_tz.len ))==0 ) {
+		LOG(L_ERR,"ERROR:cpl_init: no more shm mem. for saving TZ!\n");
+		goto error;
+	}
+	memcpy(cpl_env.orig_tz.s,"TZ=",3);
+	if (ptr)
+		strcpy(cpl_env.orig_tz.s+3,ptr);
+
+	/* convert realm_prefix from string null terminated to str */
+	if (cpl_env.realm_prefix.s) {
+		/* convert the realm_prefix to lower cases */
+		strlower( &cpl_env.realm_prefix );
+	}
+
+	/* Register a child process that will keep updating
+	 * its local configuration */
+	cfg_register_child(1);
+
+	return 0;
+error:
+	return -1;
+}
+
+
+
+static int cpl_child_init(int rank)
+{
+	pid_t pid;
+
+	/* don't do anything for main process and TCP manager process */
+	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0;
+
+	/* only child 1 will fork the aux process */
+	if (rank==1) {
+		pid = fork();
+		if (pid==-1) {
+			LOG(L_CRIT,"ERROR:cpl_child_init(%d): cannot fork: %s!\n",
+				rank, strerror(errno));
+			goto error;
+		} else if (pid==0) {
+			/* I'm the child */
+
+			/* initialize the config framework */
+			if (cfg_child_init()) goto error;
+
+			cpl_aux_process( cpl_env.cmd_pipe[0], cpl_env.log_dir);
+		} else {
+			LOG(L_INFO,"INFO:cpl_child_init(%d): I just gave birth to a child!"
+				" I'm a PARENT!!\n",rank);
+			/* I'm the parent -> remember the pid */
+			aux_process = pid;
+		}
+	}
+
+	return cpl_db_init(DB_URL, DB_TABLE);
+error:
+	return -1;
+}
+
+
+
+static int cpl_exit(void)
+{
+	/* free the TZ orig */
+	if (cpl_env.orig_tz.s)
+		shm_free(cpl_env.orig_tz.s);
+
+	/* if still running, stop the aux process */
+	if (!aux_process) {
+		LOG(L_INFO,"INFO:cpl_c:cpl_exit: aux process hasn't been created -> "
+			"nothing to kill :-(\n");
+	} else {
+		/* kill the auxiliary process */
+		if (kill( aux_process, SIGKILL)!=0) {
+			if (errno==ESRCH) {
+				LOG(L_INFO,"INFO:cpl_c:cpl_exit: seems that my child is "
+					"already dead! :-((\n");
+			} else {
+				LOG(L_ERR,"ERROR:cpl_c:cpl_exit: killing the aux. process "
+					"failed! kill said: %s\n",strerror(errno));
+				return -1;
+			}
+		} else {
+			LOG(L_INFO,"INFO:cl_c:cpl_exit: I have blood on my hands!! I just"
+				" killed my own child!");
+		}
+	}
+	return 0;
+}
+
+
+
+#define BUILD_UH_SHM      (1<<0)
+#define BUILD_UH_ADDSIP   (1<<1)
+
+static inline int build_userhost(struct sip_uri *uri, str *uh, int flg)
+{
+	static char buf[MAX_USERHOST_LEN];
+	unsigned char do_strip;
+	char *p;
+	int i;
+
+	/* do we need to strip realm prefix? */
+	do_strip = 0;
+	if (cpl_env.realm_prefix.len && cpl_env.realm_prefix.len<uri->host.len) {
+		for( i=cpl_env.realm_prefix.len-1 ; i>=0 ; i-- )
+			if ( cpl_env.realm_prefix.s[i]!=((uri->host.s[i])|(0x20)) )
+				break;
+		if (i==-1)
+			do_strip = 1;
+	}
+
+	/* calculate the len (without terminating \0) */
+	uh->len = 4*((flg&BUILD_UH_ADDSIP)!=0) + uri->user.len + 1 +
+		uri->host.len - do_strip*cpl_env.realm_prefix.len;
+	if (flg&BUILD_UH_SHM) {
+		uh->s = (char*)shm_malloc( uh->len + 1 );
+		if (!uh->s) {
+			LOG(L_ERR,"ERROR:cpl-c:build_userhost: no more shm memory.\n");
+			return -1;
+		}
+	} else {
+		uh->s = buf;
+		if ( uh->len > MAX_USERHOST_LEN ) {
+			LOG(L_ERR,"ERROR:cpl-c:build_userhost: user+host longer than %d\n",
+				MAX_USERHOST_LEN);
+			return -1;
+		}
+	}
+
+	/* build user@host */
+	p = uh->s;
+	if (flg&BUILD_UH_ADDSIP) {
+		memcpy( uh->s, "sip:", 4);
+		p += 4;
+	}
+	/* user part */
+	if (cpl_env.case_sensitive) {
+		memcpy( p, uri->user.s, uri->user.len);
+		p += uri->user.len;
+	} else {
+		for(i=0;i<uri->user.len;i++)
+			*(p++) = (0x20)|(uri->user.s[i]);
+	}
+	*(p++) = '@';
+	/* host part in lower cases */
+	for( i=do_strip*cpl_env.realm_prefix.len ; i< uri->host.len ; i++ )
+		*(p++) = (0x20)|(uri->host.s[i]);
+	*(p++) = 0;
+
+	/* sanity check */
+	if (p-uh->s!=uh->len+1) {
+		LOG(L_CRIT,"BUG:cpl-c:build_userhost: buffer overflow l=%d,w=%ld\n",
+			uh->len,(long)(p-uh->s));
+		return -1;
+	}
+	return 0;
+}
+
+
+
+static inline int get_dest_user(struct sip_msg *msg, str *uh, int flg)
+{
+	struct sip_uri uri;
+
+	/*  get the user_name from new_uri/RURI/To */
+	DBG("DEBUG:cpl-c:get_dest_user: trying to get user from new_uri\n");
+	if ( !msg->new_uri.s || parse_uri( msg->new_uri.s,msg->new_uri.len,&uri)==-1
+	|| !uri.user.len )
+	{
+		DBG("DEBUG:cpl-c:get_dest_user: trying to get user from R_uri\n");
+		if ( parse_uri( msg->first_line.u.request.uri.s,
+		msg->first_line.u.request.uri.len ,&uri)==-1 || !uri.user.len )
+		{
+			DBG("DEBUG:cpl-c:get_dest_user: trying to get user from To\n");
+			if ( (!msg->to&&( (parse_headers(msg,HDR_TO_F,0)==-1) ||
+					!msg->to)) ||
+				parse_uri( get_to(msg)->uri.s, get_to(msg)->uri.len, &uri)==-1
+				|| !uri.user.len)
+			{
+				LOG(L_ERR,"ERROR:cpl-c:get_dest_user: unable to extract user"
+					" name from RURI or To header!\n");
+				return -1;
+			}
+		}
+	}
+	return build_userhost( &uri, uh, flg);
+}
+
+
+
+static inline int get_orig_user(struct sip_msg *msg, str *uh, int flg)
+{
+	struct to_body *from;
+	struct sip_uri uri;
+
+	/* if it's outgoing -> get the user_name from From */
+	/* parsing from header */
+	DBG("DEBUG:cpl-c:get_orig_user: trying to get user from From\n");
+	if ( parse_from_header( msg )==-1 ) {
+		LOG(L_ERR,"ERROR:cpl-c:get_orig_user: unable to extract URI "
+			"from FROM header\n");
+		return -1;
+	}
+	from = (struct to_body*)msg->from->parsed;
+	/* parse the extracted uri from From */
+	if (parse_uri( from->uri.s, from->uri.len, &uri)||!uri.user.len) {
+		LOG(L_ERR,"ERROR:cpl-c:get_orig_user: unable to extract user name "
+			"from URI (From header)\n");
+		return -1;
+	}
+	return build_userhost( &uri, uh, flg);
+}
+
+
+
+/* Params:
+ *   str1 - as unsigned int - can be CPL_RUN_INCOMING or CPL_RUN_OUTGOING
+ *   str2 - as unsigned int - flags regarding state(less)|(ful)
+ */
+static int cpl_invoke_script(struct sip_msg* msg, char* str1, char* str2)
+{
+	struct cpl_interpreter  *cpl_intr;
+	str  user;
+	str  loc;
+	str  script;
+
+	/* get the user_name */
+	if ( ((unsigned long)str1)&CPL_RUN_INCOMING ) {
+		/* if it's incoming -> get the destination user name */
+		if (get_dest_user( msg, &user, BUILD_UH_SHM)==-1)
+			goto error0;
+	} else {
+		/* if it's outgoing -> get the origin user name */
+		if (get_orig_user( msg, &user, BUILD_UH_SHM)==-1)
+			goto error0;
+	}
+
+	/* get the script for this user */
+	if (get_user_script(&user, &script, 1)==-1)
+		goto error1;
+
+	/* has the user a non-empty script? if not, return normally, allowing ser to
+	 * continue its script */
+	if ( !script.s || !script.len ) {
+		shm_free(user.s);
+		return 1;
+	}
+
+	/* build a new script interpreter */
+	if ( (cpl_intr=new_cpl_interpreter(msg,&script))==0 )
+		goto error2;
+	/* set the flags */
+	cpl_intr->flags =(unsigned int)((unsigned long)str1)|((unsigned long)str2);
+	/* attache the user */
+	cpl_intr->user = user;
+	/* for OUTGOING we need also the destination user for init. with him
+	 * the location set */
+	if ( ((unsigned long)str1)&CPL_RUN_OUTGOING ) {
+		if (get_dest_user( msg, &loc,BUILD_UH_ADDSIP)==-1)
+			goto error3;
+		if (add_location( &(cpl_intr->loc_set), &loc,10,CPL_LOC_DUPL)==-1)
+			goto error3;
+	}
+
+	/* since the script interpretation can take some time, it will be better to
+	 * send a 100 back to prevent the UAC to retransmit
+	if ( cpl_tmb.t_reply( msg, (int)100, "Running cpl script" )!=1 ) {
+		LOG(L_ERR,"ERROR:cpl_invoke_script: unable to send 100 reply!\n");
+		goto error3;
+	}
+	* this should be done from script - it's much sooner ;-) */
+
+	/* run the script */
+	switch (cpl_run_script( cpl_intr )) {
+		case SCRIPT_DEFAULT:
+			free_cpl_interpreter( cpl_intr );
+			return 1; /* execution of ser's script will continue */
+		case SCRIPT_END:
+			free_cpl_interpreter( cpl_intr );
+		case SCRIPT_TO_BE_CONTINUED:
+			return 0; /* break the SER script */
+		case SCRIPT_RUN_ERROR:
+		case SCRIPT_FORMAT_ERROR:
+			goto error3;
+	}
+
+	return 1;
+error3:
+	free_cpl_interpreter( cpl_intr );
+	return -1;
+error2:
+	shm_free(script.s);
+error1:
+	shm_free(user.s);
+error0:
+	return -1;
+}
+
+
+
+#define CPL_SCRIPT          "script"
+#define CPL_SCRIPT_LEN      (sizeof(CPL_SCRIPT)-1)
+#define ACTION_PARAM        "action"
+#define ACTION_PARAM_LEN    (sizeof(ACTION_PARAM)-1)
+#define STORE_ACTION        "store"
+#define STORE_ACTION_LEN    (sizeof(STORE_ACTION)-1)
+#define REMOVE_ACTION       "remove"
+#define REMOVE_ACTION_LEN   (sizeof(REMOVE_ACTION)-1)
+
+#define REMOVE_SCRIPT       0xcaca
+#define STORE_SCRIPT        0xbebe
+
+#define CONTENT_TYPE_HDR      ("Content-Type: application/cpl-xml"CRLF)
+#define CONTENT_TYPE_HDR_LEN  (sizeof(CONTENT_TYPE_HDR)-1)
+
+struct cpl_error {
+	int   err_code;
+	char *err_msg;
+};
+
+static struct cpl_error bad_req = {400,"Bad request"};
+static struct cpl_error intern_err = {500,"Internal server error"};
+static struct cpl_error bad_cpl = {400,"Bad CPL script"};
+
+static struct cpl_error *cpl_err = &bad_req;
+
+
+static inline int do_script_action(struct sip_msg *msg, int action)
+{
+	str  body = STR_NULL;
+	str  user = STR_NULL;
+	str  bin  = STR_NULL;
+	str  log  = STR_NULL;
+
+	/* content-length (if present) */
+	if ( !msg->content_length &&
+			((parse_headers(msg, HDR_CONTENTLENGTH_F, 0)==-1)
+			 || !msg->content_length) )
+	{
+		LOG(L_ERR,"ERROR:cpl-c:do_script_action: no Content-Length "
+			"hdr found!\n");
+		goto error;
+	}
+	body.len = get_content_length( msg );
+
+	/* get the user name */
+	if (get_dest_user( msg, &user, 0)==-1)
+		goto error;
+
+	/* we have the script and the user */
+	switch (action) {
+		case STORE_SCRIPT :
+			/* check the len -> it must not be 0 */
+			if (body.len==0) {
+				LOG(L_ERR,"ERROR:cpl-c:do_script_action: 0 content-len found "
+					"for store\n");
+				goto error_1;
+			}
+			/* get the message's body */
+			body.s = get_body( msg );
+			if (body.s==0) {
+				LOG(L_ERR,"ERROR:cpl-c:do_script_action: cannot extract "
+					"body from msg!\n");
+				goto error_1;
+			}
+			/* now compile the script and place it into database */
+			/* get the binary coding for the XML file */
+			if ( encodeCPL( &body, &bin, &log)!=1) {
+				cpl_err = &bad_cpl;
+				goto error_1;
+			}
+
+			/* write both the XML and binary formats into database */
+			if (write_to_db(user.s, &body, &bin)!=1) {
+				cpl_err = &intern_err;
+				goto error_1;
+			}
+			break;
+		case REMOVE_SCRIPT:
+			/* check the len -> it must be 0 */
+			if (body.len!=0) {
+				LOG(L_ERR,"ERROR:cpl-c:do_script_action: non-0 content-len "
+					"found for remove\n");
+				goto error_1;
+			}
+			/* remove the script for the user */
+			if (rmv_from_db(user.s)!=1) {
+				cpl_err = &intern_err;
+				goto error_1;
+			}
+			break;
+	}
+
+	if (log.s) pkg_free( log.s );
+	return 0;
+error_1:
+	if (log.s) pkg_free( log.s );
+error:
+	return -1;
+}
+
+
+
+static inline int do_script_download(struct sip_msg *msg)
+{
+	str  user  = STR_NULL;
+	str script = STR_NULL;
+
+	/* get the destination user name */
+	if (get_dest_user( msg, &user, 0)==-1)
+		goto error;
+
+	/* get the user's xml script from the database */
+	if (get_user_script(&user, &script, 0)==-1)
+		goto error;
+
+	/* add a lump with content-type hdr */
+	if (add_lump_rpl( msg, CONTENT_TYPE_HDR, CONTENT_TYPE_HDR_LEN,
+	LUMP_RPL_HDR)==0) {
+		LOG(L_ERR,"ERROR:cpl-c:do_script_download: cannot build hdr lump\n");
+		cpl_err = &intern_err;
+		goto error;
+	}
+
+	if (script.s!=0) {
+		/*DBG("script len=%d\n--------\n%.*s\n--------\n",
+			script.len, script.len, script.s);*/
+		/* user has a script -> add a body lump */
+		if ( add_lump_rpl( msg, script.s, script.len, LUMP_RPL_BODY)==0) {
+			LOG(L_ERR,"ERROR:cpl-c:do_script_download: cannot build "
+				"body lump\n");
+			cpl_err = &intern_err;
+			goto error;
+		}
+		/* build_lump_rpl duplicates the added text, so free the original */
+		shm_free( script.s );
+	}
+
+	return 0;
+error:
+	if (script.s)
+		shm_free(script.s);
+	return -1;
+}
+
+
+
+static int w_process_register(struct sip_msg* msg, char* str, char* str2)
+{
+	return cpl_process_register( msg, 0);
+}
+
+
+
+static int w_process_register_norpl(struct sip_msg* msg, char* str,char* str2)
+{
+	return cpl_process_register( msg, 1);
+}
+
+
+
+static int cpl_process_register(struct sip_msg* msg, int no_rpl)
+{
+	struct disposition *disp;
+	struct disposition_param *param;
+	int  ret;
+	int  mime;
+	int  *mimes;
+
+	/* make sure that is a REGISTER ??? */
+
+	/* here should be the CONTACT- hack */
+
+	/* is there a CONTENT-TYPE hdr ? */
+	mime = parse_content_type_hdr( msg );
+	if (mime==-1)
+		goto error;
+
+	/* check the mime type */
+	DBG("DEBUG:cpl_process_register: Content-Type mime found %u, %u\n",
+		mime>>16,mime&0x00ff);
+	if ( mime && mime==(TYPE_APPLICATION<<16)+SUBTYPE_CPLXML ) {
+		/* can be an upload or remove -> check for the content-purpose and
+		 * content-action headers */
+		DBG("DEBUG:cpl_process_register: carrying CPL -> look at "
+			"Content-Disposition\n");
+		if (parse_content_disposition( msg )!=0) {
+			LOG(L_ERR,"ERROR:cpl_process_register: Content-Disposition missing "
+				"or corrupted\n");
+			goto error;
+		}
+		disp = get_content_disposition(msg);
+		print_disposition( disp ); /* just for DEBUG */
+		/* check if the type of disposition is SCRIPT */
+		if (disp->type.len!=CPL_SCRIPT_LEN ||
+		strncasecmp(disp->type.s,CPL_SCRIPT,CPL_SCRIPT_LEN) ) {
+			LOG(L_ERR,"ERROR:cpl_process_register: bogus message - Content-Type"
+				"says CPL_SCRIPT, but Content-Disposition something else\n");
+			goto error;
+		}
+		/* disposition type is OK -> look for action parameter */
+		for(param=disp->params;param;param=param->next) {
+			if (param->name.len==ACTION_PARAM_LEN &&
+			!strncasecmp(param->name.s,ACTION_PARAM,ACTION_PARAM_LEN))
+				break;
+		}
+		if (param==0) {
+			LOG(L_ERR,"ERROR:cpl_process_register: bogus message - "
+				"Content-Disposition has no action param\n");
+			goto error;
+		}
+		/* action param found -> check its value: store or remove */
+		if (param->body.len==STORE_ACTION_LEN &&
+		!strncasecmp( param->body.s, STORE_ACTION, STORE_ACTION_LEN)) {
+			/* it's a store action -> get the script from body message and store
+			 * it into database (CPL and BINARY format) */
+			if (do_script_action( msg, STORE_SCRIPT)==-1)
+				goto error;
+		} else
+		if (param->body.len==REMOVE_ACTION_LEN &&
+		!strncasecmp( param->body.s, REMOVE_ACTION, REMOVE_ACTION_LEN)) {
+			/* it's a remove action -> remove the script from database */
+			if (do_script_action( msg, REMOVE_SCRIPT)==-1)
+				goto error;
+		} else {
+			LOG(L_ERR,"ERROR:cpl_process_register: unknown action <%.*s>\n",
+				param->body.len,param->body.s);
+			goto error;
+		}
+
+		/* do I have to send to reply? */
+		if (no_rpl)
+			goto resume_script;
+
+		/* send a 200 OK reply back */
+		cpl_fct.slb.zreply( msg, 200, "OK");
+		/* I send the reply and I don't want to return to script execution, so
+		 * I return 0 to do break */
+		goto stop_script;
+	}
+
+	/* is there an ACCEPT hdr ? */
+	if ( (ret=parse_accept_hdr(msg))==-1)
+		goto error;
+	if (ret==0 || (mimes=get_accept(msg))==0 )
+		/* accept header not present or no mimes found */
+		goto resume_script;
+
+	/* looks if the REGISTER accepts cpl-xml or * */
+	while (*mimes) {
+		DBG("DEBUG: accept mime found %u, %u\n",
+			(*mimes)>>16,(*mimes)&0x00ff);
+		if (*mimes==(TYPE_ALL<<16)+SUBTYPE_ALL ||
+		*mimes==(TYPE_APPLICATION<<16)+SUBTYPE_CPLXML )
+			break;
+		mimes++;
+	}
+	if (*mimes==0)
+		/* no accept mime that matched cpl */
+		goto resume_script;
+
+	/* get the user name from msg, retrieve the script from db
+	 * and appended to reply */
+	if (do_script_download( msg )==-1)
+		goto error;
+
+	/* do I have to send to reply? */
+	if (no_rpl)
+		goto resume_script;
+
+	/* send a 200 OK reply back */
+	cpl_fct.slb.zreply( msg, 200, "OK");
+
+stop_script:
+	return 0;
+resume_script:
+	return 1;
+error:
+	/* send a error reply back */
+	cpl_fct.slb.zreply( msg, cpl_err->err_code, cpl_err->err_msg);
+	/* I don't want to return to script execution, so I return 0 to do break */
+	return 0;
+}
+
+
+

+ 206 - 0
cpl-c/cpl_db.c

@@ -0,0 +1,206 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+ /*
+  * History:
+  * --------
+  *  2004-06-06  updated to the new DB api (andrei)
+  */
+
+#include "../../mem/shm_mem.h"
+#include "../../lib/srdb2/db.h"
+#include "../../dprint.h"
+#include "cpl_db.h"
+
+static db_ctx_t* ctx = NULL;
+static db_cmd_t* get_script;
+static db_cmd_t* write_script;
+static db_cmd_t* delete_user;
+
+
+void cpl_db_close()
+{
+	if (delete_user) db_cmd_free(delete_user);
+	delete_user = NULL;
+
+	if (write_script) db_cmd_free(write_script);
+	write_script = NULL;
+
+	if (get_script) db_cmd_free(get_script);
+	get_script = NULL;
+
+	if (ctx) {
+		db_disconnect(ctx);
+		db_ctx_free(ctx);
+		ctx = NULL;
+	}
+}
+
+
+int cpl_db_init(char* db_url, char* db_table)
+{
+	db_fld_t cols[] = {
+		{.name = "cpl_bin", .type = DB_BLOB},
+		{.name = "cpl_xml", .type = DB_STR},
+		{.name = 0}
+	};
+
+	db_fld_t match[] = {
+		{.name = "uid", .type = DB_CSTR},
+		{.name = 0}
+	};
+
+	db_fld_t vals[] = {
+		{.name = "uid",     .type = DB_CSTR},
+		{.name = "cpl_bin", .type = DB_BLOB},
+		{.name = "cpl_xml", .type = DB_STR },
+		{.name = 0}
+	};
+
+	ctx = db_ctx("cpl-c");
+	if (ctx == NULL) goto error;
+
+	if (db_add_db(ctx, db_url) < 0) goto error;
+	if (db_connect(ctx) < 0) goto error;
+
+	get_script = db_cmd(DB_GET, ctx, db_table, cols, match, NULL);
+	if (!get_script) goto error;
+
+	write_script = db_cmd(DB_PUT, ctx, db_table, NULL, NULL, vals);
+	if (!write_script) goto error;
+
+	delete_user = db_cmd(DB_DEL, ctx, db_table, NULL, match, NULL);
+	if (!delete_user) goto error;
+
+	return 0;
+error:
+	ERR("cpl-c: Error while initializing db layer\n");
+	cpl_db_close();
+	return -1;
+}
+
+
+/* gets from database the cpl script in binary format; the returned script is
+ * allocated in shared memory
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int get_user_script(str *user, str *script, int bin)
+{
+	db_res_t* res = 0;
+	db_rec_t* rec;
+	int i;
+
+	if (bin) i = 0;
+	else i = 1;
+
+	get_script->match[0].v.cstr = user->s;
+
+	DBG("DEBUG:get_user_script: fetching script for user <%s>\n",user->s);
+	if (db_exec(&res, get_script) < 0) {
+		LOG(L_ERR,"ERROR:cpl-c:get_user_script: db_query failed\n");
+		goto error;
+	}
+
+	if (!res || !(rec = db_first(res))) {
+		DBG("DEBUG:get_user_script: user <%.*s> not found in db -> probably "
+			"he has no script\n",user->len, user->s);
+		script->s = 0;
+		script->len = 0;
+	} else {
+		if (rec->fld[i].flags & DB_NULL) {
+			DBG("DEBUG:get_user_script: user <%.*s> has a NULL script\n",
+				user->len, user->s);
+			script->s = 0;
+			script->len = 0;
+		} else {
+			DBG("DEBUG:get_user_script: we got the script len=%d\n",
+				rec->fld[i].v.blob.len);
+			script->len = rec->fld[i].v.blob.len;
+			script->s = shm_malloc( script->len );
+			if (!script->s) {
+				LOG(L_ERR,"ERROR:cpl-c:get_user_script: no free sh_mem\n");
+				goto error;
+			}
+			memcpy( script->s, rec->fld[i].v.blob.s,
+				script->len);
+		}
+	}
+
+	if (res) db_res_free(res);
+	return 1;
+error:
+	if (res)
+		db_res_free(res);
+	script->s = 0;
+	script->len = 0;
+	return -1;
+}
+
+
+
+/* inserts into database a cpl script in XML format(xml) along with its binary
+ * format (bin)
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int write_to_db(char *usr, str *xml, str *bin)
+{
+	write_script->vals[0].v.cstr = usr;
+	write_script->vals[1].v.blob = *bin;
+	write_script->vals[2].v.lstr = *xml;
+
+	/* No need to do update/insert here, the db layer does that
+	 * automatically without the need to query the db
+	 */
+	if (db_exec(NULL, write_script) < 0) {
+		ERR("cpl-c: Error while writing script into database\n");
+		return -1;
+	}
+	return 0;
+}
+
+
+
+/* delete from database the entity record for a given user - if a user has no
+ * script, he will be removed completely from db; users without script are not
+ * allowed into db ;-)
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int rmv_from_db(char *usr)
+{
+	delete_user->match[0].v.cstr = usr;
+	
+	if (db_exec(NULL, delete_user) < 0) {
+		LOG(L_ERR,"ERROR:cpl-c:rmv_from_db: error when deleting script for "
+			"user \"%s\"\n",usr);
+		return -1;
+	}
+
+	return 1;
+}
+

+ 63 - 0
cpl-c/cpl_db.h

@@ -0,0 +1,63 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _CPL_DB_H
+#define _CPL_DB_H
+
+#include "../../lib/srdb2/db.h"
+
+
+int cpl_db_bind(char* db_url);
+int cpl_db_init(char* db_url, char* db_table);
+void cpl_db_close();
+
+
+/* inserts into database a cpl script in XML format(xml) along with its binary
+ * format (bin)
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int write_to_db( char *usr, str *xml, str *bin);
+
+
+/* fetch from database the binary format of the cpl script for a given user
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int get_user_script(str *user, str *script, int bin);
+
+
+/* delete from database the entire record for a given user - if a user has no
+ * script, he will be removed completely from db; users without script are not
+ * allowed into db ;-)
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int rmv_from_db(char *usr);
+
+
+#endif

+ 69 - 0
cpl-c/cpl_env.h

@@ -0,0 +1,69 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History
+ * 11-06-1004:  created (bogdan)
+ */
+
+#ifndef _CPL_C_ENV_H
+#define _CPL_C_ENV_H
+
+#include "../../str.h"
+#include "../../usr_avp.h"
+#include "../../modules/sl/sl.h"
+#include "../usrloc/usrloc.h"
+#include "../../modules/tm/tm_load.h"
+
+struct cpl_enviroment {
+	char  *log_dir;         /* dir where the user log should be dumped */
+	int    proxy_recurse;   /* numbers of proxy redirection accepted */
+	int    proxy_route;     /* script route to be run before proxy */
+	int    nat_flag;        /* flag for marking looked up contact as NAT */
+	int    case_sensitive;  /* is user part case sensitive ? */
+	str    realm_prefix;    /* domain prefix to be ignored */
+	int    cmd_pipe[2];     /* communication pipe with aux. process */
+	str    orig_tz;         /* a copy of the original TZ; kept as a null
+                             * terminated string in "TZ=value" format;
+                             * used only by run_time_switch */
+	udomain_t*  lu_domain;  /* domain used for lookup */
+	int lu_append_branches; /* how many branches lookup should add */
+	int timer_avp_type;     /* specs - type and name - of the timer AVP */
+	int_str timer_avp;
+};
+
+
+struct cpl_functions {
+	struct tm_binds tmb;     /* Structure with pointers to tm funcs */
+	usrloc_api_t ulb;        /* Structure with pointers to usrloc funcs */
+	sl_api_t slb;            /* sl module functions */
+};
+
+extern struct cpl_enviroment cpl_env;
+extern struct cpl_functions  cpl_fct;
+
+#endif
+
+

+ 234 - 0
cpl-c/cpl_loader.c

@@ -0,0 +1,234 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-08-21: cpl_remove() added (bogdan)
+ * 2003-06-24: file created (bogdan)
+ */
+
+
+#include <stdio.h>
+#include <sys/uio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include "../../str.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "cpl_db.h"
+#include "cpl_parser.h"
+#include "cpl_loader.h"
+
+
+#define MAX_STATIC_BUF 256
+
+extern db_con_t* db_hdl;
+
+
+
+#if 0
+/* debug function -> write into a file the content of a str struct. */
+int write_to_file(char *filename, str *buf)
+{
+	int fd;
+	int ret;
+
+	fd = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0644);
+	if (!fd) {
+		LOG(L_ERR,"ERROR:cpl-c:write_to_file: cannot open file : %s\n",
+			strerror(errno));
+		goto error;
+	}
+
+	while ( (ret=write( fd, buf->s, buf->len))!=buf->len) {
+		if ((ret==-1 && errno!=EINTR)|| ret!=-1) {
+			LOG(L_ERR,"ERROR:cpl-c:write_to_file:cannot write to file:"
+				"%s write_ret=%d\n",strerror(errno), ret );
+			goto error;
+		}
+	}
+	close(fd);
+
+	return 0;
+error:
+	return -1;
+}
+#endif
+
+
+
+/* Loads a file into a buffer; first the file length will be determined for
+ * allocated an exact buffer len for storing the file content into.
+ * Returns:  1 - success
+ *          -1 - error
+ */
+int load_file( char *filename, str *xml)
+{
+	int n;
+	int offset;
+	int fd;
+
+	xml->s = 0;
+	xml->len = 0;
+
+	/* open the file for reading */
+	fd = open(filename,O_RDONLY);
+	if (fd==-1) {
+		LOG(L_ERR,"ERROR:cpl-c:load_file: cannot open file for reading:"
+			" %s\n",strerror(errno));
+		goto error;
+	}
+
+	/* get the file length */
+	if ( (xml->len=lseek(fd,0,SEEK_END))==-1) {
+		LOG(L_ERR,"ERROR:cpl-c:load_file: cannot get file length (lseek):"
+			" %s\n", strerror(errno));
+		goto error;
+	}
+	DBG("DEBUG:cpl-c:load_file: file size = %d\n",xml->len);
+	if ( lseek(fd,0,SEEK_SET)==-1 ) {
+		LOG(L_ERR,"ERROR:cpl-c:load_file: cannot go to beginning (lseek):"
+			" %s\n",strerror(errno));
+		goto error;
+	}
+
+	/* get some memory */
+	xml->s = (char*)pkg_malloc( xml->len+1/*null terminated*/ );
+	if (!xml->s) {
+		LOG(L_ERR,"ERROR:cpl-c:load_file: no more free pkg memory\n");
+		goto error;
+	}
+
+	/*start reading */
+	offset = 0;
+	while ( offset<xml->len ) {
+		n=read( fd, xml->s+offset, xml->len-offset);
+		if (n==-1) {
+			if (errno!=EINTR) {
+				LOG(L_ERR,"ERROR:cpl-c:load_file: read failed:"
+					" %s\n", strerror(errno));
+				goto error;
+			}
+		} else {
+			if (n==0) break;
+			offset += n;
+		}
+	}
+	if (xml->len!=offset) {
+		LOG(L_ERR,"ERROR:cpl-c:load_file: couldn't read all file!\n");
+		goto error;
+	}
+	xml->s[xml->len] = 0;
+
+	close(fd);
+	return 1;
+error:
+	if (fd!=-1) close(fd);
+	if (xml->s) pkg_free( xml->s);
+	return -1;
+}
+
+
+
+/* Writes an array of texts into the given response file.
+ * Accepts also empty texts, case in which it will be created an empty
+ * response file.
+ */
+void write_to_file( char *file, str *txt, int n )
+{
+	int fd;
+
+	/* open file for write */
+	fd = open( file, O_WRONLY|O_CREAT|O_TRUNC/*|O_NOFOLLOW*/, 0600 );
+	if (fd==-1) {
+		LOG(L_ERR,"ERROR:cpl-c:write_to_file: cannot open response file "
+			"<%s>: %s\n", file, strerror(errno));
+		return;
+	}
+
+	/* write the txt, if any */
+	if (n>0) {
+again:
+		if ( writev( fd, (struct iovec*)txt, n)==-1) {
+			if (errno==EINTR) {
+				goto again;
+			} else {
+				LOG(L_ERR,"ERROR:cpl-c:write_logs_to_file: writev failed: "
+					"%s\n", strerror(errno) );
+			}
+		}
+	}
+
+	/* close the file*/
+	close( fd );
+	return;
+}
+
+
+
+static inline int check_userhost( char *p, char *end)
+{
+	char *p1;
+	int  dot;
+
+	/* parse user name */
+	p1 = p;
+	while (p<end && (isalnum((int)*p) || *p=='-' || *p=='_' || *p=='.' ))
+		p++;
+	if (p==p1 || p==end || *p!='@')
+		return -1;
+	p++;
+	/* parse the host part */
+	dot = 1;
+	p1 = p;
+	while (p<end) {
+		if (*p=='.') {
+			if (dot) return -1; /* dot after dot */
+			dot = 1;
+		} else if (isalnum((int)*p) || *p=='-' || *p=='_' ) {
+			dot = 0;
+		} else {
+			return -1;
+		}
+		p++;
+	}
+	if (p1==p || dot)
+		return -1;
+
+	return 0;
+}
+
+#undef MAX_STATIC_BUF
+

+ 45 - 0
cpl-c/cpl_loader.h

@@ -0,0 +1,45 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-06-24: file created (bogdan)
+ */
+
+#ifndef _CPL_LOADER_H
+#define _CPL_LOADER_H
+
+#include "../../str.h"
+
+int load_file( char *filename, str *xml);
+
+#endif
+
+
+
+
+

+ 108 - 0
cpl-c/cpl_log.c

@@ -0,0 +1,108 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-09-22: created (bogdan)
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "cpl_log.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+
+
+static str  cpl_logs[MAX_LOG_NR];
+static int  nr_logs;
+
+
+void reset_logs()
+{
+	nr_logs = 0;
+}
+
+
+
+void append_log( int nr, ...)
+{
+	va_list ap;
+	int     i;
+
+
+	if ( nr_logs+nr>MAX_LOG_NR ) {
+		LOG(L_ERR,"ERROR:cpl-c:append_log: no more space fr logging\n");
+		return;
+	}
+
+	va_start(ap, nr);
+
+	for(i=0;i<nr;i++,nr_logs++) {
+		cpl_logs[nr_logs].s   = va_arg(ap, char *);
+		cpl_logs[nr_logs].len = va_arg(ap, int );
+	}
+
+	va_end(ap);
+}
+
+
+
+void compile_logs( str *log)
+{
+	int i;
+	char *p;
+
+	log->s = 0;
+	log->len = 0;
+
+	if (nr_logs==0)
+		/* no logs */
+		return;
+
+	/* compile the total len */
+	for(i=0;i<nr_logs;i++)
+		log->len += cpl_logs[i].len;
+
+	/* get a buffer */
+	log->s = (char*)pkg_malloc(log->len);
+	if (log->s==0) {
+		LOG(L_ERR,"ERROR:cpl-c:compile_logs: no more pkg mem\n");
+		log->len = 0;
+		return;
+	}
+
+	/*copy all logs into buffer */
+	p = log->s;
+	for(i=0;i<nr_logs;i++) {
+		memcpy( p, cpl_logs[i].s, cpl_logs[i].len);
+		p += cpl_logs[i].len;
+	}
+
+	return;
+}
+

+ 62 - 0
cpl-c/cpl_log.h

@@ -0,0 +1,62 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-09-22: created (bogdan)
+ *
+ */
+
+
+#ifndef _CPL_LOG_H_
+#define _CPL_LOG_H_
+
+#include <stdarg.h>
+#include "../../str.h"
+
+
+#define MAX_LOG_NR    64
+
+#define MSG_ERR     "Error: "
+#define MSG_ERR_LEN (sizeof(MSG_ERR)-1)
+#define MSG_WARN    "Warning: "
+#define MSG_WARN_LEN (sizeof(MSG_WARN)-1)
+#define MSG_NOTE     "Notice: "
+#define MSG_NOTE_LEN (sizeof(MSG_NOTE)-1)
+
+#define LF       "\n"
+#define LF_LEN   (1)
+
+
+void reset_logs();
+
+void append_log( int nr, ...);
+
+void compile_logs( str *log);
+
+#endif
+

+ 281 - 0
cpl-c/cpl_nonsig.c

@@ -0,0 +1,281 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-06-27: file created (bogdan)
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/uio.h>
+#include <signal.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../cfg/cfg_struct.h"
+#include "cpl_nonsig.h"
+#include "CPL_tree.h"
+
+
+#define MAX_LOG_FILE_NAME      32
+#define MAX_FD                 32
+
+#define FILE_NAME_SUFIX        ".log"
+#define FILE_NAME_SUFIX_LEN    (sizeof(FILE_NAME_SUFIX)-1)
+
+#define LOG_SEPARATOR          ": "
+#define LOG_SEPARATOR_LEN      (sizeof(LOG_SEPARATOR)-1)
+
+#define DEFAULT_LOG_NAME       "default_log"
+#define DEFAULT_LOG_NAME_LEN   (sizeof(DEFAULT_LOG_NAME)-1)
+
+#define LOG_TERMINATOR          "\n"
+#define LOG_TERMINATOR_LEN      (sizeof(LOG_TERMINATOR)-1)
+
+
+static char file[MAX_LOG_DIR_SIZE+1+MAX_LOG_FILE_NAME+FILE_NAME_SUFIX_LEN+1];
+static char *file_ptr;
+
+
+static inline void write_log( struct cpl_cmd *cmd)
+{
+	struct iovec  wr_vec[5];
+	time_t now;
+	char *time_ptr;
+	int fd;
+	int ret;
+
+	/* build file name (cmd->s1 is the user name)*/
+	if (cmd->s1.len>MAX_LOG_FILE_NAME)
+		cmd->s1.len = MAX_LOG_FILE_NAME;
+	memcpy(file_ptr, cmd->s1.s, cmd->s1.len );
+	memcpy(file_ptr+cmd->s1.len,FILE_NAME_SUFIX,FILE_NAME_SUFIX_LEN);
+	file_ptr[cmd->s1.len+FILE_NAME_SUFIX_LEN] = 0;
+
+	/* get current date+time -> wr_vec[0] */
+	time( &now );
+	time_ptr = ctime( &now );
+	wr_vec[0].iov_base = time_ptr;
+	wr_vec[0].iov_len = strlen( time_ptr );
+	/* ctime_r adds a \n at the end -> overwrite it with space */
+	time_ptr[ wr_vec[0].iov_len-1 ] = ' ';
+
+	/* log name (cmd->s2) ->  wr_vec[1] */
+	if (cmd->s2.s==0 || cmd->s2.len==0) {
+		wr_vec[1].iov_base = DEFAULT_LOG_NAME;
+		wr_vec[1].iov_len = DEFAULT_LOG_NAME_LEN;
+	} else {
+		wr_vec[1].iov_base = cmd->s2.s;
+		wr_vec[1].iov_len = cmd->s2.len;
+	}
+
+	/* log separator ->  wr_vec[2] */
+	wr_vec[2].iov_base = LOG_SEPARATOR;
+	wr_vec[2].iov_len = LOG_SEPARATOR_LEN;
+
+	/* log comment (cmd->s3) ->  wr_vec[3] */
+	wr_vec[3].iov_base = cmd->s3.s;
+	wr_vec[3].iov_len = cmd->s3.len;
+
+	/* log terminator ->  wr_vec[2] */
+	wr_vec[4].iov_base = LOG_TERMINATOR;
+	wr_vec[4].iov_len = LOG_TERMINATOR_LEN;
+
+	/* [create+]open the file */
+	fd = open( file, O_CREAT|O_APPEND|O_WRONLY, 0664);
+	if (fd==-1) {
+		LOG(L_ERR,"ERROR:cpl_c:write_log: cannot open file [%s] : %s\n",
+			file, strerror(errno) );
+		return;
+	}
+	/* get the log */
+	DBG("DEBUG:cpl_c:write_log: logging into [%s]... \n",file);
+	/* I'm really not interested in the return code for write ;-) */
+	while ( (ret=writev( fd, wr_vec, 5))==-1 ) {
+		if (errno==EINTR)
+			continue;
+		LOG(L_ERR,"ERROR:cpl_c:write_log: writing to log file [%s] : %s\n",
+			file, strerror(errno) );
+	}
+	close (fd);
+
+	shm_free( cmd->s1.s );
+}
+
+
+
+static inline void send_mail( struct cpl_cmd *cmd)
+{
+	char *argv[5];
+	int pfd[2];
+	pid_t  pid;
+	int i;
+
+	if (pipe(pfd) < 0) {
+		LOG(L_ERR,"ERROR:cpl_c:send_mail: pipe failed: %s\n",strerror(errno));
+		return;
+	}
+
+	/* even if I haven't fork yet, I push the date on the pipe just to get
+	 * rid of one more malloc + copy */
+	if (cmd->s3.len && cmd->s3.s) {
+		if ( (i=write( pfd[1], cmd->s3.s, cmd->s3.len ))!=cmd->s3.len ) {
+			LOG(L_ERR,"ERROR:cpl_c:send_mail: write returned error %s\n",
+				strerror(errno));
+			goto error;
+		}
+	}
+
+	if ( (pid = fork()) < 0) {
+		LOG(L_ERR,"ERROR:cpl_c:send_mail: fork failed: %s\n",strerror(errno));
+		goto error;
+	} else if (pid==0) {
+		/* child -> close all descriptors excepting pfd[0] */
+		for (i=3; i < MAX_FD; i++)
+			if (i!=pfd[0])
+				close(i);
+		if (pfd[0] != STDIN_FILENO) {
+			dup2(pfd[0], STDIN_FILENO);
+			close(pfd[0]);
+		}
+
+		/* set the argument vector*/
+		argv[0] = "mail";
+		argv[1] = "-s";
+		if (cmd->s2.s && cmd->s2.len) {
+			/* put the subject in this format : <"$subject"\0> */
+			if ( (argv[2]=(char*)pkg_malloc(1+cmd->s2.len+1+1))==0) {
+				LOG(L_ERR,"ERROR:cpl_c:send_mail: cannot get pkg memory\n");
+				goto child_exit;
+			}
+			argv[2][0] = '\"';
+			memcpy(argv[2]+1,cmd->s2.s,cmd->s2.len);
+			argv[2][cmd->s2.len+1] = '\"';
+			argv[2][cmd->s2.len+2] = 0;
+		} else {
+			argv[2] = "\"[CPL notification]\"";
+		}
+		/* put the TO in <$to\0> format*/
+		if ( (argv[3]=(char*)pkg_malloc(cmd->s1.len+1))==0) {
+			LOG(L_ERR,"ERROR:cpl_c:send_mail: cannot get pkg memory\n");
+			goto child_exit;
+		}
+		memcpy(argv[3],cmd->s1.s,cmd->s1.len);
+		argv[3][cmd->s1.len] = 0;
+		/* last element in vector mist be a null pointer */
+		argv[4] = (char*)0;
+		/* just debug */
+		for(i=0;i<sizeof(argv)/sizeof(char*);i++)
+			DBG(" argv[%d] = %s\n",i,argv[i]);
+		/* once I copy localy all the data from shm mem -> free the shm */
+		shm_free( cmd->s1.s );
+
+		/* set an alarm -> sending the email shouldn't take more than 10 sec */
+		alarm(10);
+		/* run the external mailer */
+		DBG("DEBUG:cpl_c:send_mail: new forked process created -> "
+			"doing execv..\n");
+		execv("/usr/bin/mail",argv);
+		/* if we got here means execv exit with error :-( */
+		LOG(L_ERR,"ERROR:cpl_c:send_mail: execv failed! (%s)\n",
+			strerror(errno));
+child_exit:
+		_exit(127);
+	}
+
+	/* parent -> close both ends of pipe */
+	close(pfd[0]);
+	close(pfd[1]);
+	return;
+error:
+	shm_free( cmd->s1.s );
+	close(pfd[0]);
+	close(pfd[1]);
+	return;
+}
+
+
+
+
+void cpl_aux_process( int cmd_out, char *log_dir)
+{
+	struct cpl_cmd cmd;
+	int len;
+
+	/* this process will ignore SIGCHLD signal */
+	if (signal( SIGCHLD, SIG_IGN)==SIG_ERR) {
+		LOG(L_ERR,"ERROR:cpl_c:cpl_aux_process: cannot set to IGNORE "
+			"SIGCHLD signal\n");
+	}
+
+	/* set the path for logging */
+	if (log_dir) {
+		strcpy( file, log_dir);
+		file_ptr = file + strlen(log_dir);
+		*(file_ptr++) = '/';
+	}
+
+	while(1) {
+		/* let's read a command from pipe */
+		len = read( cmd_out, &cmd, sizeof(struct cpl_cmd));
+		if (len!=sizeof(struct cpl_cmd)) {
+			if (len>=0) {
+				LOG(L_ERR,"ERROR:cpl_aux_processes: truncated message"
+					" read from pipe! -> discarded\n");
+			} else if (errno!=EAGAIN) {
+				LOG(L_ERR,"ERROR:cpl_aux_process: pipe reading failed: "
+					" : %s\n",strerror(errno));
+			}
+			sleep(1);
+			continue;
+		}
+
+		/* update the local config */
+		cfg_update();
+
+		/* process the command*/
+		switch (cmd.code) {
+			case CPL_LOG_CMD:
+				write_log( &cmd );
+				break;
+			case CPL_MAIL_CMD:
+				send_mail( &cmd );
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_aux_process: unknown command (%d) "
+					"received! -> ignoring\n",cmd.code);
+		} /* end switch*/
+
+	}
+}
+

+ 73 - 0
cpl-c/cpl_nonsig.h

@@ -0,0 +1,73 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-06-27: file created (bogdan)
+ */
+
+#ifndef _CPL_NONSIG_H_
+#define _CPL_NONSIG_H_
+
+#include <unistd.h>
+
+#include "../../str.h"
+#include "cpl_env.h"
+
+struct cpl_cmd {
+	unsigned int code;
+	str s1;
+	str s2;
+	str s3;
+};
+
+
+#define CPL_LOG_CMD    1
+#define CPL_MAIL_CMD   2
+
+#define MAX_LOG_DIR_SIZE    256
+
+
+void cpl_aux_process( int cmd_out, char *log_dir);
+
+
+static inline void write_cpl_cmd(unsigned int code, str *s1, str *s2, str *s3)
+{
+	static struct cpl_cmd cmd;
+
+	cmd.code = code;
+	cmd.s1 = *s1;
+	cmd.s2 = *s2;
+	cmd.s3 = *s3;
+
+	if (write( cpl_env.cmd_pipe[1], &cmd, sizeof(struct cpl_cmd) )==-1)
+		LOG(L_ERR,"ERROR:cpl_c:write_cpl_cmd: write ret: %s\n",
+			strerror(errno));
+}
+
+
+
+#endif

+ 1605 - 0
cpl-c/cpl_parser.c

@@ -0,0 +1,1605 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/parser.h>
+
+#include "../../parser/parse_uri.h"
+#include "../../dprint.h"
+#include "../../str.h"
+#include "../../ut.h"
+#include "CPL_tree.h"
+#include "sub_list.h"
+#include "cpl_log.h"
+
+
+
+static struct node *list = 0;
+static xmlDtdPtr     dtd;     /* DTD file */
+static xmlValidCtxt  cvp;     /* validating context */
+
+
+typedef unsigned short length_type ;
+typedef length_type*   length_type_ptr;
+
+enum {EMAIL_TO,EMAIL_HDR_NAME,EMAIL_KNOWN_HDR_BODY,EMAIL_UNKNOWN_HDR_BODY};
+
+
+#define ENCONDING_BUFFER_SIZE 65536
+
+#define FOR_ALL_ATTR(_node,_attr) \
+	for( (_attr)=(_node)->properties ; (_attr) ; (_attr)=(_attr)->next)
+
+/* right and left space trimming */
+#define trimlr(_s_) \
+	do{\
+		for(;(_s_).s[(_s_).len-1]==' ';(_s_).s[--(_s_).len]=0);\
+		for(;(_s_).s[0]==' ';(_s_).s=(_s_).s+1,(_s_).len--);\
+	}while(0);
+
+#define check_overflow(_p_,_offset_,_end_,_error_) \
+	do{\
+		if ((_p_)+(_offset_)>=(_end_)) { \
+			LOG(L_ERR,"ERROR:cpl-c:%s:%d: overflow -> buffer to small\n",\
+				__FILE__,__LINE__);\
+			goto _error_;\
+		}\
+	}while(0)\
+
+#define set_attr_type(_p_,_type_,_end_,_error_) \
+	do{\
+		check_overflow(_p_,sizeof(length_type),_end_,_error_);\
+		*((length_type_ptr)(_p_)) = htons((length_type)(_type_));\
+		(_p_) += sizeof(length_type);\
+	}while(0)\
+
+#define append_short_attr(_p_,_n_,_end_,_error_) \
+	do{\
+		check_overflow(_p_,sizeof(length_type),_end_,_error_);\
+		*((length_type_ptr)(_p_)) = htons((length_type)(_n_));\
+		(_p_) += sizeof(length_type);\
+	}while(0)
+
+#define append_str_attr(_p_,_s_,_end_,_error_) \
+	do{\
+		check_overflow(_p_,(_s_).len + 1*((((_s_).len)&0x0001)==1),\
+			_end_,_error_);\
+		*((length_type_ptr)(_p_)) = htons((length_type)(_s_).len);\
+		(_p_) += sizeof(length_type);\
+		memcpy( (_p_), (_s_).s, (_s_).len);\
+		(_p_) += (_s_).len + 1*((((_s_).len)&0x0001)==1);\
+	}while(0)
+
+#define append_double_str_attr(_p_,_s1_,_s2_,_end_,_error_) \
+	do{\
+		check_overflow(_p_,(_s1_).len + (_s2_).len +\
+			1*((((_s2_).len+(_s2_).len)&0x0001)==1), _end_, _error_);\
+		*((length_type_ptr)(_p_))=htons((length_type)((_s1_).len)+(_s2_).len);\
+		(_p_) += sizeof(length_type);\
+		memcpy( (_p_), (_s1_).s, (_s1_).len);\
+		(_p_) += (_s1_).len;\
+		memcpy( (_p_), (_s2_).s, (_s2_).len);\
+		(_p_) += (_s2_).len + 1*((((_s1_).len+(_s2_).len)&0x0001)==1);\
+	}while(0)
+
+#define get_attr_val(_attr_name_,_val_,_error_) \
+	do { \
+		(_val_).s = (char*)xmlGetProp(node,(_attr_name_));\
+		(_val_).len = strlen((_val_).s);\
+		/* remove all spaces from begin and end */\
+		trimlr( (_val_) );\
+		if ((_val_).len==0) {\
+			LOG(L_ERR,"ERROR:cpl_c:%s:%d: attribute <%s> has an "\
+				"empty value\n",__FILE__,__LINE__,(_attr_name_));\
+			goto _error_;\
+		}\
+	}while(0)\
+
+
+
+#define MAX_EMAIL_HDR_SIZE   7 /*we are looking only for SUBJECT and BODY ;-)*/
+#define MAX_EMAIL_BODY_SIZE    512
+#define MAX_EMAIL_SUBJECT_SIZE 32
+
+static inline char *decode_mail_url(char *p, char *p_end, char *url,
+														unsigned char *nr_attr)
+{
+	static char buf[ MAX_EMAIL_HDR_SIZE ];
+	char c;
+	char foo;
+	unsigned short hdr_len;
+	unsigned short *len;
+	int max_len;
+	int status;
+
+	/* init */
+	hdr_len = 0;
+	max_len = 0;
+	status = EMAIL_TO;
+	(*nr_attr) ++;
+	set_attr_type(p, TO_ATTR, p_end, error); /* attr type */
+	len = ((unsigned short*)(p));  /* attr val's len */
+	*len = 0; /* init the len */
+	p += 2;
+
+	/* parse the whole url */
+	do {
+		/* extract a char from the encoded url */
+		if (*url=='+') {
+			/* substitute a blank for a plus */
+			c=' ';
+			url++;
+		/* Look for a hex encoded character */
+		} else if ( (*url=='%') && *(url+1) && *(url+2) ) {
+			/* hex encoded - convert to a char */
+			c = hex2int(url[1]);
+			foo = hex2int(url[2]);
+			if (c==-1 || foo==-1) {
+				LOG(L_ERR, "ERROR:cpl_c:decode_mail_url: non-ASCII escaped "
+					"character in mail url [%.*s]\n", 3, url);
+				goto error;
+			}
+			c = c<<4 | foo;
+			url += 3;
+		} else {
+			/* normal character - just copy it without changing */
+			c = *url;
+			url++;
+		}
+
+		/* finally we got a character !! */
+		switch (c) {
+			case '?':
+				switch (status) {
+					case EMAIL_TO:
+						if (*len==0) {
+							LOG(L_ERR,"ERROR:cpl_c:decode_mail_url: empty TO "
+								"address found in MAIL node!\n");
+							goto error;
+						}
+						if (((*len)&0x0001)==1) p++;
+						*len = htons(*len);
+						hdr_len = 0;
+						status = EMAIL_HDR_NAME;
+						break;
+					default: goto parse_error;
+				}
+				break;
+			case '=':
+				switch (status) {
+					case EMAIL_HDR_NAME:
+						DBG("DEBUG:cpl_c:decode_mail_url: hdr [%.*s] found\n",
+							hdr_len,buf);
+						if ( hdr_len==BODY_EMAILHDR_LEN &&
+						strncasecmp(buf,BODY_EMAILHDR_STR,hdr_len)==0 ) {
+							/* BODY hdr found */
+							set_attr_type( p, BODY_ATTR, p_end, error);
+							max_len = MAX_EMAIL_BODY_SIZE;
+						} else if ( hdr_len==SUBJECT_EMAILHDR_LEN &&
+						strncasecmp(buf,SUBJECT_EMAILHDR_STR,hdr_len)==0 ) {
+							/* SUBJECT hdr found */
+							set_attr_type( p, SUBJECT_ATTR, p_end, error);
+							max_len = MAX_EMAIL_SUBJECT_SIZE;
+						} else {
+							DBG("DEBUG:cpl_c:decode_mail_url: unknown hdr ->"
+								" ignoring\n");
+							status = EMAIL_UNKNOWN_HDR_BODY;
+							break;
+						}
+						(*nr_attr) ++;
+						len = ((unsigned short*)(p));  /* attr val's len */
+						*len = 0; /* init the len */
+						p += 2;
+						status = EMAIL_KNOWN_HDR_BODY;
+						break;
+					default: goto parse_error;
+				}
+				break;
+			case '&':
+				switch (status) {
+					case EMAIL_KNOWN_HDR_BODY:
+						if (((*len)&0x0001)==1) p++;
+						*len = htons(*len);
+					case EMAIL_UNKNOWN_HDR_BODY:
+						hdr_len = 0;
+						status = EMAIL_HDR_NAME;
+						break;
+					default: goto parse_error;
+				}
+				break;
+			case 0:
+				switch (status) {
+					case EMAIL_TO:
+						if (*len==0) {
+							LOG(L_ERR,"ERROR:cpl_c:decode_mail_url: empty TO "
+								"address found in MAIL node!\n");
+							goto error;
+						}
+					case EMAIL_KNOWN_HDR_BODY:
+						if (((*len)&0x0001)==1) p++;
+						*len = htons(*len);
+					case EMAIL_UNKNOWN_HDR_BODY:
+						break;
+					default: goto parse_error;
+				}
+				break;
+			default:
+				switch (status) {
+					case EMAIL_TO:
+						(*len)++;
+						*(p++) = c;
+						if (*len==URL_MAILTO_LEN &&
+						!strncasecmp(p-(*len),URL_MAILTO_STR,(*len))) {
+							DBG("DEBUG:cpl_c:decode_mail_url: MAILTO: found at"
+								" the beginning of TO -> removed\n");
+							p -= (*len);
+							*len = 0;
+						}
+						break;
+					case EMAIL_KNOWN_HDR_BODY:
+						if ((*len)<max_len) (*len)++;
+						*(p++) = c;
+						break;
+					case EMAIL_HDR_NAME:
+						if (hdr_len<MAX_EMAIL_HDR_SIZE) hdr_len++;
+						buf[hdr_len-1] = c;
+						break;
+					case EMAIL_UNKNOWN_HDR_BODY:
+						/* do nothing */
+						break;
+					default : goto parse_error;
+				}
+		}
+	}while(c!=0);
+
+	return p;
+parse_error:
+	LOG(L_ERR,"ERROR:cpl_c:decode_mail_url: unexpected char [%c] in state %d"
+		" in email url \n",*url,status);
+error:
+	return 0;
+}
+
+
+
+/* Attr. encoding for ADDRESS node:
+ *   | attr_t(2) attr_len(2) attr_val(2*x) |  IS/CONTAINS/SUBDOMAIN_OF attr (NT)
+ */
+static inline int encode_address_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		switch (attr->name[0]) {
+			case 'i': case 'I':
+				set_attr_type(p, IS_ATTR, buf_end, error);
+				break;
+			case 'c': case 'C':
+				set_attr_type(p, CONTAINS_ATTR, buf_end, error);
+				break;
+			case 's': case 'S':
+				set_attr_type(p, SUBDOMAIN_OF_ATTR, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_address_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		/* copy also the \0 from the end of string */
+		val.len++;
+		append_str_attr(p, val, buf_end, error);
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for ADDRESS_SWITCH node:
+ *   | attr1_t(2) attr1_val(2) |                FIELD attr
+ *  [| attr2_t(2) attr2_val(2) |]?              SUBFILED attr
+ */
+static inline int encode_address_switch_attr(xmlNodePtr node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		switch(attr->name[0]) {
+			case 'F': case 'f':
+				set_attr_type(p, FIELD_ATTR, buf_end, error);
+				if (val.s[0]=='D' || val.s[0]=='d')
+					append_short_attr(p, DESTINATION_VAL, buf_end, error);
+				else if (val.s[6]=='A' || val.s[6]=='a')
+					append_short_attr(p,ORIGINAL_DESTINATION_VAL,buf_end,error);
+				else if (!val.s[6])
+					append_short_attr(p, ORIGIN_VAL, buf_end, error);
+				else {
+					LOG(L_ERR,"ERROR:cpl_c:encode_address_switch_attr: unknown"
+					" value <%s> for FIELD attr\n",val.s);
+					goto error;
+				};
+				break;
+			case 'S': case 's':
+				set_attr_type(p, SUBFIELD_ATTR, buf_end, error);
+				switch (val.s[0]) {
+					case 'u': case 'U':
+						append_short_attr(p, USER_VAL, buf_end, error);
+						break;
+					case 'h': case 'H':
+						append_short_attr(p, HOST_VAL, buf_end, error);
+						break;
+					case 'p': case 'P':
+						append_short_attr(p, PORT_VAL, buf_end, error);
+						break;
+					case 't': case 'T':
+						append_short_attr(p, TEL_VAL, buf_end, error);
+						break;
+					case 'd': case 'D':
+						/*append_short_attr(p, DISPLAY_VAL, buf_end, error);
+						break;*/  /* NOT YET SUPPORTED BY INTERPRETER */
+					case 'a': case 'A':
+						/*append_short_attr(p, ADDRESS_TYPE_VAL, buf_end,error);
+						break;*/  /* NOT YET SUPPORTED BY INTERPRETER */
+					default:
+						LOG(L_ERR,"ERROR:cpl_c:encode_address_switch_attr: "
+							"unknown value <%s> for SUBFIELD attr\n",val.s);
+						goto error;
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_address_switch_attr: unknown"
+					" attribute <%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for LANGUAGE node:
+ *   | attr_t(2) attr_len(2) attr_val(2*x) |              MATCHES attr  (NNT)
+ */
+static inline int encode_lang_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	char           *end;
+	char           *val_bk;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		/* there is only one attribute -> MATCHES */
+		if (attr->name[0]!='M' && attr->name[0]!='m') {
+			LOG(L_ERR,"ERROR:cpl_c:encode_lang_attr: unknown attribute "
+				"<%s>\n",attr->name);
+			goto error;
+		}
+		val.s = val_bk = (char*)xmlGetProp(node,attr->name);
+		/* parse the language-tag */
+		for(end=val.s,val.len=0;;end++) {
+			/* trim all spaces from the beginning of the tag */
+			if (!val.len && (*end==' ' || *end=='\t')) continue;
+			/* we cannot have more than 2 attrs - LANG_TAG and LANG_SUBTAG */
+			if ((*nr_attr)>=2) goto lang_error;
+			if (((*end)|0x20)>='a' && ((*end)|0x20)<='z') {
+				val.len++; continue;
+			} else if (*end=='*' && val.len==0 && (*nr_attr)==0 &&
+			(*end==' '|| *end=='\t' || *end==0)) {
+				val.len++;
+				set_attr_type(p, MATCHES_TAG_ATTR, buf_end, error);
+			} else if (val.len && (*nr_attr)==0 && *end=='-' ) {
+				set_attr_type(p, MATCHES_TAG_ATTR, buf_end, error);
+			} else if (val.len && ((*nr_attr)==0 || (*nr_attr)==1) &&
+			(*end==' '|| *end=='\t' || *end==0)) {
+				set_attr_type(p,
+					(!(*nr_attr))?MATCHES_TAG_ATTR:MATCHES_SUBTAG_ATTR,
+					buf_end, error );
+			} else goto lang_error;
+			(*nr_attr)++;
+			/*DBG("----> language tag=%d; %d [%.*s]\n",*(p-1),
+				val.len,val.len,end-val.len);*/
+			val.s = end-val.len;
+			append_str_attr(p, val, buf_end, error);
+			val.len = 0;
+			if (*end==0) break;
+		}
+	}
+
+	return p-p_orig;
+lang_error:
+	LOG(L_ERR,"ERROR:cpl-c:encode_lang_attr: bad value for language_tag <%s>\n",
+		val_bk);
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for PRIORITY node:
+ *   | attr1_t(2) attr1_val(2) |                  LESS/GREATER/EQUAL attr
+ *  [| attr2_t(2) attr2_len(2) attr_val(2*x) |]?  PRIOSTR attr (NT)
+ */
+static inline int encode_priority_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char  *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* attribute's name */
+		switch(attr->name[0]) {
+			case 'L': case 'l':
+				set_attr_type(p, LESS_ATTR, buf_end, error);
+				break;
+			case 'G': case 'g':
+				set_attr_type(p, GREATER_ATTR, buf_end, error);
+				break;
+			case 'E': case 'e':
+				set_attr_type(p, EQUAL_ATTR, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_priority_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+		/* attribute's encoded value */
+		get_attr_val( attr->name , val, error);
+		if ( val.len==EMERGENCY_STR_LEN &&
+		!strncasecmp(val.s,EMERGENCY_STR,val.len) ) {
+			append_short_attr(p, EMERGENCY_VAL, buf_end, error);
+		} else if ( val.len==URGENT_STR_LEN &&
+		!strncasecmp(val.s,URGENT_STR,val.len) ) {
+			append_short_attr(p, URGENT_VAL, buf_end, error);
+		} else if ( val.len==NORMAL_STR_LEN &&
+		!strncasecmp(val.s,NORMAL_STR,val.len) ) {
+			append_short_attr(p, NORMAL_VAL, buf_end, error);
+		} else if ( val.len==NON_URGENT_STR_LEN &&
+		!strncasecmp(val.s,NON_URGENT_STR,val.len) ) {
+			append_short_attr(p, NON_URGENT_VAL, buf_end, error);
+		} else {
+			append_short_attr(p, UNKNOWN_PRIO_VAL, buf_end, error);
+			set_attr_type(p, PRIOSTR_ATTR, buf_end, error);
+			val.len++; /* append \0 also */
+			append_str_attr(p, val, buf_end, error);
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for STRING_SWITCH node:
+ *  [| attr1_t(2) attr1_len(2) attr_val(2*x) |]?  IS attr  (NT)
+ *  [| attr2_t(2) attr2_len(2) attr_val(2*x) |]?  CONTAINS attr (NT)
+ */
+static inline int encode_string_switch_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* there is only one attribute -> MATCHES */
+		if (attr->name[0]!='F' && attr->name[0]!='f') {
+			LOG(L_ERR,"ERROR:cpl_c:encode_string_switch_attr: unknown "
+				"attribute <%s>\n",attr->name);
+			goto error;
+		}
+		set_attr_type(p, FIELD_ATTR, buf_end, error);
+		/* attribute's encoded value */
+		get_attr_val( attr->name , val, error);
+		switch (val.s[0]) {
+			case 'S': case 's':
+				append_short_attr(p, SUBJECT_VAL, buf_end, error);
+				break;
+			case 'O': case 'o':
+				append_short_attr(p, ORGANIZATION_VAL, buf_end, error);
+				break;
+			case 'U': case 'u':
+				append_short_attr(p, USER_AGENT_VAL, buf_end, error);
+				break;
+			case 'D': case 'd':
+				append_short_attr(p, DISPLAY_VAL, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_string_switch_attr: unknown "
+					"value <%s> for FIELD\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for STRING node:
+ *  [| attr1_t(2) attr1_len(2) attr_val(2*x) |]?  IS attr  (NT)
+ *  [| attr2_t(2) attr2_len(2) attr_val(2*x) |]?  CONTAINS attr (NT)
+ */
+static inline int encode_string_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char  *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		switch(attr->name[0]) {
+			case 'I': case 'i':
+				set_attr_type(p, IS_ATTR, buf_end, error);
+				break;
+			case 'C': case 'c':
+				set_attr_type(p, CONTAINS_ATTR, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_string_attr: unknown "
+					"attribute <%s>\n",attr->name);
+				goto error;
+		}
+		/* attribute's encoded value */
+		get_attr_val( attr->name , val, error);
+		val.len++; /* grab also the \0 */
+		append_str_attr(p,val, buf_end, error);
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for TIME_SWITCH node:
+ *  [| attr1_t(2) attr1_len(2) attr_val(2*x) |]?  TZID attr  (NT)
+ *  [| attr2_t(2) attr2_len(2) attr_val(2*x) |]?  TZURL attr (NT)
+ */
+static inline int encode_time_switch_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	static str     tz_str = STR_STATIC_INIT("TZ=");
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		switch(attr->name[2]) {
+			case 'I': case 'i':
+				set_attr_type(p, TZID_ATTR, buf_end, error);
+				/* attribute's encoded value */
+				get_attr_val( attr->name , val, error);
+				val.len++; /* grab also the \0 */
+				append_double_str_attr(p,tz_str,val, buf_end, error);
+				break;
+			case 'U': case 'u':
+				/* set_attr_type(p, TZURL_ATTR, buf_end, error);
+				 * is a waste of space to copy the url - the interpreter doesn't
+				 * use it at all ;-) */
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_time_switch_attr: unknown "
+					"attribute <%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for TIME node:
+ *   | attr1_t(2) attr1_len(2) attr1_val(2*x) |       DSTART attr  (NT)
+ *  [| attr2_t(2) attr2_len(2) attr2_val(2*x) |]?     DTEND attr (NT)
+ *  [| attr3_t(2) attr3_len(2) attr3_val(2*x) |]?     DURATION attr (NT)
+ *  [| attr4_t(2) attr4_len(2) attr4_val(2*x) |]?     FREQ attr (NT)
+ *  [| attr5_t(2) attr5_len(2) attr5_val(2*x) |]?     WKST attr (NT)
+ *  [| attr6_t(2) attr6_len(2) attr6_val(2*x) |]?     BYYEARDAY attr (NT)
+ *  [| attr7_t(2) attr7_len(2) attr7_val(2*x) |]?     COUNT attr (NT)
+ *  [| attr8_t(2) attr8_len(2) attr8_val(2*x) |]?     BYSETPOS attr (NT)
+ *  [| attr9_t(2) attr9_len(2) attr9_val(2*x) |]?     BYMONTH attr (NT)
+ *  [| attr10_t(2) attr10_len(2) attr_val10(2*x) |]?  BYMONTHDAY attr (NT)
+ *  [| attr11_t(2) attr11_len(2) attr_val11(2*x) |]?  BYMINUTE attr (NT)
+ *  [| attr12_t(2) attr12_len(2) attr_val12(2*x) |]?  INTERVAL attr (NT)
+ *  [| attr13_t(2) attr13_len(2) attr_val13(2*x) |]?  UNTIL attr (NT)
+ *  [| attr14_t(2) attr14_len(2) attr_val14(2*x) |]?  BYSECOND attr (NT)
+ *  [| attr15_t(2) attr15_len(2) attr_val15(2*x) |]?  BYHOUR attr (NT)
+ *  [| attr16_t(2) attr16_len(2) attr_val16(2*x) |]?  BYDAY attr (NT)
+ *  [| attr17_t(2) attr17_len(2) attr_val17(2*x) |]?  BYWEEKNO attr (NT)
+ */
+static inline int encode_time_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		switch (attr->name[4]) {
+			case 0:
+				if (attr->name[0]=='F' || attr->name[0]=='f')
+					set_attr_type(p, FREQ_ATTR, buf_end, error);
+				else if (attr->name[0]=='W' || attr->name[0]=='w')
+					set_attr_type(p, WKST_ATTR, buf_end, error);
+				break;
+			case 'a': case 'A':
+				if (attr->name[0]=='D' || attr->name[0]=='d')
+					set_attr_type(p, DTSTART_ATTR, buf_end, error);
+				else if (attr->name[0]=='B' || attr->name[0]=='b')
+					set_attr_type(p, BYYEARDAY_ATTR, buf_end, error);
+				break;
+			case 't': case 'T':
+				if (attr->name[0]=='D' || attr->name[0]=='d')
+					set_attr_type(p, DURATION_ATTR, buf_end, error);
+				else if (attr->name[0]=='C' || attr->name[0]=='c')
+					set_attr_type(p, COUNT_ATTR, buf_end, error);
+				else if (attr->name[0]=='B' || attr->name[0]=='b')
+					set_attr_type(p, BYSETPOS_ATTR, buf_end, error);
+				break;
+			case 'n': case 'N':
+				if (!attr->name[0])
+					set_attr_type(p, BYMONTH_ATTR, buf_end, error);
+				else if (attr->name[0]=='D' || attr->name[0]=='d')
+					set_attr_type(p, BYMONTHDAY_ATTR, buf_end, error);
+				else if (attr->name[0]=='e' || attr->name[0]=='E')
+					set_attr_type(p, BYMINUTE_ATTR, buf_end, error);
+				break;
+			case 'd': case 'D':
+				set_attr_type(p, DTEND_ATTR, buf_end, error);
+				break;
+			case 'r': case 'R':
+				set_attr_type(p, INTERVAL_ATTR, buf_end, error);
+				break;
+			case 'l': case 'L':
+				set_attr_type(p, UNTIL_ATTR, buf_end, error);
+				break;
+			case 'c': case 'C':
+				set_attr_type(p, BYSECOND_ATTR, buf_end, error);
+				break;
+			case 'u': case 'U':
+				set_attr_type(p, BYHOUR_ATTR, buf_end, error);
+				break;
+			case 'y': case 'Y':
+				set_attr_type(p, BYDAY_ATTR, buf_end, error);
+				break;
+			case 'e': case 'E':
+				set_attr_type(p, BYWEEKNO_ATTR, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_time_attr: unknown "
+					"attribute <%s>\n",attr->name);
+				goto error;
+		}
+		/* attribute's encoded value */
+		get_attr_val( attr->name , val, error);
+		val.len++; /* grab also the \0 */
+		append_str_attr(p,val, buf_end, error);
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for LOOKUP node:
+ *  | attr1_t(2) attr1_len(2) attr1_val(2*x) |      SOURCE attr  (NT)
+ * [| attr2_t(2) attr2_val(2) |]?                   CLEAR attr
+ */
+static inline int encode_lookup_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		/* get attribute's value */
+		get_attr_val( attr->name , val, error);
+		if ( !strcasecmp((const char*)attr->name,"source") ) {
+			/* this param will not be copied, since it has only one value ;-)*/
+			if ( val.len!=SOURCE_REG_STR_LEN ||
+			strncasecmp( val.s, SOURCE_REG_STR, val.len) ) {
+				LOG(L_ERR,"ERROR:cpl_c:encode_lookup_attr: unsupported value"
+					" <%.*s> in SOURCE param\n",val.len,val.s);
+				goto error;
+			}
+		} else if ( !strcasecmp((const char*)attr->name,"clear") ) {
+			(*nr_attr)++;
+			set_attr_type(p, CLEAR_ATTR, buf_end, error);
+			if ( val.len==3 && !strncasecmp(val.s,"yes",3) )
+				append_short_attr(p, YES_VAL, buf_end, error);
+			else if ( val.len==2 && !strncasecmp(val.s,"no",2) )
+				append_short_attr(p, NO_VAL, buf_end, error);
+			else {
+				LOG(L_ERR,"ERROR:cpl_c:encode_lookup_attr: unknown value "
+					"<%.*s> for attribute CLEAR\n",val.len,val.s);
+				goto error;
+			}
+		} else if ( !strcasecmp((const char*)attr->name,"timeout") ) {
+			LOG(L_WARN,"WARNING:cpl_c:encode_lookup_attr: unsupported param "
+				"TIMEOUT; skipping\n");
+		} else {
+			LOG(L_ERR,"ERROR:cpl_c:encode_lookup_attr: unknown attribute "
+				"<%s>\n",attr->name);
+			goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+
+/* Attr. encoding for LOCATION node:
+ *  | attr1_t(2) attr1_len(2) attr1_val(2*x) |      URL attr  (NT)
+ * [| attr2_t(2) attr2_val(2) |]?                   PRIORITY attr
+ * [| attr3_t(2) attr3_val(2) |]?                   CLEAR attr
+ */
+static inline int encode_location_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	struct sip_uri uri;
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	unsigned short nr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* get attribute's value */
+		get_attr_val( attr->name , val, error);
+		switch(attr->name[0]) {
+			case 'U': case 'u':
+				set_attr_type(p, URL_ATTR, buf_end, error);
+				/* check if it's a valid SIP URL -> just call
+				 * parse uri function and see if returns error ;-) */
+				if (parse_uri( val.s, val.len, &uri)!=0) {
+					LOG(L_ERR,"ERROR:cpl-c:encrypt_location_attr: <%s> is "
+						"not a valid SIP URL\n",val.s);
+					goto error;
+				}
+				val.len++; /*copy also the \0 */
+				append_str_attr(p,val, buf_end, error);
+				break;
+			case 'P': case 'p':
+				set_attr_type(p, PRIORITY_ATTR, buf_end, error);
+				if (val.s[0]=='0') nr=0;
+				else if (val.s[0]=='1') nr=10;
+				else goto prio_error;
+				if (val.s[1]!='.') goto prio_error;
+				if (val.s[2]<'0' || val.s[2]>'9') goto prio_error;
+				nr += val.s[2] - '0';
+				if (nr>10)
+					goto prio_error;
+				append_short_attr(p, nr, buf_end, error);
+				break;
+			case 'C': case 'c':
+				set_attr_type(p, CLEAR_ATTR, buf_end, error);
+				if (val.s[0]=='y' || val.s[0]=='Y')
+					append_short_attr(p, YES_VAL, buf_end, error);
+				else
+					append_short_attr(p, NO_VAL, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_location_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+prio_error:
+	LOG(L_ERR,"ERROR:cpl_c:encode_location_attr: invalid priority <%s>\n",
+		val.s);
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for REMOVE_LOCATION node:
+ * [| attr1_t(2) attr1_len(2) attr1_val(2*x) |]?    LOCATION attr  (NT)
+ */
+static inline int encode_rmvloc_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	struct sip_uri uri;
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		switch(attr->name[0]) {
+			case 'L': case 'l':
+				set_attr_type(p, LOCATION_ATTR, buf_end, error);
+				/* get the value of the attribute */
+				get_attr_val( attr->name , val, error);
+				/* check if it's a valid SIP URL -> just call
+				 * parse uri function and see if returns error ;-) */
+				if (parse_uri( val.s, val.len, &uri)!=0) {
+					LOG(L_ERR,"ERROR:cpl-c:encrypt_rmvloc_attr: <%s> is "
+						"not a valid SIP URL\n",val.s);
+					goto error;
+				}
+				val.len++; /*copy also the \0 */
+				append_str_attr(p,val, buf_end, error);
+				break;
+			case 'P': case 'p':
+			case 'V': case 'v':
+				/* as the interpreter ignores PARAM and VALUE attributes, we will
+				 * do the same ;-) */
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_rmvloc_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for PROXY node:
+ * [| attr1_t(2) attr1_val(2) |]?                   RECURSE attr
+ * [| attr2_t(2) attr2_val(2) |]?                   TIMEOUT attr
+ * [| attr3_t(2) attr3_val(2) |]?                   ORDERING attr
+ */
+static inline int encode_proxy_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	unsigned int   nr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		switch(attr->name[0]) {
+			case 'R': case 'r':
+				set_attr_type(p, RECURSE_ATTR, buf_end, error);
+				if (val.s[0]=='y' || val.s[0]=='Y')
+					append_short_attr(p, YES_VAL, buf_end, error);
+				else if (val.s[0]=='n' || val.s[0]=='N')
+					append_short_attr(p, NO_VAL, buf_end, error);
+				else {
+					LOG(L_ERR,"ERROR:cpl_c:encode_proxy_attr: unknown value "
+					"<%s> for attribute RECURSE\n",val.s);
+					goto error;
+				}
+				break;
+			case 'T': case 't':
+				set_attr_type(p, TIMEOUT_ATTR, buf_end, error);
+				if (str2int(&val,&nr)==-1) {
+					LOG(L_ERR,"ERROR:cpl_c:encode_proxy_attr: bad value <%.*s>"
+						" for attribute TIMEOUT\n",val.len,val.s);
+					goto error;
+				}
+				append_short_attr(p, (unsigned short)nr, buf_end, error);
+				break;
+			case 'O': case 'o':
+				set_attr_type(p, ORDERING_ATTR, buf_end, error);
+				switch (val.s[0]) {
+					case 'p': case'P':
+						append_short_attr(p, PARALLEL_VAL, buf_end, error);
+						break;
+					case 'S': case 's':
+						append_short_attr(p, SEQUENTIAL_VAL, buf_end, error);
+						break;
+					case 'F': case 'f':
+						append_short_attr(p, FIRSTONLY_VAL, buf_end, error);
+						break;
+					default:
+						LOG(L_ERR,"ERROR:cpl_c:encode_proxy_attr: unknown "
+							"value <%s> for attribute ORDERING\n",val.s);
+						goto error;
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_proxy_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for REJECT node:
+ *  | attr1_t(2) attr1_val(2) |                      STATUS attr
+ * [| attr2_t(2) attr2_len(2) attr2_val(2*x)|]?      REASON attr (NT)
+ */
+static inline int encode_reject_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	unsigned int   nr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		switch(attr->name[0]) {
+			case 'R': case 'r':
+				set_attr_type(p, REASON_ATTR, buf_end, error);
+				val.len++; /* grab also the /0 */
+				append_str_attr(p, val, buf_end, error);
+				break;
+			case 'S': case 's':
+				set_attr_type(p, STATUS_ATTR, buf_end, error);
+				if (str2int(&val,&nr)==-1) {
+					/*it was a non numeric value */
+					if (val.len==BUSY_STR_LEN &&
+					!strncasecmp(val.s,BUSY_STR,val.len)) {
+						append_short_attr(p, BUSY_VAL, buf_end, error);
+					} else if (val.len==NOTFOUND_STR_LEN &&
+					!strncasecmp(val.s,NOTFOUND_STR,val.len)) {
+						append_short_attr(p, NOTFOUND_VAL, buf_end, error);
+					} else if (val.len==ERROR_STR_LEN &&
+					!strncasecmp(val.s,ERROR_STR,val.len)) {
+						append_short_attr(p, ERROR_VAL, buf_end, error);
+					} else if (val.len==REJECT_STR_LEN &&
+					!strncasecmp(val.s,REJECT_STR,val.len)) {
+						append_short_attr(p, REJECT_VAL, buf_end, error);
+					} else {
+						LOG(L_ERR,"ERROR:cpl_c:encode_priority_attr: bad "
+							"val. <%s> for STATUS\n",val.s);
+						goto error;
+					}
+				} else if (nr<400 || nr>700) {
+					LOG(L_ERR,"ERROR:cpl_c:encode_priority_attr: bad "
+						"code <%d> for STATUS\n",nr);
+					goto error;
+				} else {
+					append_short_attr(p, nr, buf_end, error);
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_priority_attr: unknown attribute "
+					"<%s>\n",attr->name);
+				goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for REDIRECT node:
+ *  | attr1_t(2) attr1_val(2) |                      STATUS attr
+ * [| attr2_t(2) attr2_len(2) attr2_val(2*x)|]?      REASON attr (NT)
+ */
+static inline int encode_redirect_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		if (attr->name[0]=='p' || attr->name[0]=='P') {
+			set_attr_type(p, PERMANENT_ATTR, buf_end, error);
+			/* get the value */
+			get_attr_val( attr->name , val, error);
+			if (val.s[0]=='y' || val.s[0]=='Y')
+				append_short_attr( p, YES_VAL, buf_end, error);
+			else if (val.s[0]=='n' || val.s[0]=='N')
+				append_short_attr( p, NO_VAL, buf_end, error);
+			else {
+				LOG(L_ERR,"ERROR:cpl_c:encode_redirect_attr: bad "
+					"val. <%s> for PERMANENT\n",val.s);
+				goto error;
+			}
+		} else {
+			LOG(L_ERR,"ERROR:cpl_c:encode_redirect_attr: unknown attribute "
+				"<%s>\n",attr->name);
+			goto error;
+		}
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for LOG node:
+ *  [| attr1_t(2) attr1_len(2) attr1_val(2*x) |]?       NAME attr  (NT)
+ *  [| attr2_t(2) attr2_len(2) attr2_val(2*x) |]?       COMMENT attr (NT)
+ */
+static inline int encode_log_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		switch (attr->name[0] ) {
+			case 'n': case 'N':
+				if (val.len>MAX_NAME_SIZE) val.len=MAX_NAME_SIZE;
+				set_attr_type(p, NAME_ATTR, buf_end, error);
+				break;
+			case 'c': case 'C':
+				if (val.len>MAX_COMMENT_SIZE) val.len=MAX_COMMENT_SIZE;
+				set_attr_type(p, COMMENT_ATTR, buf_end, error);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:encode_log_attr: unknown attribute "
+					"<%s>\n",attr->name);
+					goto error;
+		}
+		/* be sure there is a \0 at the end of string */
+		val.s[val.len++]=0;
+		append_str_attr(p,val, buf_end, error);
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for MAIL node:
+ *   | attr1_t(2) attr1_len(2) attr1_val(2*x) |        TO_ATTR attr  (NNT)
+ *  [| attr2_t(2) attr2_len(2) attr2_val(2*x) |]?      SUBJECT_ATTR attr (NNT)
+ *  [| attr3_t(2) attr3_len(2) attr3_val(2*x) |]?      BODY_ATTR attr (NNT)
+ */
+static inline int encode_mail_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		/* there is only one attribute -> URL */
+		if (attr->name[0]!='u' && attr->name[0]!='U') {
+			LOG(L_ERR,"ERROR:cpl_c:encode_node_attr: unknown attribute "
+					"<%s>\n",attr->name);
+			goto error;
+		}
+		p = decode_mail_url( p, buf_end,
+			(char*)xmlGetProp(node,attr->name), nr_attr);
+		if (p==0)
+			goto error;
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for SUBACTION node:
+ */
+static inline int encode_subaction_attr(xmlNodePtr  node, char *node_ptr,
+																char *buf_end)
+{
+	xmlAttrPtr     attr;
+	str            val;
+
+	FOR_ALL_ATTR(node,attr) {
+		/* there is only one attribute -> ID */
+		if ((attr->name[0]|0x20)=='i' && ((attr->name[1]|0x20)=='d') &&
+		attr->name[2]==0 ) {
+			/* get the value of the attribute */
+			get_attr_val( attr->name , val, error);
+			if ((list = append_to_list(list, node_ptr,val.s))==0) {
+				LOG(L_ERR,"ERROR:cpl_c:encode_subaction_attr: failed to add "
+					"subaction into list -> pkg_malloc failed?\n");
+				goto error;
+			}
+		} else {
+			LOG(L_ERR,"ERROR:cpl_c:encode_subaction_attr: unknown attribute "
+				"<%s>\n",attr->name);
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	return -1;
+}
+
+
+
+/* Attr. encoding for SUB node:
+ *   | attr1_t(2) attr1_val(2) |              REF_ATTR attr
+ */
+static inline int encode_sub_attr(xmlNodePtr  node, char *node_ptr, char *buf_end)
+{
+	xmlAttrPtr     attr;
+	char           *p, *p_orig;
+	unsigned char  *nr_attr;
+	char           *sub_ptr;
+	str            val;
+
+	nr_attr = &(NR_OF_ATTR(node_ptr));
+	*nr_attr = 0;
+	p = p_orig = ATTR_PTR(node_ptr);
+
+	FOR_ALL_ATTR(node,attr) {
+		(*nr_attr)++;
+		/* there is only one attribute -> REF */
+		if ( strcasecmp("ref",(char*)attr->name)!=0 ) {
+			LOG(L_ERR,"ERROR:cpl_c:encode_sub_attr: unknown attribute "
+				"<%s>\n",attr->name);
+			goto error;
+		}
+		set_attr_type(p, REF_ATTR, buf_end, error);
+		/* get the value of the attribute */
+		get_attr_val( attr->name , val, error);
+		if ( (sub_ptr=search_the_list(list, val.s))==0 ) {
+			LOG(L_ERR,"ERROR:cpl_c:encode_sub_attr: unable to find declaration "
+				"of subaction <%s>\n",val.s);
+			goto error;
+		}
+		append_short_attr(p,(unsigned short)(node_ptr-sub_ptr),buf_end,error);
+	}
+
+	return p-p_orig;
+error:
+	return -1;
+}
+
+
+
+/* Returns :  -1 - error
+ *            >0 - subtree size of the given node
+ */
+int encode_node( xmlNodePtr node, char *p, char *p_end)
+{
+	xmlNodePtr kid;
+	unsigned short sub_tree_size;
+	int attr_size;
+	int kid_size;
+	int foo;
+
+	/* counting the kids */
+	for(kid=node->children,foo=0;kid;kid=kid->next)
+		if (kid->type==XML_ELEMENT_NODE) foo++;
+	check_overflow(p,GET_NODE_SIZE(foo),p_end,error);
+	NR_OF_KIDS(p) = foo;
+
+	/* size of the encoded attributes */
+	attr_size = 0;
+
+	/* init the number of attributes */
+	NR_OF_ATTR(p) = 0;
+
+	/* encode node name */
+	switch (node->name[0]) {
+		case 'a':case 'A':
+			switch (node->name[7]) {
+				case 0:
+					NODE_TYPE(p) = ADDRESS_NODE;
+					attr_size = encode_address_attr( node, p, p_end);
+					break;
+				case '-':
+					NODE_TYPE(p) = ADDRESS_SWITCH_NODE;
+					attr_size = encode_address_switch_attr( node, p, p_end);
+					break;
+				default:
+					NODE_TYPE(p) = ANCILLARY_NODE;
+					break;
+			}
+			break;
+		case 'B':case 'b':
+			NODE_TYPE(p) = BUSY_NODE;
+			break;
+		case 'c':case 'C':
+			NODE_TYPE(p) = CPL_NODE;
+			break;
+		case 'd':case 'D':
+			NODE_TYPE(p) = DEFAULT_NODE;
+			break;
+		case 'f':case 'F':
+			NODE_TYPE(p) = FAILURE_NODE;
+			break;
+		case 'i':case 'I':
+			NODE_TYPE(p) = INCOMING_NODE;
+			break;
+		case 'l':case 'L':
+			switch (node->name[2]) {
+				case 'g':case 'G':
+					NODE_TYPE(p) = LOG_NODE;
+					attr_size = encode_log_attr( node, p, p_end);
+					break;
+				case 'o':case 'O':
+					NODE_TYPE(p) = LOOKUP_NODE;
+					attr_size = encode_lookup_attr( node, p, p_end);
+					break;
+				case 'c':case 'C':
+					NODE_TYPE(p) = LOCATION_NODE;
+					attr_size = encode_location_attr( node, p, p_end);
+					break;
+				default:
+					if (node->name[8]) {
+						NODE_TYPE(p) = LANGUAGE_SWITCH_NODE;
+					} else {
+						NODE_TYPE(p) = LANGUAGE_NODE;
+						attr_size = encode_lang_attr( node, p, p_end);
+					}
+					break;
+			}
+			break;
+		case 'm':case 'M':
+			NODE_TYPE(p) =  MAIL_NODE;
+			attr_size = encode_mail_attr( node, p, p_end);
+			break;
+		case 'n':case 'N':
+			switch (node->name[3]) {
+				case 'F':case 'f':
+					NODE_TYPE(p) = NOTFOUND_NODE;
+					break;
+				case 'N':case 'n':
+					NODE_TYPE(p) = NOANSWER_NODE;
+					break;
+				default:
+					NODE_TYPE(p) = NOT_PRESENT_NODE;
+					break;
+			}
+			break;
+		case 'o':case 'O':
+			if (node->name[1]=='t' || node->name[1]=='T') {
+				NODE_TYPE(p) = OTHERWISE_NODE;
+			} else {
+				NODE_TYPE(p) = OUTGOING_NODE;
+			}
+			break;
+		case 'p':case 'P':
+			if (node->name[2]=='o' || node->name[2]=='O') {
+				NODE_TYPE(p) = PROXY_NODE;
+				attr_size = encode_proxy_attr( node, p, p_end);
+			} else if (node->name[8]) {
+				NODE_TYPE(p) = PRIORITY_SWITCH_NODE;
+			} else {
+				NODE_TYPE(p) = PRIORITY_NODE;
+				attr_size = encode_priority_attr( node, p, p_end);
+			}
+			break;
+		case 'r':case 'R':
+			switch (node->name[2]) {
+				case 'j':case 'J':
+					NODE_TYPE(p) = REJECT_NODE;
+					attr_size = encode_reject_attr( node, p, p_end);
+					break;
+				case 'm':case 'M':
+					NODE_TYPE(p) = REMOVE_LOCATION_NODE;
+					attr_size = encode_rmvloc_attr( node, p, p_end);
+					break;
+				default:
+					if (node->name[8]) {
+						NODE_TYPE(p) = REDIRECTION_NODE;
+					} else {
+						NODE_TYPE(p) = REDIRECT_NODE;
+						attr_size = encode_redirect_attr( node, p, p_end);
+					}
+					break;
+			}
+			break;
+		case 's':case 'S':
+			switch (node->name[3]) {
+				case 0:
+					NODE_TYPE(p) = SUB_NODE;
+					attr_size = encode_sub_attr( node, p, p_end);
+					break;
+				case 'c':case 'C':
+					NODE_TYPE(p) = SUCCESS_NODE;
+					break;
+				case 'a':case 'A':
+					NODE_TYPE(p) = SUBACTION_NODE;
+					attr_size = encode_subaction_attr( node, p, p_end);
+					break;
+				default:
+					if (node->name[6]) {
+						NODE_TYPE(p) = STRING_SWITCH_NODE;
+						attr_size = encode_string_switch_attr( node, p, p_end);
+					} else {
+						NODE_TYPE(p) = STRING_NODE;
+						attr_size = encode_string_attr( node, p, p_end);
+					}
+					break;
+			}
+			break;
+		case 't':case 'T':
+			if (node->name[4]) {
+				NODE_TYPE(p) = TIME_SWITCH_NODE;
+				attr_size = encode_time_switch_attr( node, p, p_end);
+			} else {
+				NODE_TYPE(p) = TIME_NODE;
+				attr_size = encode_time_attr( node, p, p_end);
+			}
+			break;
+		default:
+			LOG(L_ERR,"ERROR:cpl-c:encode_node: unknown node <%s>\n",
+				node->name);
+			goto error;
+	}
+
+	/* compute the total length of the node (including attributes) */
+	if (attr_size<0)
+		goto error;
+	sub_tree_size =  SIMPLE_NODE_SIZE(p) + (unsigned short)attr_size;
+
+	/* encrypt all the kids */
+	for(kid = node->children,foo=0;kid;kid=kid->next) {
+		if (kid->type!=XML_ELEMENT_NODE) continue;
+		SET_KID_OFFSET( p, foo, sub_tree_size);
+		kid_size = encode_node( kid, p+sub_tree_size, p_end);
+		if (kid_size<=0)
+			goto error;
+		sub_tree_size += (unsigned short)kid_size;
+		foo++;
+	}
+
+	return sub_tree_size;
+error:
+	return -1;
+}
+
+
+
+#define BAD_XML       "CPL script is not a valid XML document"
+#define BAD_XML_LEN   (sizeof(BAD_XML)-1)
+#define BAD_CPL       "CPL script doesn't respect CPL grammar"
+#define BAD_CPL_LEN   (sizeof(BAD_CPL)-1)
+#define NULL_CPL      "Empty CPL script"
+#define NULL_CPL_LEN  (sizeof(NULL_CPL)-1)
+#define ENC_ERR       "Encoding of the CPL script failed"
+#define ENC_ERR_LEN   (sizeof(ENC_ERR)-1)
+
+int encodeCPL( str *xml, str *bin, str *log)
+{
+	static char buf[ENCONDING_BUFFER_SIZE];
+	xmlDocPtr  doc;
+	xmlNodePtr cur;
+
+	doc  = 0;
+	list = 0;
+
+	/* reset all the logs (if any) to catch some possible err/warn/notice
+	 * from the parser/validater/encoder */
+	reset_logs();
+
+	/* parse the xml */
+	doc = xmlParseDoc( (unsigned char*)xml->s );
+	if (!doc) {
+		append_log( 1, MSG_ERR BAD_XML LF, MSG_ERR_LEN+BAD_XML_LEN+LF_LEN);
+		LOG(L_ERR,"ERROR:cpl:encodeCPL:" BAD_XML "\n");
+		goto error;
+	}
+
+	/* check the xml against dtd */
+	if (xmlValidateDtd(&cvp, doc, dtd)!=1) {
+		append_log( 1, MSG_ERR BAD_CPL LF, MSG_ERR_LEN+BAD_CPL_LEN+LF_LEN);
+		LOG(L_ERR,"ERROR:cpl-c:encodeCPL: " BAD_CPL "\n");
+		goto error;
+	}
+
+	cur = xmlDocGetRootElement(doc);
+	if (!cur) {
+		append_log( 1, MSG_ERR NULL_CPL LF, MSG_ERR_LEN+NULL_CPL_LEN+LF_LEN);
+		LOG(L_ERR,"ERROR:cpl-c:encodeCPL: " NULL_CPL "\n");
+		goto error;
+	}
+
+	bin->len = encode_node( cur, buf, buf+ENCONDING_BUFFER_SIZE);
+	if (bin->len<0) {
+		append_log( 1, MSG_ERR ENC_ERR LF, MSG_ERR_LEN+ENC_ERR_LEN+LF_LEN);
+		LOG(L_ERR,"ERROR:cpl-c:encodeCPL: " ENC_ERR "\n");
+		goto error;
+	}
+
+	xmlFreeDoc(doc);
+	if (list) delete_list(list);
+	/* compile the log buffer */
+	compile_logs( log );
+	bin->s = buf;
+	return 1;
+error:
+	if (doc) xmlFreeDoc(doc);
+	if (list) delete_list(list);
+	/* compile the log buffer */
+	compile_logs( log );
+	return 0;
+}
+
+
+#if 0
+static void err_print(void *ctx, const char *msg, ...)
+{
+	va_list ap;
+	//char *t;
+
+	va_start( ap, msg);
+	LOG(L_ERR,"->>>> my errr <%s>\n",msg);
+	//while ( (t=va_arg(ap, char *))!=0) {
+	//	LOG(L_ERR,"   -> <%s>\n",t);
+	//}
+	vfprintf(stderr,msg,ap);
+	//append_log( 2, ERR, ERR_LEN, msg, strlen(msg) );
+	va_end(ap);
+}
+#endif
+
+
+/* loads and parse the dtd file; a validating context is created */
+int init_CPL_parser( char* DTD_filename )
+{
+	dtd = xmlParseDTD( NULL, (unsigned char*)DTD_filename);
+	if (!dtd) {
+		LOG(L_ERR,"ERROR:cpl-c:init_CPL_parser: DTD not parsed successfully\n");
+		return -1;
+	}
+	cvp.userData = (void *) stderr;
+	cvp.error    = (xmlValidityErrorFunc) /*err_print*/ fprintf;
+	cvp.warning  = (xmlValidityWarningFunc) /*err_print*/ fprintf;
+
+	return 1;
+}
+

+ 37 - 0
cpl-c/cpl_parser.h

@@ -0,0 +1,37 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _CPL_PARSER_H
+#define _CPL_PARSER_H
+
+#include "../../str.h"
+
+int init_CPL_parser( char* DTD_filename );
+int encodeCPL(str *xml, str *bin, str *log);
+
+
+#endif

+ 543 - 0
cpl-c/cpl_proxy.h

@@ -0,0 +1,543 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-07-29: file created (bogdan)
+ * 2004-06-14: flag CPL_IS_STATEFUL is set now immediately after the 
+ *             transaction is created (bogdan)
+ */
+
+#include "../../modules/tm/h_table.h"
+#include "../../parser/contact/parse_contact.h"
+
+
+#define duplicate_str( _orig_ , _new_ ) \
+	do {\
+		(_new_) = (str*)shm_malloc(sizeof(str)+(_orig_)->len);\
+		if (!(_new_)) goto mem_error;\
+		(_new_)->len = (_orig_)->len;\
+		(_new_)->s = (char*)((_new_))+sizeof(str);\
+		memcpy((_new_)->s,(_orig_)->s,(_orig_)->len);\
+	} while(0)
+
+#define search_and_duplicate_hdr( _intr_ , _field_ , _name_ , _sfoo_ ) \
+	do {\
+		if (!(_intr_)->_field_) {\
+			if (!(_intr_)->msg->_field_) { \
+				if (parse_headers((_intr_)->msg,_name_,0)==-1) {\
+					LOG(L_ERR,"ERROR:run_proxy: bad %llx hdr\n",_name_);\
+					goto runtime_error;\
+				} else if ( !(_intr_)->msg->_field_) {\
+					(_intr_)->_field_ = STR_NOT_FOUND;\
+				} else {\
+					(_sfoo_) = &((_intr_)->msg->_field_->body);\
+					duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
+				}\
+			} else {\
+				(_sfoo_) = &((_intr_)->msg->_field_->body);\
+				duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
+			}\
+		} else {\
+			(_sfoo_) = (_intr_)->_field_;\
+			duplicate_str( (_sfoo_) , (_intr_)->_field_ );\
+		}\
+	}while(0)
+
+
+
+static inline int parse_q(str *q, unsigned int *prio)
+{
+	if (q->s[0]=='0')
+		*prio=0;
+	else if (q->s[0]=='1')
+		*prio=10;
+	else
+		goto error;
+	if (q->s[1]!='.')
+		goto error;
+	if (q->s[2]<'0' || q->s[2]>'9')
+		goto error;
+	*prio += q->s[2] - '0';
+	if (*prio>10)
+		goto error;
+
+	return 0;
+error:
+	LOG(L_ERR,"ERROR:cpl-c:parse_q:bad q param <%.*s>\n",q->len,q->s);
+	return -1;
+}
+
+
+
+static inline int add_contacts_to_loc_set(struct sip_msg* msg,
+													struct location **loc_set)
+{
+	struct sip_uri uri;
+	struct contact *contacts;
+	unsigned int prio;
+
+	/* we need to have the contact header */
+	if (msg->contact==0) {
+		/* find and parse the Contact header */
+		if ((parse_headers(msg, HDR_CONTACT_F, 0)==-1) || (msg->contact==0) ) {
+			LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: error parsing or "
+				"no Contact hdr found!\n");
+			goto error;
+		}
+	}
+
+	/* extract from contact header the all the addresses */
+	if (parse_contact( msg->contact )!=0) {
+		LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to parse "
+			"Contact hdr!\n");
+		goto error;
+	}
+
+	/* in contact hdr, in parsed attr, we should have a list of contacts */
+	if ( msg->contact->parsed ) {
+		contacts = ((struct contact_body*)msg->contact->parsed)->contacts;
+		for( ; contacts ; contacts=contacts->next) {
+			/* check if the contact is a valid sip uri */
+			if (parse_uri( contacts->uri.s, contacts->uri.len , &uri)!=0) {
+				continue;
+			}
+			/* convert the q param to int value (if any) */
+			if (contacts->q) {
+				if (parse_q( &(contacts->q->body), &prio )!=0)
+					continue;
+			} else {
+				prio = 10; /* set default to minimum */
+			}
+			/* add the uri to location set */
+			if (add_location( loc_set, &contacts->uri,prio, CPL_LOC_DUPL)!=0) {
+				LOG(L_ERR,"ERROR:cpl-c:add_contacts_to_loc_set: unable to add "
+				"<%.*s>\n",contacts->uri.len,contacts->uri.s);
+			}
+		}
+	}
+
+	return 0;
+error:
+	return -1;
+}
+
+
+
+static void reply_callback( struct cell* t, int type, struct tmcb_params* ps)
+{
+	struct cpl_interpreter *intr = (struct cpl_interpreter*)(*(ps->param));
+	struct location        *loc  = 0;
+	int rez;
+
+	if (intr==0) {
+		LOG(L_WARN,"WARNING:cpl-c:reply_callback: param=0 for callback %d,"
+			" transaction=%p \n",type,t);
+		return;
+	}
+
+	if (type&TMCB_RESPONSE_OUT) {
+		/* the purpose of the final reply is to trash down the interpreter
+		 * structure! it's the safest place to do that, since this callback
+		 * it's called only once per transaction for final codes (>=200) ;-) */
+		if (ps->code>=200) {
+			DBG("DEBUG:cpl-c:final_reply: code=%d  -------------->\n"
+				" --------------------------> final reply received\n",
+			ps->code);
+			/* CPL interpretation done, call established -> destroy */
+			free_cpl_interpreter( intr );
+			/* set to zero the param callback*/
+			*(ps->param) = 0;
+		}
+		return;
+	} else if (!(type&TMCB_ON_FAILURE)) {
+		LOG(L_ERR,"BUG:cpl-c:reply_callback: unknown type %d\n",type);
+		goto exit;
+	}
+
+	DBG("DEBUG:cpl-c:negativ_reply: ------------------------------>\n"
+		" ---------------------------------> negativ reply received\n");
+
+	intr->flags |= CPL_PROXY_DONE;
+	intr->msg = ps->req;
+
+	/* if it's a redirect-> do I have to added to the location set ? */
+	if (intr->proxy.recurse && (ps->code)/100==3) {
+		DBG("DEBUG:cpl-c:negativ_reply: recurse level %d processing..\n",
+				intr->proxy.recurse);
+		intr->proxy.recurse--;
+		/* get the locations from the Contact */
+		add_contacts_to_loc_set( ps->rpl, &(intr->loc_set));
+		switch (intr->proxy.ordering) {
+			case SEQUENTIAL_VAL:
+				/* update the last_to_proxy to last location from set */
+				if (intr->proxy.last_to_proxy==0) {
+					/* the pointer went through entire old set -> set it to the
+					 * updated set, from the beginning  */
+					if (intr->loc_set==0)
+						/* the updated set is also empty -> proxy ended */
+						break;
+					intr->proxy.last_to_proxy = intr->loc_set;
+				}
+				while(intr->proxy.last_to_proxy->next)
+					intr->proxy.last_to_proxy=intr->proxy.last_to_proxy->next;
+				break;
+			case PARALLEL_VAL:
+				/* push the whole new location set to be proxy */
+				intr->proxy.last_to_proxy = intr->loc_set;
+				break;
+			case FIRSTONLY_VAL:
+				intr->proxy.last_to_proxy = 0;
+				break;
+		}
+	}
+
+	/* the current proxying failed -> do I have another location to try ?
+	 * This applies only for SERIAL forking or if RECURSE is set */
+	if (intr->proxy.last_to_proxy) {
+		/* continue proxying */
+		DBG("DEBUG:cpl-c:failed_reply: resuming proxying....\n");
+		switch (intr->proxy.ordering) {
+			case PARALLEL_VAL:
+				/* I get here only if I got a 3xx and RECURSE in on ->
+				 * forward to all location from location set */
+				intr->proxy.last_to_proxy = 0;
+				cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags );
+				break;
+			case SEQUENTIAL_VAL:
+				/* place a new branch to the next location from loc. set*/
+				loc = remove_first_location( &(intr->loc_set) );
+				/*print_location_set(intr->loc_set);*/
+				/* update (if necessary) the last_to_proxy location  */
+				if (intr->proxy.last_to_proxy==loc)
+					intr->proxy.last_to_proxy = 0;
+				cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags );
+				break;
+			default:
+				LOG(L_CRIT,"BUG:cpl_c:failed_reply: unexpected ordering found "
+					"when continuing proxying (%d)\n",intr->proxy.ordering);
+				goto exit;
+		}
+		/* nothing more to be done */
+		return;
+	} else {
+		/* done with proxying.... -> process the final response */
+		DBG("DEBUG:cpl-c:failed_reply:final_reply: got a final %d\n",ps->code);
+		intr->ip = 0;
+		if (ps->code==486 || ps->code==600) {
+			/* busy response */
+			intr->ip = intr->proxy.busy;
+		} else if (ps->code==408) {
+			/* request timeout -> no response */
+			intr->ip = intr->proxy.noanswer;
+		} else if (((ps->code)/100)==3) {
+			/* redirection */
+			/* add to the location list all the addresses from Contact */
+			add_contacts_to_loc_set( ps->rpl, &(intr->loc_set));
+			print_location_set( intr->loc_set );
+			intr->ip = intr->proxy.redirect;
+		} else {
+			/* generic failure */
+			intr->ip = intr->proxy.failure;
+		}
+
+		if (intr->ip==0)
+			intr->ip = (intr->proxy.default_)?
+				intr->proxy.default_:DEFAULT_ACTION;
+		if (intr->ip!=DEFAULT_ACTION)
+			intr->ip = get_first_child( intr->ip );
+
+		if( intr->ip==DEFAULT_ACTION)
+			rez = run_default(intr);
+		else
+			rez = cpl_run_script(intr);
+		switch ( rez ) {
+			case SCRIPT_END:
+				/* we don't need to free the interpreter here since it will 
+				 * be freed in the final_reply callback */
+			case SCRIPT_TO_BE_CONTINUED:
+				return;
+			case SCRIPT_RUN_ERROR:
+			case SCRIPT_FORMAT_ERROR:
+				goto exit;
+			default:
+				LOG(L_CRIT,"BUG:cpl-c:failed_reply: improper result %d\n",
+					rez);
+				goto exit;
+		}
+	}
+
+exit:
+	/* in case of error the default response chosen by ser at the last
+	 * proxying will be forwarded to the UAC */
+	free_cpl_interpreter( intr );
+	/* set to zero the param callback*/
+	*(ps->param) = 0;
+	return;
+}
+
+
+
+static inline char *run_proxy( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short n;
+	char *kid;
+	char *p;
+	int i;
+	str *s;
+	struct location *loc;
+	int_str tmp;
+
+	intr->proxy.ordering = PARALLEL_VAL;
+	intr->proxy.recurse = (unsigned short)cpl_env.proxy_recurse;
+
+	/* identify the attributes */
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr( p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case TIMEOUT_ATTR:
+				if (cpl_env.timer_avp.n || cpl_env.timer_avp.s.s) {
+					tmp.n=(int)n;
+					if ( add_avp( AVP_TRACK_TO | cpl_env.timer_avp_type,
+					cpl_env.timer_avp, tmp)<0) {
+						LOG(L_ERR,"ERROR:run_proxy: unable to set "
+							"timer AVP\n");
+						/* continue */
+					}
+				}
+				break;
+			case RECURSE_ATTR:
+				switch (n) {
+					case NO_VAL:
+						intr->proxy.recurse = 0;
+						break;
+					case YES_VAL:
+						/* already set as default */
+						break;
+					default:
+						LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found"
+							" for attr. RECURSE in PROXY node!\n",n);
+						goto script_error;
+				}
+				break;
+			case ORDERING_ATTR:
+				if (n!=PARALLEL_VAL && n!=SEQUENTIAL_VAL && n!=FIRSTONLY_VAL){
+					LOG(L_ERR,"ERROR:run_proxy: invalid value (%u) found"
+						" for attr. ORDERING in PROXY node!\n",n);
+					goto script_error;
+				}
+				intr->proxy.ordering = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_proxy: unknown attribute (%d) in"
+					"PROXY node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	intr->proxy.busy = intr->proxy.noanswer = 0;
+	intr->proxy.redirect = intr->proxy.failure = intr->proxy.default_ = 0;
+
+	/* this is quite an "expensive" node to run, so let's make some checking
+	 * before getting deeply into it */
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case BUSY_NODE :
+				intr->proxy.busy = kid;
+				break;
+			case NOANSWER_NODE:
+				intr->proxy.noanswer = kid;
+				break;
+			case REDIRECTION_NODE:
+				intr->proxy.redirect = kid;
+				break;
+			case FAILURE_NODE:
+				intr->proxy.failure = kid;
+				break;
+			case DEFAULT_NODE:
+				intr->proxy.default_ = kid;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_proxy: unknown output node type"
+					" (%d) for PROXY node\n",NODE_TYPE(kid));
+				goto script_error;
+		}
+	}
+
+	/* if the location set if empty, I will go directly on failure/default */
+	if (intr->loc_set==0) {
+		DBG("DEBUG:run_proxy: location set found empty -> going on "
+			"failure/default branch\n");
+			if (intr->proxy.failure)
+				return get_first_child(intr->proxy.failure);
+			else if (intr->proxy.default_)
+				return get_first_child(intr->proxy.default_);
+			else return DEFAULT_ACTION;
+	}
+
+	/* if it's the first execution of a proxy node, force parsing of the needed
+	 * headers and duplicate them in shared memory */
+	if (!(intr->flags&CPL_PROXY_DONE)) {
+		/* user name is already in shared memory */
+		/* requested URI - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
+		s = GET_RURI( intr->msg );
+		duplicate_str( s , intr->ruri );
+		intr->flags |= CPL_RURI_DUPLICATED;
+		/* TO header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
+		if (!intr->to) {
+			if (!intr->msg->to &&
+			(parse_headers(intr->msg,HDR_TO_F,0)==-1 || !intr->msg->to)) {
+				LOG(L_ERR,"ERROR:run_proxy: bad msg or missing TO header\n");
+				goto runtime_error;
+			}
+			s = &(get_to(intr->msg)->uri);
+		} else {
+			s = intr->to;
+		}
+		duplicate_str( s , intr->to );
+		intr->flags |= CPL_TO_DUPLICATED;
+		/* FROM header - mandatory in SIP msg (cannot be STR_NOT_FOUND) */
+		if (!intr->from) {
+			if (parse_from_header( intr->msg )==-1)
+				goto runtime_error;
+			s = &(get_from(intr->msg)->uri);
+		} else {
+			s = intr->from;
+		}
+		duplicate_str( s , intr->from );
+		intr->flags |= CPL_FROM_DUPLICATED;
+		/* SUBJECT header - optional in SIP msg (can be STR_NOT_FOUND) */
+		if (intr->subject!=STR_NOT_FOUND) {
+			search_and_duplicate_hdr(intr,subject,HDR_SUBJECT_F,s);
+			if (intr->subject!=STR_NOT_FOUND)
+				intr->flags |= CPL_SUBJECT_DUPLICATED;
+		}
+		/* ORGANIZATION header - optional in SIP msg (can be STR_NOT_FOUND) */
+		if ( intr->organization!=STR_NOT_FOUND) {
+			search_and_duplicate_hdr(intr,organization,HDR_ORGANIZATION_F,s);
+			if ( intr->organization!=STR_NOT_FOUND)
+				intr->flags |= CPL_ORGANIZATION_DUPLICATED;
+		}
+		/* USER_AGENT header - optional in SIP msg (can be STR_NOT_FOUND) */
+		if (intr->user_agent!=STR_NOT_FOUND) {
+			search_and_duplicate_hdr(intr,user_agent,HDR_USERAGENT_F,s);
+			if (intr->user_agent!=STR_NOT_FOUND)
+				intr->flags |= CPL_USERAGENT_DUPLICATED;
+		}
+		/* ACCEPT_LANGUAGE header - optional in SIP msg
+		 * (can be STR_NOT_FOUND) */
+		if (intr->accept_language!=STR_NOT_FOUND) {
+			search_and_duplicate_hdr(intr,accept_language,
+				HDR_ACCEPTLANGUAGE_F,s);
+			if (intr->accept_language!=STR_NOT_FOUND)
+				intr->flags |= CPL_ACCEPTLANG_DUPLICATED;
+		}
+		/* PRIORITY header - optional in SIP msg (can be STR_NOT_FOUND) */
+		if (intr->priority!=STR_NOT_FOUND) {
+			search_and_duplicate_hdr(intr,priority,HDR_PRIORITY_F,s);
+			if (intr->priority!=STR_NOT_FOUND)
+				intr->flags |= CPL_PRIORITY_DUPLICATED;
+		}
+
+		/* now is the first time doing proxy, so I can still be stateless;
+		 * as proxy is done all the time stateful, I have to switch from
+		 * stateless to stateful if necessary.  */
+		if ( !(intr->flags&CPL_IS_STATEFUL) ) {
+			i = cpl_fct.tmb.t_newtran( intr->msg );
+			if (i<0) {
+				LOG(L_ERR,"ERROR:cpl-c:run_proxy: failed to build new "
+					"transaction!\n");
+				goto runtime_error;
+			} else if (i==0) {
+				LOG(L_ERR,"ERROR:cpl-c:run_proxy: processed INVITE is a "
+					"retransmission!\n");
+				/* instead of generating an error is better just to break the
+				 * script by returning EO_SCRIPT */
+				return EO_SCRIPT;
+			}
+			intr->flags |= CPL_IS_STATEFUL;
+		}
+
+		/* as I am interested in getting the responses back - I need to install
+		 * some callback functions for replies  */
+		if (cpl_fct.tmb.register_tmcb(intr->msg,0,
+		TMCB_ON_FAILURE|TMCB_RESPONSE_OUT,reply_callback,(void*)intr, 0) <= 0){
+			LOG(L_ERR, "ERROR:cpl_c:run_proxy: failed to register "
+				"TMCB_RESPONSE_OUT callback\n");
+			goto runtime_error;
+		}
+	}
+
+	switch (intr->proxy.ordering) {
+		case FIRSTONLY_VAL:
+			/* forward the request only to the first address from loc. set */
+			/* location set cannot be empty -> was checked before */
+			loc = remove_first_location( &(intr->loc_set) );
+			intr->proxy.last_to_proxy = 0;
+			/* set the new ip before proxy -> otherwise race cond with rpls */
+			intr->ip = CPL_TO_CONTINUE;
+			if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags )==-1)
+				goto runtime_error;
+			break;
+		case PARALLEL_VAL:
+			/* forward to all location from location set */
+			intr->proxy.last_to_proxy = 0;
+			/* set the new ip before proxy -> otherwise race cond with rpls */
+			intr->ip = CPL_TO_CONTINUE;
+			if (cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags)
+			==-1)
+				goto runtime_error;
+			break;
+		case SEQUENTIAL_VAL:
+			/* forward the request one at the time to all addresses from
+			 * loc. set; location set cannot be empty -> was checked before */
+			/* use the first location from set */
+			loc = remove_first_location( &(intr->loc_set) );
+			/* set as the last_to_proxy the last location from set */
+			intr->proxy.last_to_proxy = intr->loc_set;
+			while (intr->proxy.last_to_proxy&&intr->proxy.last_to_proxy->next)
+				intr->proxy.last_to_proxy = intr->proxy.last_to_proxy->next;
+			/* set the new ip before proxy -> otherwise race cond with rpls */
+			intr->ip = CPL_TO_CONTINUE;
+			if (cpl_proxy_to_loc_set(intr->msg,&loc,intr->flags)==-1)
+				goto runtime_error;
+			break;
+	}
+
+	return CPL_TO_CONTINUE;
+script_error:
+	return CPL_SCRIPT_ERROR;
+mem_error:
+	LOG(L_ERR,"ERROR:run_proxy: no more free shm memory\n");
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+}
+
+
+

+ 233 - 0
cpl-c/cpl_rpc.c

@@ -0,0 +1,233 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#include <stdio.h>
+#include <sys/uio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/uio.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include "../../str.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "cpl_db.h"
+#include "cpl_parser.h"
+#include "cpl_loader.h"
+#include "cpl_rpc.h"
+
+
+static inline int check_userhost( char *p, char *end)
+{
+	char *p1;
+	int  dot;
+
+	/* parse user name */
+	p1 = p;
+	while (p<end && (isalnum((int)*p) || *p=='-' || *p=='_' || *p=='.' ))
+		p++;
+	if (p==p1 || p==end || *p!='@')
+		return -1;
+	p++;
+	/* parse the host part */
+	dot = 1;
+	p1 = p;
+	while (p<end) {
+		if (*p=='.') {
+			if (dot) return -1; /* dot after dot */
+			dot = 1;
+		} else if (isalnum((int)*p) || *p=='-' || *p=='_' ) {
+			dot = 0;
+		} else {
+			return -1;
+		}
+		p++;
+	}
+	if (p1==p || dot)
+		return -1;
+
+	return 0;
+}
+
+
+static const char* cpl_load_doc[] = {
+	"Load a CPL script to the server.", /* Documentation string */
+	0                                   /* Method signature(s) */
+};
+
+
+static void cpl_load(rpc_t* rpc, void* c)
+{
+	char* cpl_file;
+	int cpl_file_len;
+	str user;
+	str xml = STR_NULL;
+	str bin = STR_NULL;
+	str enc_log = STR_NULL;
+
+	DBG("DEBUG:cpl-c:cpl_load: \"LOAD_CPL\" FIFO command received!\n");
+
+	if (rpc->scan(c, "s", &user.s) < 1) {
+		rpc->fault(c, 400, "Username parameter not found");
+		return;
+	}
+	user.len = strlen(user.s);
+
+	DBG("DEBUG:cpl_load: user=%.*s\n", user.len, user.s);
+
+	if (rpc->scan(c, "s", &cpl_file) < 1) {
+		rpc->fault(c, 400, "CPL file name expected");
+		return;
+	}
+	cpl_file_len = strlen(cpl_file);
+	DBG("DEBUG:cpl-c:cpl_load: cpl file=%s\n", cpl_file);
+
+	     /* check user+host */
+	if (check_userhost( user.s, user.s+user.len)!=0) {
+		LOG(L_ERR,"ERROR:cpl-c:cpl_load: invalid user@host [%.*s]\n",
+		    user.len,user.s);
+		rpc->fault(c, 400, "Bad user@host: %.*s", user.len, user.s);
+		return;
+	}
+
+	/* load the xml file - this function will allocated a buff for the loading
+	 * the cpl file and attach it to xml.s -> don't forget to free it! */
+	if (load_file( cpl_file, &xml)!=1) {
+		rpc->fault(c, 400, "Cannot read CPL file\n");
+		goto error;
+	}
+
+	/* get the binary coding for the XML file */
+	if (encodeCPL( &xml, &bin, &enc_log)!=1) {
+		rpc->fault(c, 400, "%.*s", enc_log.len, enc_log.s);
+		goto error;
+	}
+
+	/* write both the XML and binary formats into database */
+	if (write_to_db(user.s, &xml, &bin)!=1) {
+		rpc->fault(c, 400, "Cannot save CPL to database");
+		goto error;
+	}
+
+	/* free the memory used for storing the cpl script in XML format */
+	pkg_free( xml.s );
+
+	/* everything was OK -> dump the logs into response file */
+	rpc->add(c, "S", &enc_log);
+	if (enc_log.s) pkg_free ( enc_log.s );
+	return;
+error:
+	if (enc_log.s) pkg_free ( enc_log.s );
+	if (xml.s) pkg_free ( xml.s );
+}
+
+
+
+static const char* cpl_remove_doc[] = {
+	"Remove a CPL script from server.", /* Documentation string */
+	0                                   /* Method signature(s) */
+};
+
+static void cpl_remove(rpc_t* rpc, void* c)
+{
+	char* user;
+	int user_len;
+
+	DBG("DEBUG:cpl-c:cpl_remove: \"REMOVE_CPL\" FIFO command received!\n");
+
+	if (rpc->scan(c, "s", &user) < 1) {
+		rpc->fault(c, 400, "Username parameter not found");
+		return;
+	}
+	user_len = strlen(user);
+
+	/* check user+host */
+	if (check_userhost( user, user+user_len)!=0) {
+		LOG(L_ERR,"ERROR:cpl-c:cpl_remove: invalid user@host [%.*s]\n",
+		    user_len,user);
+		rpc->fault(c, 400, "Bad user@host: '%s'", user);
+		return;
+	}
+	
+	if (rmv_from_db(user)!=1) {
+		rpc->fault(c, 400, "Error while removing CPL script of %s from database", user);
+		return;
+	}
+}
+
+
+static const char* cpl_get_doc[] = {
+	"Return a CPL script.",      /* Documentation string */
+	0                          /* Method signature(s) */
+};
+
+static void cpl_get(rpc_t* rpc, void* c)
+{
+	str user;
+	str script = STR_NULL;
+
+	if (rpc->scan(c, "S", &user) < 1) {
+		rpc->fault(c, 400, "Username parameter expected");
+		return;
+	}
+
+	DBG("DEBUG:cpl-c:cpl_get: user=%.*s\n", user.len, user.s);
+
+	     /* check user+host */
+	if (check_userhost( user.s, user.s+user.len)!=0) {
+		LOG(L_ERR,"ERROR:cpl-c:cpl_load: invalid user@host [%.*s]\n",
+			user.len,user.s);
+		rpc->fault(c, 400, "Bad user@host '%.*s'", user.len, user.s);
+		return;
+	}
+	
+	     /* get the script for this user */
+	if (get_user_script(&user, &script, 0)==-1) {
+		rpc->fault(c, 500, "Database query failed");
+		return;
+	}
+
+	rpc->add(c, "S", &script);
+	if (script.s) shm_free( script.s );
+}
+
+
+/* 
+ * RPC Methods exported by this module 
+ */
+rpc_export_t cpl_rpc_methods[] = {
+	{"cpl.load",   cpl_load,   cpl_load_doc,   0},
+	{"cpl.remove", cpl_remove, cpl_remove_doc, 0},
+	{"cpl.get",    cpl_get,    cpl_get_doc,    0},
+	{0, 0, 0, 0}
+};

+ 36 - 0
cpl-c/cpl_rpc.h

@@ -0,0 +1,36 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef _CPL_RPC_H
+#define _CPL_RPC_H
+
+#include "../../rpc.h"
+
+extern rpc_export_t cpl_rpc_methods[];
+
+#endif /* _CPL_RPC_H */

+ 1086 - 0
cpl-c/cpl_run.c

@@ -0,0 +1,1086 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-01-23 : created (bogdan)
+ * 2003-09-11 : build_lump_rpl() merged into add_lump_rpl() (bogdan)
+ * 2004-06-14 : all global variables merged into cpl_env and cpl_fct;
+ *              append_branches param added to lookup node (bogdan)
+ * 2004-06-14 : flag CPL_IS_STATEFUL is set now immediately after the 
+ *              transaction is created (bogdan)
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "../../str.h"
+#include "../../dprint.h"
+#include "../../parser/msg_parser.h"
+#include "../../data_lump_rpl.h"
+#include "../../modules/tm/tm_load.h"
+#include "../usrloc/usrloc.h"
+#include "CPL_tree.h"
+#include "loc_set.h"
+#include "cpl_utils.h"
+#include "cpl_nonsig.h"
+#include "cpl_sig.h"
+#include "cpl_env.h"
+#include "cpl_run.h"
+
+
+#define EO_SCRIPT            ((char*)0xffffffff)
+#define DEFAULT_ACTION       ((char*)0xfffffffe)
+#define CPL_SCRIPT_ERROR     ((char*)0xfffffffd)
+#define CPL_RUNTIME_ERROR    ((char*)0xfffffffc)
+#define CPL_TO_CONTINUE      ((char*)0xfffffffb)
+
+#define HDR_NOT_FOUND        ((char*)0xffffffff)
+#define UNDEF_CHAR           (0xff)
+
+
+
+#define check_overflow_by_ptr(_ptr_,_intr_,_error_) \
+	do {\
+		if ( (char*)(_ptr_)>(_intr_)->script.len+(_intr_)->script.s ) {\
+			LOG(L_ERR,"ERROR:cpl_c: overflow detected ip=%p ptr=%p in "\
+			"func. %s, line %d\n",(_intr_)->ip,_ptr_,__FILE__,__LINE__);\
+			goto _error_; \
+		} \
+	}while(0)
+
+#define check_overflow_by_offset(_len_,_intr_,_error_) \
+	do {\
+		if ( (char*)((_intr_)->ip+(_len_)) > \
+		(_intr_)->script.len+(_intr_)->script.s ) {\
+			LOG(L_ERR,"ERROR:cpl_c: overflow detected ip=%p offset=%d in "\
+			"func. %s, line %d\n",(_intr_)->ip,_len_,__FILE__,__LINE__);\
+			goto _error_; \
+		} \
+	}while(0)
+
+#define get_first_child(_node_) \
+	((NR_OF_KIDS(_node_)==0)?DEFAULT_ACTION:(_node_)+KID_OFFSET(_node_,0))
+
+#define get_basic_attr(_p_,_code_,_n_,_intr_,_error_) \
+	do{\
+		check_overflow_by_ptr( (_p_)+BASIC_ATTR_SIZE, _intr_, _error_);\
+		_code_ = ntohs( *((unsigned short*)(_p_)) );\
+		_n_ =  ntohs( *((unsigned short*)((_p_)+2)) );\
+		(_p_) += 4;\
+	}while(0)
+
+#define get_str_attr(_p_,_s_,_len_,_intr_,_error_,_FIXUP_) \
+	do{\
+		if ( ((int)(_len_))-(_FIXUP_)<=0 ) {\
+			LOG(L_ERR,"ERROR:cpl_c:%s:%d: attribute is an empty string\n",\
+				__FILE__,__LINE__);\
+			goto _error_; \
+		} else {\
+			check_overflow_by_ptr( (_p_)+(_len_), _intr_, _error_);\
+			_s_ = _p_;\
+			(_p_) += (_len_) + 1*(((_len_)&0x0001)==1);\
+			(_len_) -= (_FIXUP_);\
+		}\
+	}while(0)
+
+
+
+struct cpl_interpreter* new_cpl_interpreter( struct sip_msg *msg, str *script)
+{
+	struct cpl_interpreter *intr = 0;
+
+	intr = (struct cpl_interpreter*)shm_malloc(sizeof(struct cpl_interpreter));
+	if (!intr) {
+		LOG(L_ERR,"ERROR:build_cpl_interpreter: no more free memory!\n");
+		goto error;
+	}
+	memset( intr, 0, sizeof(struct cpl_interpreter));
+
+	/* init the interpreter*/
+	intr->script.s = script->s;
+	intr->script.len = script->len;
+	intr->recv_time = time(0);
+	intr->ip = script->s;
+	intr->msg = msg;
+
+	/* check the beginning of the script */
+	if ( NODE_TYPE(intr->ip)!=CPL_NODE ) {
+		LOG(L_ERR,"ERROR:build_cpl_interpreter: first node is not CPL!!\n");
+		goto error;
+	}
+
+	return intr;
+error:
+	return 0;
+}
+
+
+
+void free_cpl_interpreter(struct cpl_interpreter *intr)
+{
+	if (intr) {
+		empty_location_set( &(intr->loc_set) );
+		if (intr->script.s)
+			shm_free( intr->script.s);
+		if (intr->user.s)
+			shm_free(intr->user.s);
+		if (intr->flags&CPL_RURI_DUPLICATED)
+			shm_free(intr->ruri);
+		if (intr->flags&CPL_TO_DUPLICATED)
+			shm_free(intr->to);
+		if (intr->flags&CPL_FROM_DUPLICATED)
+			shm_free(intr->from);
+		if (intr->flags&CPL_SUBJECT_DUPLICATED)
+			shm_free(intr->subject);
+		if (intr->flags&CPL_ORGANIZATION_DUPLICATED)
+			shm_free(intr->organization);
+		if (intr->flags&CPL_USERAGENT_DUPLICATED)
+			shm_free(intr->user_agent);
+		if (intr->flags&CPL_ACCEPTLANG_DUPLICATED)
+			shm_free(intr->accept_language);
+		if (intr->flags&CPL_PRIORITY_DUPLICATED)
+			shm_free(intr->priority);
+		shm_free(intr);
+	}
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_cpl_node( struct cpl_interpreter *intr )
+{
+	char *kid;
+	unsigned char start;
+	int i;
+
+	start = (intr->flags&CPL_RUN_INCOMING)?INCOMING_NODE:OUTGOING_NODE;
+	/* look for the starting node (incoming or outgoing) */
+	for(i=0;i<NR_OF_KIDS(intr->ip);i++) {
+		kid= intr->ip + KID_OFFSET(intr->ip,i);
+		if ( NODE_TYPE(kid)==start ) {
+			return get_first_child(kid);
+		} else
+		if (NODE_TYPE(kid)==SUBACTION_NODE ||
+		NODE_TYPE(kid)==ANCILLARY_NODE ||
+		NODE_TYPE(kid)==INCOMING_NODE  ||
+		NODE_TYPE(kid)==OUTGOING_NODE ) {
+			continue;
+		} else {
+			LOG(L_ERR,"ERROR:cpl_c:run_cpl_node: unknown child type (%d) "
+				"for CPL node!!\n",NODE_TYPE(kid));
+			return CPL_SCRIPT_ERROR;
+		}
+	}
+
+	DBG("DEBUG:cpl_c:run_cpl_node: CPL node has no %d subnode -> default\n",
+		start);
+	return DEFAULT_ACTION;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_lookup( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short n;
+	unsigned char  clear;
+	char *p;
+	char *kid;
+	char *failure_kid = 0;
+	char *success_kid = 0;
+	char *notfound_kid = 0;
+	int  i;
+	time_t      tc;
+	urecord_t*  r;
+	ucontact_t* contact;
+
+	clear = NO_VAL;
+
+	/* check the params */
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr(p,attr_name,n,intr,script_error);
+		switch (attr_name) {
+			case CLEAR_ATTR:
+				if (n!=YES_VAL && n!=NO_VAL)
+					LOG(L_WARN,"WARNING:run_lookup: invalid value (%u) found"
+						" for param. CLEAR in LOOKUP node -> using "
+						"default (%u)!\n",n,clear);
+				else
+					clear = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_lookup: unknown attribute (%d) in "
+					"LOOKUP node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	/* check the kids */
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case SUCCESS_NODE :
+				success_kid = kid;
+				break;
+			case NOTFOUND_NODE:
+				notfound_kid = kid;
+				break;
+			case FAILURE_NODE:
+				failure_kid = kid;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_lookup: unknown output node type"
+					" (%d) for LOOKUP node\n",NODE_TYPE(kid));
+				goto script_error;
+		}
+	}
+
+	kid = failure_kid;
+
+	if (cpl_env.lu_domain) {
+		/* fetch user's contacts via usrloc */
+		tc = time(0);
+		cpl_fct.ulb.lock_udomain( cpl_env.lu_domain );
+		i = cpl_fct.ulb.get_urecord( cpl_env.lu_domain, &intr->user, &r);
+		if (i < 0) {
+			/* failure */
+			LOG(L_ERR, "ERROR:run_lookup: Error while querying usrloc\n");
+			cpl_fct.ulb.unlock_udomain( cpl_env.lu_domain );
+		} else if (i > 0) {
+			/* not found */
+			DBG("DBG:cpl-c:run_lookup: '%.*s' Not found in usrloc\n",
+				intr->user.len, intr->user.s);
+			cpl_fct.ulb.unlock_udomain( cpl_env.lu_domain );
+			kid = notfound_kid;
+		} else {
+			contact = r->contacts;
+			/* skip expired contacts */
+			while ( contact && contact->expires <= tc)
+				contact = contact->next;
+			/* any contacts left? */
+			if (contact) {
+				/* clear loc set if requested */
+				if (clear)
+					empty_location_set( &(intr->loc_set) );
+				/* start adding locations to set */
+				do {
+					DBG("DBG:cpl-c:run_lookup: adding <%.*s>q=%d\n",
+						contact->c.len,contact->c.s,(int)(10*contact->q));
+					if (add_location( &(intr->loc_set), &contact->c,
+					(int)(10*contact->q),
+					CPL_LOC_DUPL|((contact->flags & FL_NAT)*CPL_LOC_NATED)
+					)==-1) {
+						LOG(L_ERR,"ERROR:cpl-c:run_lookup: unable to add "
+							"location to set :-(\n");
+						cpl_fct.ulb.unlock_udomain( cpl_env.lu_domain );
+						goto runtime_error;
+					}
+					contact = contact->next;
+				}while( contact && cpl_env.lu_append_branches);
+				/* set the flag for modifying the location set */
+				intr->flags |= CPL_LOC_SET_MODIFIED;
+				/* we found a valid contact */
+				kid = success_kid;
+			} else {
+				/* no valid contact found */
+				kid = notfound_kid;
+			}
+			cpl_fct.ulb.unlock_udomain( cpl_env.lu_domain );
+		}
+
+	}
+
+	if (kid)
+		return get_first_child(kid);
+	return DEFAULT_ACTION;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_location( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short n;
+	char  *p;
+	unsigned char  prio;
+	unsigned char  clear;
+	str url;
+	int i;
+
+	clear = NO_VAL;
+	prio = 10;
+	url.s = (char*)UNDEF_CHAR;
+	url.len = 0; /* fixes gcc 4.0 warning */
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)>1) {
+		LOG(L_ERR,"ERROR:run_location: LOCATION node suppose to have max "
+			"one child, not %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr(p,attr_name,n,intr,script_error);
+		switch (attr_name) {
+			case URL_ATTR:
+				url.len = n;
+				get_str_attr( p, url.s, url.len, intr, script_error,1);
+				break;
+			case PRIORITY_ATTR:
+				if ( n>10)
+					LOG(L_WARN,"WARNING:run_location: invalid value (%u) found"
+						" for param. PRIORITY in LOCATION node -> using "
+						"default (%u)!\n",n,prio);
+				else
+					prio = n;
+				break;
+			case CLEAR_ATTR:
+				if (n!=YES_VAL && n!=NO_VAL)
+					LOG(L_WARN,"WARNING:run_location: invalid value (%u) found"
+						" for param. CLEAR in LOCATION node -> using "
+						"default (%u)!\n",n,clear);
+				else
+					clear = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_location: unknown attribute (%d) in "
+					"LOCATION node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	if (url.s==(char*)UNDEF_CHAR) {
+		LOG(L_ERR,"ERROR:run_location: param. URL missing in LOCATION node\n");
+		goto script_error;
+	}
+
+	if (clear)
+		empty_location_set( &(intr->loc_set) );
+	if (add_location( &(intr->loc_set), &url, prio, 0/*no dup*/ )==-1) {
+		LOG(L_ERR,"ERROR:run_location: unable to add location to set :-(\n");
+		goto runtime_error;
+	}
+	/* set the flag for modifying the location set */
+	intr->flags |= CPL_LOC_SET_MODIFIED;
+
+	return get_first_child(intr->ip);
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_remove_location( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short n;
+	char *p;
+	str url;
+	int i;
+
+	url.s = (char*)UNDEF_CHAR;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)>1) {
+		LOG(L_ERR,"ERROR:cpl_c:run_remove_location: REMOVE_LOCATION node "
+			"suppose to have max one child, not %d!\n",
+			NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	/* dirty hack to speed things up in when loc set is already empty */
+	if (intr->loc_set==0)
+		goto done;
+
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr(p,attr_name,n,intr,script_error);
+		switch (attr_name) {
+			case LOCATION_ATTR:
+				url.len = n;
+				get_str_attr( p, url.s, url.len, intr, script_error,1);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_remove_location: unknown attribute "
+					"(%d) in REMOVE_LOCATION node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	if (url.s==(char*)UNDEF_CHAR) {
+		DBG("DEBUG:run_remove_location: remove all locs from loc_set\n");
+		empty_location_set( &(intr->loc_set) );
+	} else {
+		remove_location( &(intr->loc_set), url.s, url.len );
+	}
+	/* set the flag for modifying the location set */
+	intr->flags |= CPL_LOC_SET_MODIFIED;
+
+done:
+	return get_first_child(intr->ip);
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_sub( struct cpl_interpreter *intr )
+{
+	char  *p;
+	unsigned short offset;
+	unsigned short attr_name;
+	int i;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)!=0) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: SUB node doesn't suppose to have any "
+			"sub-nodes. Found %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	/* check the number of attr */
+	i = NR_OF_ATTR( intr->ip );
+	if (i!=1) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: incorrect nr. of attr. %d (<>1) in "
+			"SUB node\n",i);
+		goto script_error;
+	}
+	/* get attr's name */
+	p = ATTR_PTR(intr->ip);
+	get_basic_attr( p, attr_name, offset, intr, script_error);
+	if (attr_name!=REF_ATTR) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: invalid attr. %d (expected %d)in "
+			"SUB node\n", attr_name, REF_ATTR);
+		goto script_error;
+	}
+	/* make the jump */
+	p = intr->ip - offset;
+	/* check the destination pointer -> are we still inside the buffer ;-) */
+	if (((char*)p)<intr->script.s) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: jump offset lower than the script "
+			"beginning -> underflow!\n");
+		goto script_error;
+	}
+	check_overflow_by_ptr( p+SIMPLE_NODE_SIZE(intr->ip), intr, script_error);
+	/* check to see if we hit a subaction node */
+	if ( NODE_TYPE(p)!=SUBACTION_NODE ) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: sub. jump hit a nonsubaction node!\n");
+		goto script_error;
+	}
+	if ( NR_OF_ATTR(p)!=0 ) {
+		LOG(L_ERR,"ERROR:cpl_c:run_sub: invalid subaction node reached "
+		"(attrs=%d); expected (0)!\n",NR_OF_ATTR(p));
+		goto script_error;
+	}
+
+	return get_first_child(p);
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_reject( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short status;
+	unsigned short n;
+	char *reason_s;
+	char *p;
+	int i;
+
+	reason_s = (char*)UNDEF_CHAR;
+	status = UNDEF_CHAR;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)!=0) {
+		LOG(L_ERR,"ERROR:cpl_c:run_reject: REJECT node doesn't suppose to have "
+			"any sub-nodes. Found %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr( p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case STATUS_ATTR:
+				status = n;
+				break;
+			case REASON_ATTR:
+				get_str_attr( p, reason_s, n, intr, script_error,1);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:run_reject: unknown attribute "
+					"(%d) in REJECT node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	if (status==UNDEF_CHAR) {
+		LOG(L_ERR,"ERROR:cpl_c:run_reject: mandatory attribute STATUS "
+			"not found\n");
+		goto script_error;
+	}
+	if (status<400 || status>=700) {
+		LOG(L_ERR,"ERROR:cpl_c:run_reject: bad attribute STATUS "
+			"(%d)\n",status);
+		goto script_error;
+	}
+
+	if (reason_s==(char*)UNDEF_CHAR ) {
+		switch (status) {
+			case 486:
+				reason_s = "Busy Here";
+				break;
+			case 404:
+				reason_s = "Not Found";
+				break;
+			case 603:
+				reason_s = "Decline";
+				break;
+			case 500:
+				reason_s = "Internal Server Error";
+				break;
+			default:
+				reason_s = "Generic Error";
+		}
+	}
+
+	/* if still stateless and FORCE_STATEFUL set -> build the transaction */
+	if ( !(intr->flags&CPL_IS_STATEFUL) && intr->flags&CPL_FORCE_STATEFUL) {
+		i = cpl_fct.tmb.t_newtran( intr->msg );
+		if (i<0) {
+			LOG(L_ERR,"ERROR:cpl-c:run_reject: failed to build new "
+				"transaction!\n");
+			goto runtime_error;
+		} else if (i==0) {
+			LOG(L_ERR,"ERROR:cpl-c:run_reject: processed INVITE is a "
+				"retransmission!\n");
+			/* instead of generating an error is better just to break the
+			 * script by returning EO_SCRIPT */
+			return EO_SCRIPT;
+		}
+		intr->flags |= CPL_IS_STATEFUL;
+	}
+
+	/* send the reply */
+	if ( intr->flags&CPL_IS_STATEFUL ) {
+		/* reply statefully */
+		i = cpl_fct.tmb.t_reply(intr->msg, (int)status, reason_s );
+	} else {
+		/* reply statelessly */
+		i = cpl_fct.slb.zreply(intr->msg, status, reason_s );
+	}
+
+	if ( i!=1 ) {
+		LOG(L_ERR,"ERROR:run_reject: unable to send reject reply!\n");
+		goto runtime_error;
+	}
+
+	return EO_SCRIPT;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_redirect( struct cpl_interpreter *intr )
+{
+	struct location *loc;
+	struct lump_rpl *lump;
+	unsigned short attr_name;
+	unsigned short permanent;
+	unsigned short n;
+	char *p;
+	str lump_str;
+	char *cp;
+	int i;
+
+	permanent = NO_VAL;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)!=0) {
+		LOG(L_ERR,"ERROR:cpl-c:run_redirect: REDIRECT node doesn't suppose "
+			"to have any sub-nodes. Found %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	/* read the attributes of the REDIRECT node*/
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr( p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case PERMANENT_ATTR:
+				if (n!=YES_VAL && n!=NO_VAL) {
+					LOG(L_ERR,"ERROR:cpl-c:run_redirect: unsupported value (%d)"
+						" in attribute PERMANENT for REDIRECT node",n);
+					goto script_error;
+				}
+				permanent = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_redirect: unknown attribute "
+					"(%d) in REDIRECT node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	/* build the lump for Contact header */
+	lump_str.len = 9 /*"Contact: "*/;
+	for(loc=intr->loc_set;loc;loc=loc->next)
+		lump_str.len += 1/*"<"*/ + loc->addr.uri.len + 7/*">;q=x.x"*/ +
+			2*(loc->next!=0)/*" ,"*/;
+	lump_str.len += CRLF_LEN;
+
+	lump_str.s = pkg_malloc( lump_str.len );
+	if(!lump_str.s) {
+		LOG(L_ERR,"ERROR:cpl-c:run_redirect: out of pkg memory!\n");
+		goto runtime_error;
+	}
+	cp = lump_str.s;
+	memcpy( cp , "Contact: " , 9);
+	cp += 9;
+	for(loc=intr->loc_set;loc;loc=loc->next) {
+		*(cp++) = '<';
+		memcpy(cp,loc->addr.uri.s,loc->addr.uri.len);
+		cp += loc->addr.uri.len;
+		memcpy(cp,">;q=",4);
+		cp += 4;
+		*(cp++) = (loc->addr.priority!=10)?'0':'1';
+		*(cp++) = '.';
+		*(cp++) = '0'+(loc->addr.priority%10);
+		if (loc->next) {
+			*(cp++) = ' ';
+			*(cp++) = ',';
+		}
+	}
+	memcpy(cp,CRLF,CRLF_LEN);
+
+	/* if still stateless and FORCE_STATEFUL set -> build the transaction */
+	if ( !(intr->flags&CPL_IS_STATEFUL) && intr->flags&CPL_FORCE_STATEFUL) {
+		i = cpl_fct.tmb.t_newtran( intr->msg );
+		if (i<0) {
+			LOG(L_ERR,"ERROR:cpl-c:run_redirect: failed to build new "
+				"transaction!\n");
+			pkg_free( lump_str.s );
+			goto runtime_error;
+		} else if (i==0) {
+			LOG(L_ERR,"ERROR:cpl-c:run_redirect: processed INVITE is a "
+				"retransmission!\n");
+			/* instead of generating an error is better just to break the
+			 * script by returning EO_SCRIPT */
+			pkg_free( lump_str.s );
+			return EO_SCRIPT;
+		}
+		intr->flags |= CPL_IS_STATEFUL;
+	}
+
+	/* add the lump to the reply */
+	lump = add_lump_rpl( intr->msg, lump_str.s , lump_str.len , LUMP_RPL_HDR);
+	if(!lump) {
+		LOG(L_ERR,"ERROR:cpl-c:run_redirect: unable to add lump_rpl! \n");
+		pkg_free( lump_str.s );
+		goto runtime_error;
+	}
+
+	/* send the reply */
+	if ( intr->flags&CPL_IS_STATEFUL ) {
+		/* reply statefully */
+		if (permanent)
+			i = cpl_fct.tmb.t_reply( intr->msg, (int)301, "Moved permanently");
+		else
+			i = cpl_fct.tmb.t_reply( intr->msg, (int)302, "Moved temporarily");
+	} else {
+		/* reply statelessly */
+		if (permanent)
+			i = cpl_fct.slb.zreply( intr->msg, 301, "Moved permanently");
+		else
+			i = cpl_fct.slb.zreply( intr->msg, 302, "Moved temporarily");
+	}
+
+	/* msg which I'm working on can be in private memory or is a clone into
+	 * shared memory (if I'm after a failed proxy); So, it's better to removed
+	 * by myself the lump that I added previously */
+	unlink_lump_rpl( intr->msg, lump);
+	free_lump_rpl( lump );
+
+	if (i!=1) {
+		LOG(L_ERR,"ERROR:cpl-c:run_redirect: unable to send "
+			"redirect reply!\n");
+		goto runtime_error;
+	}
+
+	return EO_SCRIPT;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_log( struct cpl_interpreter *intr )
+{
+	char  *p;
+	unsigned short attr_name;
+	unsigned short n;
+	str name    = STR_NULL;
+	str comment = STR_NULL;
+	str user;
+	int i;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)>1) {
+		LOG(L_ERR,"ERROR:cpl_c:run_log: LOG node suppose to have max one child"
+			", not %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	/* is logging enabled? */
+	if ( cpl_env.log_dir==0 )
+		goto done;
+
+	/* read the attributes of the LOG node*/
+	p = ATTR_PTR(intr->ip);
+	for( i=NR_OF_ATTR(intr->ip); i>0 ; i-- ) {
+		get_basic_attr( p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case NAME_ATTR:
+				get_str_attr( p, name.s, n, intr, script_error,1);
+				name.len = n;
+				break;
+			case COMMENT_ATTR:
+				get_str_attr( p, comment.s, n, intr, script_error,1);
+				comment.len = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:run_log: unknown attribute "
+					"(%d) in LOG node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	if (comment.len==0) {
+		LOG(L_NOTICE,"NOTICE:cpl_c:run_log: LOG node has no comment attr -> "
+			"skipping\n");
+		goto done;
+	}
+
+	user.len = intr->user.len + name.len + comment.len;
+	/* duplicate the attrs in shm memory */
+	user.s = p = (char*)shm_malloc( user.len );
+	if (!user.s) {
+		LOG(L_ERR,"ERROR:cpl_c:run_log: no more shm memory!\n");
+		goto runtime_error;
+	}
+	/* copy the user name */
+	memcpy( p, intr->user.s, intr->user.len);
+	user.len = intr->user.len;
+	p += intr->user.len;
+	/* copy the log name */
+	if (name.len) {
+		memcpy( p, name.s, name.len );
+		name.s = p;
+		p += name.len;
+	}
+	/* copy the comment */
+	memcpy( p, comment.s, comment.len);
+	comment.s = p;
+
+	/* send the command */
+	write_cpl_cmd( CPL_LOG_CMD, &user, &name, &comment );
+
+done:
+	return get_first_child(intr->ip);
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_mail( struct cpl_interpreter *intr )
+{
+	unsigned short attr_name;
+	unsigned short n;
+	char  *p;
+	str subject = STR_NULL;
+	str body    = STR_NULL;
+	str to      = STR_NULL;
+	int i;
+
+	/* sanity check */
+	if (NR_OF_KIDS(intr->ip)>1) {
+		LOG(L_ERR,"ERROR:cpl_c:run_mail: MAIL node suppose to have max one"
+			" child, not %d!\n",NR_OF_KIDS(intr->ip));
+		goto script_error;
+	}
+
+	/* read the attributes of the MAIL node*/
+	for( i=NR_OF_ATTR(intr->ip),p=ATTR_PTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr(p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case TO_ATTR:
+				get_str_attr(p, to.s, n, intr, script_error,0);
+				to.len = n;
+				break;
+			case SUBJECT_ATTR:
+				get_str_attr(p, subject.s, n, intr, script_error,0);
+				subject.len = n;
+				break;
+			case BODY_ATTR:
+				get_str_attr(p, body.s, n, intr, script_error,0);
+				body.len = n;
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_mail: unknown attribute "
+					"(%d) in MAIL node\n",attr_name);
+				goto script_error;
+		}
+	}
+
+	if (to.len==0) {
+		LOG(L_ERR,"ERROR:cpl_c:run_mail: email has an empty TO hdr!\n");
+		goto script_error;
+	}
+	if (body.len==0 && subject.len==0) {
+		LOG(L_WARN,"WARNING:cpl_c:run_mail: I refuse to send email with no "
+			"body and no subject -> skipping...\n");
+		goto done;
+	}
+
+	/* duplicate the attrs in shm memory */
+	p = (char*)shm_malloc( to.len + subject.len + body.len );
+	if (!p) {
+		LOG(L_ERR,"ERROR:cpl_c:run_mail: no more shm memory!\n");
+		goto runtime_error;
+	}
+	/* copy the TO */
+	memcpy( p, to.s, to.len );
+	to.s = p;
+	p += to.len;
+	/* copy the subject */
+	if (subject.len) {
+		memcpy( p, subject.s, subject.len );
+		subject.s = p;
+		p += subject.len;
+	}
+	/* copy the body */
+	if (body.len) {
+		memcpy( p, body.s, body.len );
+		body.s = p;
+		p += body.len;
+	}
+
+	/* send the command */
+	write_cpl_cmd( CPL_MAIL_CMD, &to, &subject, &body);
+
+done:
+	return get_first_child(intr->ip);
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+static inline int run_default( struct cpl_interpreter *intr )
+{
+	if (!(intr->flags&CPL_PROXY_DONE)) {
+		/* no signaling operations */
+		if ( !(intr->flags&CPL_LOC_SET_MODIFIED) ) {
+			/*  no location modifications */
+			if (intr->loc_set==0 ) {
+				/* case 1 : no location modifications or signaling operations
+				 * performed, location set empty ->
+				 * Look up the user's location through whatever mechanism the
+				 * server would use if no CPL script were in effect */
+				return SCRIPT_DEFAULT;
+			} else {
+				/* case 2 : no location modifications or signaling operations
+				 * performed, location set non-empty: (This can only happen 
+				 * for outgoing calls.) ->
+				 * Proxy the call to the address in the location set.
+				 * With other words, let ser to continue processing the
+				 * request as nothing happened */
+				return SCRIPT_DEFAULT;
+			}
+		} else {
+			/* case 3 : location modifications performed, no signaling 
+			 * operations ->
+			 * Proxy the call to the addresses in the location set */
+			if (!cpl_proxy_to_loc_set(intr->msg,&(intr->loc_set),intr->flags))
+				return SCRIPT_END;
+			return SCRIPT_RUN_ERROR;
+		}
+	} else {
+		/* case 4 : proxy operation previously taken -> return whatever the 
+		 * "best" response is of all accumulated responses to the call to this
+		 * point, according to the rules of the underlying signaling
+		 * protocol. */
+		/* we will let ser to choose and forward one of the replies -> for this
+		 * nothing must be done */
+		return SCRIPT_END;
+	}
+	/*return SCRIPT_RUN_ERROR;*/
+}
+
+
+
+/* include all inline functions for processing the switches */
+#include "cpl_switches.h"
+/* include inline function for running proxy node */
+#include "cpl_proxy.h"
+
+
+
+int cpl_run_script( struct cpl_interpreter *intr )
+{
+	char *new_ip;
+
+	do {
+		check_overflow_by_offset( SIMPLE_NODE_SIZE(intr->ip), intr, error);
+		switch ( NODE_TYPE(intr->ip) ) {
+			case CPL_NODE:
+				DBG("DEBUG:cpl_run_script: processing CPL node \n");
+				new_ip = run_cpl_node( intr ); /*UPDATED&TESTED*/
+				break;
+			case ADDRESS_SWITCH_NODE:
+				DBG("DEBUG:cpl_run_script: processing address-switch node\n");
+				new_ip = run_address_switch( intr ); /*UPDATED&TESTED*/
+				break;
+			case STRING_SWITCH_NODE:
+				DBG("DEBUG:cpl_run_script: processing string-switch node\n");
+				new_ip = run_string_switch( intr ); /*UPDATED&TESTED*/
+				break;
+			case PRIORITY_SWITCH_NODE:
+				DBG("DEBUG:cpl_run_script: processing priority-switch node\n");
+				new_ip = run_priority_switch( intr ); /*UPDATED&TESTED*/
+				break;
+			case TIME_SWITCH_NODE:
+				DBG("DEBUG:cpl_run_script: processing time-switch node\n");
+				new_ip = run_time_switch( intr ); /*UPDATED&TESTED*/
+				break;
+			case LANGUAGE_SWITCH_NODE:
+				DBG("DEBUG:cpl_run_script: processing language-switch node\n");
+				new_ip = run_language_switch( intr ); /*UPDATED&TESTED*/
+				break;
+			case LOOKUP_NODE:
+				DBG("DEBUG:cpl_run_script: processing lookup node\n");
+				new_ip = run_lookup( intr ); /*UPDATED&TESTED*/
+				break;
+			case LOCATION_NODE:
+				DBG("DEBUG:cpl_run_script: processing location node\n");
+				new_ip = run_location( intr ); /*UPDATED&TESTED*/
+				break;
+			case REMOVE_LOCATION_NODE:
+				DBG("DEBUG:cpl_run_script: processing remove_location node\n");
+				new_ip = run_remove_location( intr ); /*UPDATED&TESTED*/
+				break;
+			case PROXY_NODE:
+				DBG("DEBUG:cpl_run_script: processing proxy node\n");
+				new_ip = run_proxy( intr );/*UPDATED&TESTED*/
+				break;
+			case REJECT_NODE:
+				DBG("DEBUG:cpl_run_script: processing reject node\n");
+				new_ip = run_reject( intr ); /*UPDATED&TESTED*/
+				break;
+			case REDIRECT_NODE:
+				DBG("DEBUG:cpl_run_script: processing redirect node\n");
+				new_ip = run_redirect( intr ); /*UPDATED&TESTED*/
+				break;
+			case LOG_NODE:
+				DBG("DEBUG:cpl_run_script: processing log node\n");
+				new_ip = run_log( intr ); /*UPDATED&TESTED*/
+				break;
+			case MAIL_NODE:
+				DBG("DEBUG:cpl_run_script: processing mail node\n");
+				new_ip = run_mail( intr ); /*UPDATED&TESTED*/
+				break;
+			case SUB_NODE:
+				DBG("DEBUG:cpl_run_script: processing sub node\n");
+				new_ip = run_sub( intr ); /*UPDATED&TESTED*/
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_run_script: unknown type node (%d)\n",
+					NODE_TYPE(intr->ip));
+				goto error;
+		}
+
+		if (new_ip==CPL_RUNTIME_ERROR) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_run_script: runtime error\n");
+			return SCRIPT_RUN_ERROR;
+		} else if (new_ip==CPL_SCRIPT_ERROR) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_run_script: script error\n");
+			return SCRIPT_FORMAT_ERROR;
+		} else if (new_ip==DEFAULT_ACTION) {
+			DBG("DEBUG:cpl_c:cpl_run_script: running default action\n");
+			return run_default(intr);
+		} else if (new_ip==EO_SCRIPT) {
+			DBG("DEBUG:cpl_c:cpl_run_script: script interpretation done!\n");
+			return SCRIPT_END;
+		} else if (new_ip==CPL_TO_CONTINUE) {
+			DBG("DEBUG:cpl_c:cpl_run_script: done for the moment; waiting "
+				"after signaling!\n");
+			return SCRIPT_TO_BE_CONTINUED;
+		}
+		/* move to the new instruction */
+		intr->ip = new_ip;
+	}while(1);
+
+error:
+	return SCRIPT_FORMAT_ERROR;
+}

+ 101 - 0
cpl-c/cpl_run.h

@@ -0,0 +1,101 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _CPL_RUN_H
+#define _CPL_RUN_H
+
+#include "../../str.h"
+#include "../../parser/msg_parser.h"
+
+#define SCRIPT_END               0
+#define SCRIPT_DEFAULT           1
+#define SCRIPT_TO_BE_CONTINUED   2
+#define SCRIPT_RUN_ERROR         -1
+#define SCRIPT_FORMAT_ERROR      -2
+
+#define CPL_RUN_OUTGOING               (1<<0)
+#define CPL_RUN_INCOMING               (1<<1)
+#define CPL_IS_STATEFUL                (1<<2)
+#define CPL_FORCE_STATEFUL             (1<<3)
+#define CPL_LOC_SET_MODIFIED           (1<<5)
+#define CPL_PROXY_DONE                 (1<<6)
+#define CPL_RURI_DUPLICATED            (1<<10)
+#define CPL_TO_DUPLICATED              (1<<11)
+#define CPL_FROM_DUPLICATED            (1<<12)
+#define CPL_SUBJECT_DUPLICATED         (1<<13)
+#define CPL_ORGANIZATION_DUPLICATED    (1<<14)
+#define CPL_USERAGENT_DUPLICATED       (1<<15)
+#define CPL_ACCEPTLANG_DUPLICATED      (1<<16)
+#define CPL_PRIORITY_DUPLICATED        (1<<17)
+
+#define STR_NOT_FOUND           ((str*)0xffffffff)
+
+
+struct cpl_interpreter {
+	unsigned int flags;
+	str user;              /* user */
+	str script;            /* CPL script */
+	char *ip;              /* instruction pointer */
+	int recv_time;         /* receiving time stamp */
+	struct sip_msg *msg;
+	struct location *loc_set;     /* location set */
+	/* pointers to the string-headers needed for switches; can point directly
+	 * into the sip_msg structure (if no proxy took places) or to private
+	 * buffers into shm_memory (after a proxy happend); if a hdr is copy into a
+	 * private buffer, a corresponding flag will be set (xxxx_DUPLICATED) */
+	str *ruri;
+	str *to;
+	str *from;
+	str *subject;
+	str *organization;
+	str *user_agent;
+	str *accept_language;
+	str *priority;
+	/* grouped date the is needed when doing proxy */
+	struct proxy_st {
+		unsigned short ordering;
+		unsigned short recurse;
+		/* I have to know which will be the last location that will be proxy */
+		struct location *last_to_proxy;
+		/* shortcuts to the subnodes */
+		char *busy;
+		char *noanswer;
+		char *redirect;
+		char *failure;
+		char *default_;
+	}proxy;
+};
+
+struct cpl_interpreter* new_cpl_interpreter( struct sip_msg *msg, str *script);
+
+void free_cpl_interpreter(struct cpl_interpreter *intr);
+
+int cpl_run_script( struct cpl_interpreter *cpl_intr );
+
+#endif
+
+

+ 129 - 0
cpl-c/cpl_sig.c

@@ -0,0 +1,129 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#include "../../action.h"
+#include "../../dset.h"
+#include "../../route.h"
+#include "../../modules/tm/tm_load.h"
+#include "loc_set.h"
+#include "cpl_sig.h"
+#include "cpl_env.h"
+
+
+/* forwards the msg to the given location set; if flags has set the
+ * CPL_PROXY_DONE, all locations will be added as branches, otherwise, the first
+ * one will set as RURI (this is ha case when this is the first proxy of the
+ * message)
+ * The given list of location will be freed, returning 0 instead.
+ * Returns:  0 - OK
+ *          -1 - error */
+int cpl_proxy_to_loc_set( struct sip_msg *msg, struct location **locs,
+													unsigned char flag)
+{
+	struct location *foo;
+	struct action act;
+	struct run_act_ctx ra_ctx;
+
+	if (!*locs) {
+		LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: empty loc set!!\n");
+		goto error;
+	}
+	
+	init_run_actions_ctx(&ra_ctx);
+	/* if it's the first time when this sip_msg is proxied, use the first addr
+	 * in loc_set to rewrite the RURI */
+	if (!(flag&CPL_PROXY_DONE)) {
+		DBG("DEBUG:cpl_c:cpl_proxy_to_loc_set: rewriting Request-URI with "
+			"<%s>\n",(*locs)->addr.uri.s);
+		/* build a new action for setting the URI */
+		memset(&act, 0, sizeof(act));
+		act.type = SET_URI_T;
+		act.val[0].type = STRING_ST;
+		act.val[0].u.string = (*locs)->addr.uri.s;
+		act.next = 0;
+		/* push the action */
+		if (do_action(&ra_ctx, &act, msg) < 0) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: do_action failed\n");
+			goto error;
+		}
+		/* is the location NATED? */
+		if ((*locs)->flags&CPL_LOC_NATED) setflag(msg,cpl_env.nat_flag);
+		/* free the location and point to the next one */
+		foo = (*locs)->next;
+		free_location( *locs );
+		*locs = foo;
+	}
+
+	/* add the rest of the locations as branches */
+	while(*locs) {
+		DBG("DEBUG:cpl_c:cpl_proxy_to_loc_set: appending branch "
+			"<%.*s>\n",(*locs)->addr.uri.len,(*locs)->addr.uri.s);
+		if(append_branch(msg, &(*locs)->addr.uri,
+				 0, 0, Q_UNSPECIFIED, 0, 0, 0, 0)==-1){
+			LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: failed when "
+				"appending branch <%s>\n",(*locs)->addr.uri.s);
+			goto error;
+		}
+		/* is the location NATED? */
+		if ((*locs)->flags&CPL_LOC_NATED) setflag(msg,cpl_env.nat_flag);
+		/* free the location and point to the next one */
+		foo = (*locs)->next;
+		free_location( *locs );
+		*locs = foo;
+	}
+
+	/* run what proxy route is set */
+	if (cpl_env.proxy_route) {
+		if (run_actions(&ra_ctx, main_rt.rlist[cpl_env.proxy_route], msg)<0) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: "
+				"Error in do_action for proxy_route\n");
+		}
+	}
+
+	/* do t_forward */
+	if ( flag&CPL_IS_STATEFUL ) {
+		/* transaction exists */
+		if (cpl_fct.tmb.t_forward_nonack(msg, 0/*no proxy*/)==-1) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: t_forward_nonack "
+				"failed !\n");
+			goto error;
+		}
+	} else {
+		/* no transaction -> build & fwd */
+		if (cpl_fct.tmb.t_relay(msg, 0, 0)==-1) {
+			LOG(L_ERR,"ERROR:cpl_c:cpl_proxy_to_loc_set: t_relay failed !\n");
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	return -1;
+}
+
+

+ 39 - 0
cpl-c/cpl_sig.h

@@ -0,0 +1,39 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef _CPL_CPL_SIG_H_
+#define _CPL_CPL_SIG_H_
+
+#include "cpl_run.h"
+
+int cpl_proxy_to_loc_set( struct sip_msg *msg, struct location **loc_set,
+													unsigned char flag);
+
+
+#endif
+

+ 1111 - 0
cpl-c/cpl_switches.h

@@ -0,0 +1,1111 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * History:
+ * -------
+ * 2003-06-27: file created (bogdan)
+ */
+
+#include "cpl_time.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_uri.h"
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_address_switch( struct cpl_interpreter *intr )
+{
+	static str def_port_str = STR_STATIC_INIT("5060");
+	unsigned short field, subfield;
+	char  *p;
+	char  *kid;
+	unsigned short  attr_name;
+	unsigned short n;
+	int i;
+	int k;
+	str cpl_val;
+	str *msg_val;
+	str *uri;
+	struct sip_uri parsed_uri;
+
+	field = subfield = UNDEF_CHAR;
+	msg_val = 0;
+
+	p=ATTR_PTR(intr->ip);
+	/* parse the attributes */
+	for( i=NR_OF_ATTR(intr->ip) ; i>0 ; i-- ) {
+		get_basic_attr( p, attr_name, n, intr, script_error);
+		switch (attr_name) {
+			case FIELD_ATTR:
+				if (field!=UNDEF_CHAR) {
+					LOG(L_ERR,"ERROR:cpl-c:run_address_switch: multiple FIELD "
+						"attrs found\n");
+					goto script_error;
+				}
+				field = n;
+				break;
+			case SUBFIELD_ATTR:
+				if (subfield!=UNDEF_CHAR) {
+					LOG(L_ERR,"ERROR:cpl-c:run_address_switch: multiple SUBFIELD"
+						" attrs found\n");
+					goto script_error;
+				}
+				subfield = n; break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:run_address_switch: unknown attribute "
+					"(%d) in ADDRESS_SWITCH node\n",*p);
+				goto script_error;
+		}
+	}
+
+	if (field==UNDEF_CHAR) {
+		LOG(L_ERR,"ERROR:cpl_c:run_address_switch: mandatory param FIELD "
+			"no found\n");
+		goto script_error;
+	}
+
+	/* test the condition from all the sub-nodes */
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case NOT_PRESENT_NODE:
+				DBG("DEBUG:run_address_switch: NOT_PRESENT node found ->"
+					"skipping (useless in this case)\n");
+				break;
+			case OTHERWISE_NODE :
+				if (i!=NR_OF_KIDS(intr->ip)-1) {
+					LOG(L_ERR,"ERROR:run_address_switch: OTHERWISE node "
+						"not found as the last sub-node!\n");
+					goto script_error;
+				}
+				DBG("DEBUG:run_address_switch: matching on OTHERWISE node\n");
+				return get_first_child(kid);
+			case ADDRESS_NODE :
+				/* check the number of attributes */
+				if (NR_OF_ATTR(kid)!=1) {
+					LOG(L_ERR,"ERROR:run_address_switch: incorrect nr of attrs "
+						"(%d) in ADDRESS node\n",NR_OF_ATTR(kid));
+					goto script_error;
+				}
+				/* get the attribute name */
+				p = ATTR_PTR(kid);
+				get_basic_attr( p, attr_name, cpl_val.len, intr, script_error);
+				if (attr_name!=IS_ATTR && attr_name!=CONTAINS_ATTR &&
+				attr_name!=SUBDOMAIN_OF_ATTR) {
+					LOG(L_ERR,"ERROR:run_address_switch: unknown attribute "
+						"(%d) in ADDRESS node\n",attr_name);
+					goto script_error;
+				}
+				/* get attribute value */
+				get_str_attr( p, cpl_val.s, cpl_val.len, intr, script_error,1);
+				DBG("DEBUG:run_address_switch: testing ADDRESS branch "
+					" attr_name=%d attr_val=[%.*s](%d)..\n",
+					attr_name,cpl_val.len,cpl_val.s,cpl_val.len);
+				/* extract the needed value from the message */
+				if (!msg_val) {
+					switch (field) {
+						case ORIGIN_VAL: /* FROM */
+							if (!intr->from) {
+								/* get the header */
+								if (parse_from_header( intr->msg )==-1)
+									goto runtime_error;
+								intr->from = &(get_from(intr->msg)->uri);
+							}
+							uri = intr->from;
+						break;
+						case DESTINATION_VAL: /* RURI */
+							if (!intr->ruri)
+								intr->ruri = GET_RURI( intr->msg );
+							uri = intr->ruri;
+							break;
+						case ORIGINAL_DESTINATION_VAL: /* TO */
+							if (!intr->to) {
+								/* get and parse the header */
+								if (!intr->msg->to &&
+								(parse_headers(intr->msg,HDR_TO_F,0)==-1 ||
+								!intr->msg->to)) {
+									LOG(L_ERR,"ERROR:run_address_switch: bad "
+										"msg or missing TO header\n");
+									goto runtime_error;
+								}
+								intr->to = &(get_to(intr->msg)->uri);
+							}
+							uri = intr->to;
+							break;
+						default:
+							LOG(L_ERR,"ERROR:run_address_switch: unknown "
+								"attribute (%d) in ADDRESS node\n",field);
+							goto script_error;
+					}
+					DBG("DEBUG:run_address_switch: extracted uri is <%.*s>\n",
+						uri->len, uri->s);
+					switch (subfield) {
+						case UNDEF_CHAR:
+							msg_val = uri;
+							break;
+						case USER_VAL:
+							if (parse_uri( uri->s, uri->len, &parsed_uri)<0)
+								goto runtime_error;
+							msg_val = &(parsed_uri.user);
+							break;
+						case HOST_VAL:
+							if (parse_uri( uri->s, uri->len, &parsed_uri)<0)
+								goto runtime_error;
+							msg_val = &(parsed_uri.host);
+							break;
+						case PORT_VAL:
+							if (parse_uri( uri->s, uri->len, &parsed_uri)<0)
+								goto runtime_error;
+							if (parsed_uri.port.len!=0)
+								msg_val = &(parsed_uri.port);
+							else
+								msg_val = &def_port_str;
+							break;
+						case TEL_VAL:
+							if (parse_uri( uri->s, uri->len, &parsed_uri)<0)
+								goto runtime_error;
+							if (parsed_uri.user_param_val.len==5 &&
+							memcmp(parsed_uri.user_param_val.s,"phone",5)==0)
+								msg_val = &(parsed_uri.user);
+							break;
+						case ADDRESS_TYPE_VAL:
+						case DISPLAY_VAL:
+						default:
+							LOG(L_ERR,"ERROR:run_address_switch: unsupported "
+								"value attribute (%d) in ADDRESS node\n",
+								subfield);
+							goto script_error;
+					}
+					DBG("DEBUG:run_address_switch: extracted val. is <%.*s>\n",
+						(msg_val==0)?0:msg_val->len, (msg_val==0)?0:msg_val->s);
+				}
+				/* does the value from script match the one from message? */
+				switch (attr_name) {
+					case IS_ATTR:
+						if ( (!msg_val && !cpl_val.s) ||
+						(msg_val && msg_val->len==cpl_val.len &&
+						strncasecmp(msg_val->s,cpl_val.s,cpl_val.len)==0)) {
+							DBG("DEBUG:run_address_switch: matching on "
+								"ADDRESS node (IS)\n");
+							return get_first_child(kid);
+						}
+						break;
+					case CONTAINS_ATTR:
+						if (subfield!=DISPLAY_VAL) {
+							LOG(L_WARN,"WARNING:run_address_switch: operator "
+							"CONTAINS applies only to DISPLAY -> ignored\n");
+						} else {
+							if ( msg_val && cpl_val.len<=msg_val->len &&
+							strcasestr_str(msg_val, &cpl_val)!=0 ) {
+								DBG("DEBUG:run_address_switch: matching on "
+									"ADDRESS node (CONTAINS)\n");
+								return get_first_child(kid);
+							}
+						}
+						break;
+					case SUBDOMAIN_OF_ATTR:
+						switch (subfield) {
+							case HOST_VAL:
+								k = msg_val->len - cpl_val.len;
+								if (k>=0 && (k==0 || msg_val->s[k-1]=='.') &&
+								!strncasecmp(cpl_val.s,msg_val->s+k,cpl_val.len)
+								) {
+									DBG("DEBUG:run_address_switch: matching on "
+										"ADDRESS node (SUBDOMAIN_OF)\n");
+									return get_first_child(kid);
+								}
+								break;
+							case TEL_VAL:
+								if (msg_val==0) break;
+								if (msg_val->len>=cpl_val.len && !strncasecmp(
+								cpl_val.s,msg_val->s,cpl_val.len)) {
+									DBG("DEBUG:run_address_switch: matching on "
+										"ADDRESS node (SUBDOMAIN_OF)\n");
+									return get_first_child(kid);
+								}
+								break;
+							default:
+								LOG(L_WARN,"WARNING:run_address_switch: operator"
+									" SUBDOMAIN_OF applies only to HOST or TEL "
+									"-> ignored\n");
+						}
+						break;
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_address_switch: unknown output node type "
+					"(%d) for ADDRESS_SWITCH node\n",NODE_TYPE(kid));
+				goto script_error;
+		}
+	}
+
+	/* none of the branches of ADDRESS_SWITCH matched -> go for default */
+	return DEFAULT_ACTION;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_string_switch( struct cpl_interpreter *intr )
+{
+	unsigned short field;
+	char *p;
+	char *kid;
+	char *not_present_node;
+	unsigned short attr_name;
+	int i;
+	str cpl_val;
+	str msg_val;
+
+	not_present_node = 0;
+	msg_val.s = 0;
+	msg_val.len = 0;
+
+	/* parse the attribute */
+	if (NR_OF_ATTR(intr->ip)!=1) {
+		LOG(L_ERR,"ERROR:cpl_c:run_string_switch: node should have 1 attr, not"
+			" (%d)\n",NR_OF_ATTR(intr->ip));
+		goto script_error;
+	}
+	p=ATTR_PTR(intr->ip);
+	get_basic_attr( p, attr_name, field, intr, script_error);
+	if (attr_name!=FIELD_ATTR) {
+		LOG(L_ERR,"ERROR:cpl_c:run_string_switch: unknown param type (%d)"
+			" for STRING_SWITCH node\n",*p);
+		goto script_error;
+	}
+
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case NOT_PRESENT_NODE:
+				if (not_present_node) {
+					LOG(L_ERR,"ERROR:run_string_switch: NOT_PRESENT node "
+						"found twice!\n");
+					goto script_error;
+				}
+				not_present_node = kid;
+				break;
+			case OTHERWISE_NODE :
+				if (i!=NR_OF_KIDS(intr->ip)-1) {
+					LOG(L_ERR,"ERROR:run_string_switch: OTHERWISE node "
+						"not found as the last sub-node!\n");
+					goto script_error;
+				}
+				DBG("DEBUG:run_string_switch: matching on OTHERWISE node\n");
+				return get_first_child(kid);
+			case STRING_NODE :
+				/* check the number of attributes */
+				if (NR_OF_ATTR(kid)!=1) {
+					LOG(L_ERR,"ERROR:run_string_switch: incorrect nr of attrs "
+						"(%d) in STRING node (expected 1)\n",NR_OF_ATTR(kid));
+					goto script_error;
+				}
+				/* get the attribute name */
+				p = ATTR_PTR(kid);
+				get_basic_attr( p, attr_name, cpl_val.len, intr, script_error);
+				if (attr_name!=IS_ATTR && attr_name!=CONTAINS_ATTR ) {
+					LOG(L_ERR,"ERROR:run_string_switch: unknown attribute "
+						"(%d) in STRING node\n",attr_name);
+					goto script_error;
+				}
+				/* get attribute value */
+				get_str_attr( p, cpl_val.s, cpl_val.len, intr, script_error,1);
+				DBG("DEBUG:run_string_switch: testing STRING branch "
+					"attr_name=%d attr_val=[%.*s](%d)..\n",
+					attr_name,cpl_val.len,cpl_val.s,cpl_val.len);
+				if (!msg_val.s) {
+					switch (field) {
+						case SUBJECT_VAL: /* SUBJECT */
+							if (intr->subject==STR_NOT_FOUND)
+								goto not_present;
+							if (!intr->subject) {
+								/* get the subject header */
+								if (!intr->msg->subject) {
+									if (parse_headers(intr->msg,
+									HDR_SUBJECT_F,0)==-1) {
+										LOG(L_ERR,"ERROR:run_string_switch: "
+										"bad SUBJECT header\n");
+										goto runtime_error;
+									} else if (!intr->msg->subject) {
+										/* hdr not present */
+										intr->subject = STR_NOT_FOUND;
+										goto not_present;
+									}
+								}
+								intr->subject =
+									&(intr->msg->subject->body);
+							}
+							trim_len( msg_val.len,msg_val.s,
+								*(intr->subject));
+							break;
+						case ORGANIZATION_VAL: /* ORGANIZATION */
+							if (intr->organization==STR_NOT_FOUND)
+								goto not_present;
+							if (!intr->organization) {
+								/* get the organization header */
+								if (!intr->msg->organization) {
+									if (parse_headers(intr->msg,
+									HDR_ORGANIZATION_F,0)==-1) {
+										LOG(L_ERR,"ERROR:run_string_switch: "
+										"bad ORGANIZATION hdr\n");
+										goto runtime_error;
+									} else if (!intr->msg->organization) {
+										/* hdr not present */
+										intr->organization = STR_NOT_FOUND;
+										goto not_present;
+									}
+								}
+								intr->organization =
+									&(intr->msg->organization->body);
+							}
+							trim_len( msg_val.len,msg_val.s,
+								*(intr->organization));
+							break;
+						case USER_AGENT_VAL: /* User Agent */
+							if (intr->user_agent==STR_NOT_FOUND)
+								goto not_present;
+							if (!intr->user_agent) {
+								/* get the  header */
+								if (!intr->msg->user_agent) {
+									if (parse_headers(intr->msg,
+									HDR_USERAGENT_F,0)==-1) {
+										LOG(L_ERR,"ERROR:run_string_switch: "
+										"bad USERAGENT hdr\n");
+										goto runtime_error;
+									} else if (!intr->msg->user_agent) {
+										/* hdr not present */
+										intr->user_agent = STR_NOT_FOUND;
+										goto not_present;
+									}
+								}
+								intr->user_agent =
+									&(intr->msg->user_agent->body);
+							}
+							trim_len( msg_val.len,msg_val.s,
+								*(intr->user_agent));
+							break;
+						default:
+							LOG(L_ERR,"ERROR:run_string_switch: unknown "
+								"attribute (%d) in STRING node\n",field);
+							goto script_error;
+					}
+					DBG("DEBUG:run_string_switch: extracted msg string is "
+						"<%.*s>\n",msg_val.len, msg_val.s);
+				}
+				/* does the value from script match the one from message? */
+				switch (attr_name) {
+					case IS_ATTR:
+						if ( (!msg_val.s && !cpl_val.s) ||
+						(msg_val.len==cpl_val.len &&
+						strncasecmp(msg_val.s,cpl_val.s,cpl_val.len)==0)) {
+							DBG("DEBUG:run_string_switch: matching on "
+								"STRING node (IS)\n");
+							return get_first_child(kid);
+						}
+						break;
+					case CONTAINS_ATTR:
+						if (cpl_val.len<=msg_val.len &&
+						strcasestr_str(&msg_val, &cpl_val)!=0 ) {
+							DBG("DEBUG:run_string_switch: matching on "
+								"STRING node (CONTAINS)\n");
+							return get_first_child(kid);
+						}
+						break;
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_string_switch: unknown output node type "
+					"(%d) for STRING_SWITCH node\n",NODE_TYPE(kid));
+				goto script_error;
+		}
+	}
+
+	/* none of the branches of STRING_SWITCH matched -> go for default */
+	return DEFAULT_ACTION;
+not_present:
+	DBG("DEBUG:run_string_switch: required hdr not present in sip msg\n");
+	if (not_present_node)
+		return get_first_child(not_present_node);
+	/* look for the NOT_PRESENT node */
+	DBG("DEBUG:run_string_switch: searching for NOT_PRESENT sub-node..\n");
+	for(; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		if (NODE_TYPE(kid)==NOT_PRESENT_NODE)
+			return get_first_child(kid);
+	}
+	return DEFAULT_ACTION;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_priority_switch( struct cpl_interpreter *intr )
+{
+	static str default_val=STR_STATIC_INIT("normal");
+	unsigned short n;
+	char *p;
+	char *kid;
+	char *not_present_node;
+	unsigned short attr_name;
+	unsigned short attr_val;
+	unsigned short msg_attr_val;
+	unsigned short msg_prio;
+	int i;
+	str cpl_val = STR_NULL;
+	str msg_val = STR_NULL;
+
+	not_present_node = 0;
+	msg_attr_val = NORMAL_VAL;
+
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case NOT_PRESENT_NODE:
+				if (not_present_node) {
+					LOG(L_ERR,"ERROR:run_priority_switch: NOT_PRESENT node "
+						"found twice!\n");
+					goto script_error;
+				}
+				not_present_node = kid;
+				break;
+			case OTHERWISE_NODE :
+				if (i!=NR_OF_KIDS(intr->ip)-1) {
+					LOG(L_ERR,"ERROR:run_priority_switch: OTHERWISE node "
+						"not found as the last sub-node!\n");
+					goto script_error;
+				}
+				DBG("DEBUG:run_priority_switch: matching on OTHERWISE node\n");
+				return get_first_child(kid);
+			case PRIORITY_NODE :
+				if (NR_OF_ATTR(kid)!=1)
+					goto script_error;
+				/* get the attribute */
+				p = ATTR_PTR(kid);
+				get_basic_attr( p, attr_name, attr_val, intr, script_error);
+				if (attr_name!=LESS_ATTR && attr_name!=GREATER_ATTR &&
+				attr_name!=EQUAL_ATTR){
+					LOG(L_ERR,"ERROR:run_priority_switch: unknown attribute "
+						"(%d) in PRIORITY node\n",attr_name);
+					goto script_error;
+				}
+				/* attribute's encoded value */
+				if (attr_val!=EMERGENCY_VAL && attr_val!=URGENT_VAL &&
+				attr_val!=NORMAL_VAL && attr_val!=NON_URGENT_VAL &&
+				attr_val!=UNKNOWN_PRIO_VAL) {
+					LOG(L_ERR,"ERROR:run_priority_switch: unknown encoded "
+						"value (%d) for attribute (*d) in PRIORITY node\n",*p);
+					goto script_error;
+				}
+				if (attr_val==UNKNOWN_PRIO_VAL) {
+					if (attr_name!=EQUAL_ATTR) {
+						LOG(L_ERR,"ERROR:cpl_c:run_priority_switch:bad PRIORITY"
+							" branch: attr=EQUAL doesn't match val=UNKNOWN\n");
+						goto script_error;
+					}
+					/* if the attr is UNKNOWN, its string value is present  */
+					get_basic_attr(p, n,cpl_val.len, intr, script_error);
+					if (n!=PRIOSTR_ATTR) {
+						LOG(L_ERR,"ERROR:run_priority_switch: expected PRIOSTR"
+							"(%d) attr, found (%d)\n",PRIOSTR_ATTR,n);
+						goto script_error;
+					}
+					get_str_attr(p, cpl_val.s, cpl_val.len,intr,script_error,1);
+				}
+
+				DBG("DEBUG:run_priority_switch: testing PRIORITY branch "
+					"(attr=%d,val=%d) [%.*s](%d)..\n",
+					attr_name,attr_val,cpl_val.len,cpl_val.s,cpl_val.len);
+				if (!msg_val.s) {
+					if (!intr->priority) {
+						/* get the PRIORITY header from message */
+						if (!intr->msg->priority) {
+							if (parse_headers(intr->msg,HDR_PRIORITY_F,0)==-1){
+								LOG(L_ERR,"ERROR:run_priority_switch: bad "
+									"sip msg or PRIORITY header !\n");
+								goto runtime_error;
+							} else if (!intr->msg->priority) {
+								LOG(L_NOTICE,"NOTICE:run_priority_switch: "
+									"missing PRIORITY header -> using "
+									"default value \"normal\"!\n");
+								intr->priority = &default_val;
+							} else {
+								intr->priority =
+									&(intr->msg->priority->body);
+							}
+						} else {
+							intr->priority =
+								&(intr->msg->priority->body);
+						}
+					}
+					trim_len( msg_val.len, msg_val.s, *(intr->priority));
+					/* encode attribute's value from SIP message */
+					if ( msg_val.len==EMERGENCY_STR_LEN &&
+					!strncasecmp(msg_val.s,EMERGENCY_STR,msg_val.len) ) {
+						msg_attr_val = EMERGENCY_VAL;
+					} else if ( msg_val.len==URGENT_STR_LEN &&
+					!strncasecmp(msg_val.s,URGENT_STR,msg_val.len) ) {
+						msg_attr_val = URGENT_VAL;
+					} else if ( msg_val.len==NORMAL_STR_LEN &&
+					!strncasecmp(msg_val.s,NORMAL_STR,msg_val.len) ) {
+						msg_attr_val = NORMAL_VAL;
+					} else if ( msg_val.len==NON_URGENT_STR_LEN &&
+					!strncasecmp(msg_val.s,NON_URGENT_STR,msg_val.len) ) {
+						msg_attr_val = NON_URGENT_VAL;
+					} else {
+						msg_attr_val = UNKNOWN_PRIO_VAL;
+					}
+					DBG("DEBUG:run_priority_switch: extracted msg priority is "
+						"<%.*s> decoded as [%d]\n",
+						msg_val.len,msg_val.s,msg_attr_val);
+				}
+				DBG("DEBUG:run_priority_switch: using msg string <%.*s>\n",
+					msg_val.len, msg_val.s);
+				/* attr_val (from cpl) cannot be UNKNOWN - we already
+				 * check it -> check only for msg_attr_val for non-EQUAL op */
+				if (msg_attr_val==UNKNOWN_PRIO_VAL && attr_name!=EQUAL_ATTR) {
+					LOG(L_NOTICE,"NOTICE:run_priority_switch: UNKNOWN "
+						"value found in sip_msg when string a LESS/GREATER "
+						"cmp -> force the value to default \"normal\"\n");
+					msg_prio = NORMAL_VAL;
+				} else {
+					msg_prio = msg_attr_val;
+				}
+				/* does the value from script match the one from message? */
+				switch (attr_name) {
+					case LESS_ATTR:
+						switch (attr_val) {
+							case EMERGENCY_VAL:
+								if (msg_prio!=EMERGENCY_VAL) break; /*OK*/
+								else continue; /* for cycle for all kids */
+							case URGENT_VAL:
+								if (msg_prio!=EMERGENCY_VAL &&
+									msg_prio!=URGENT_VAL) break; /* OK */
+								else continue; /* for cycle for all kids */
+							case NORMAL_VAL:
+								if (msg_prio==NON_URGENT_VAL) break; /*OK*/
+								else continue; /* for cycle for all kids */
+							case NON_URGENT_VAL:
+								continue; /* for cycle for all kids */
+						}
+						break;
+					case GREATER_ATTR:
+						switch (attr_val) {
+							case EMERGENCY_VAL:
+								continue; /* for cycle for all kids */
+							case URGENT_VAL:
+								if (msg_prio!=EMERGENCY_VAL) break; /*OK*/
+								else continue; /* for cycle for all kids */
+							case NORMAL_VAL:
+								if (msg_prio!=NON_URGENT_VAL &&
+									msg_prio!=NORMAL_VAL) break; /*OK*/
+								else continue; /* for cycle for all kids */
+							case NON_URGENT_VAL:
+								if (msg_prio!=NON_URGENT_VAL) break; /*OK*/
+								else continue; /* for cycle for all kids */
+						}
+						break;
+					case EQUAL_ATTR:
+						if ( attr_val==msg_prio ) {
+							if (attr_val==UNKNOWN_PRIO_VAL) {
+								if ( msg_val.len==cpl_val.len &&
+								!strncasecmp(msg_val.s,cpl_val.s,msg_val.len)){
+									break; /* OK */
+								}
+							} else {
+								break; /* OK */
+							}
+						}
+						continue; /* for cycle for all kids */
+						break;
+				} /* end switch for attr_name */
+				DBG("DEBUG:run_priority_switch: matching current "
+					"PRIORITY node\n");
+				return get_first_child(kid);
+				break;
+			default:
+				LOG(L_ERR,"ERROR:run_priority_switch: unknown output node type"
+					" (%d) for PRIORITY_SWITCH node\n",NODE_TYPE(kid));
+				goto script_error;
+		} /* end switch for NODE_TYPE */
+	} /* end for for all kids */
+
+	/* none of the branches of PRIORITY_SWITCH matched -> go for default */
+	return DEFAULT_ACTION;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+inline static int set_TZ(char *tz_env)
+{
+	DBG("DEBUG:cpl-c:set_TZ: switching TZ as \"%s\"\n",tz_env);
+	if (putenv( tz_env )==-1) {
+		LOG(L_ERR,"ERROR:cpl-c:set_TZ: setenv failed -> unable to set TZ "
+			" \"%s\"\n",tz_env);
+		return -1;
+	}
+	tzset(); /* just to be sure */
+	return 0;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_time_switch( struct cpl_interpreter *intr )
+{
+	char  *p;
+	char  *kid;
+	char  *attr_str;
+	unsigned short attr_name;
+	unsigned short attr_len;
+	unsigned char  flags = 0;
+	int nr_attrs;
+	int i,j;
+	str user_tz = STR_NULL;
+	ac_tm_t att;
+	tmrec_t trt;
+
+	DBG("DEBUG:cpl-c:run_time_switch: checking recv. time stamp <%d>\n",
+		intr->recv_time);
+	switch (NR_OF_ATTR(intr->ip)) {
+		case 1:
+			p = ATTR_PTR(intr->ip);
+			get_basic_attr( p, attr_name, user_tz.len, intr, script_error);
+			if (attr_name!=TZID_ATTR) {
+				LOG(L_ERR,"ERROR:cpl-c:run_time_switch: bad attribute -> "
+					" expected=%d, found=%d\n",TZID_ATTR,attr_name);
+				goto script_error;
+			}
+			get_str_attr( p, user_tz.s, user_tz.len, intr, script_error, 1);
+		case 0:
+			break;
+		default:
+			LOG(L_ERR,"ERROR:cpl-c:run_time_switch: incorrect number of attr ->"
+				" found=%d expected=(0,1)\n",NR_OF_ATTR(intr->ip));
+			goto script_error;
+	}
+
+	if (user_tz.s && user_tz.len) {
+		if (set_TZ(user_tz.s)==-1)
+			goto runtime_error;
+		flags |= (1<<7);
+	}
+
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case NOT_PRESENT_NODE:
+				DBG("DEBUG:cpl-c:run_time_switch: NOT_PRESENT node found ->"
+					"skipping (useless in this case)\n");
+				break;
+			case OTHERWISE_NODE :
+				if (i!=NR_OF_KIDS(intr->ip)-1) {
+					LOG(L_ERR,"ERROR:cpl-c:run_time_switch: OTHERWISE node "
+						"not found as the last sub-node!\n");
+					goto script_error;
+				}
+				DBG("DEBUG:cpl-c:run_time_switch: matching on "
+					"OTHERWISE node\n");
+				return get_first_child(kid);
+			case TIME_NODE :
+				/* init structures */
+				memset( &att, 0, sizeof(att));
+				memset( &trt, 0, sizeof(trt));
+				if(ac_tm_set_time( &att, intr->recv_time))
+					goto runtime_error;
+				/* let's see how many attributes we have */
+				nr_attrs = NR_OF_ATTR(kid);
+				/* get the attributes */
+				p = ATTR_PTR(kid);
+				for(j=0;j<nr_attrs;j++) {
+					/* get the attribute */
+					get_basic_attr( p, attr_name, attr_len, intr, script_error);
+					get_str_attr( p, attr_str, attr_len, intr, script_error,1);
+					/* process the attribute */
+					DBG("DEBUG:cpl_c:run_time_node: attribute [%d] found :"
+						"[%s]\n",attr_name, attr_str);
+					switch (attr_name) {
+						case DTSTART_ATTR:
+							if( !attr_str || tr_parse_dtstart(&trt, attr_str))
+								goto parse_err;
+							flags ^= (1<<0);
+							break;
+						case DTEND_ATTR:
+							if( !attr_str || tr_parse_dtend(&trt, attr_str))
+								goto parse_err;
+							flags ^= (1<<1);
+							break;
+						case DURATION_ATTR:
+							if( !attr_str || tr_parse_duration(&trt, attr_str))
+								goto parse_err;
+							flags ^= (1<<1);
+							break;
+						case FREQ_ATTR:
+							if( attr_str && tr_parse_freq(&trt, attr_str))
+								goto parse_err;
+							break;
+						case UNTIL_ATTR:
+							if( attr_str && tr_parse_until(&trt, attr_str))
+								goto parse_err;
+							break;
+						case INTERVAL_ATTR:
+							if( attr_str && tr_parse_interval(&trt, attr_str))
+								goto parse_err;
+							break;
+						case BYDAY_ATTR:
+							if( attr_str && tr_parse_byday(&trt, attr_str))
+								goto parse_err;
+							break;
+						case BYMONTHDAY_ATTR:
+							if( attr_str && tr_parse_bymday(&trt, attr_str))
+								goto parse_err;
+							break;
+						case BYYEARDAY_ATTR:
+							if( attr_str && tr_parse_byyday(&trt, attr_str))
+								goto parse_err;
+							break;
+						case BYMONTH_ATTR:
+							if( attr_str && tr_parse_bymonth(&trt, attr_str))
+								goto parse_err;
+							break;
+						case BYWEEKNO_ATTR:
+							if( attr_str && tr_parse_byweekno(&trt, attr_str))
+								goto parse_err;
+							break;
+						case WKST_ATTR:
+							if( attr_str && tr_parse_wkst(&trt, attr_str))
+								goto parse_err;
+							break;
+						default:
+							LOG(L_ERR,"ERROR:cpl_c:run_time_switch: "
+								"unsupported attribute [%d] found in TIME "
+								"node\n",attr_name);
+							goto script_error;
+					} /* end attribute switch */
+				} /* end for*/
+				/* check the mandatory attributes */
+				if ( (flags&0x03)!=((1<<0)|(1<<1)) ) {
+					LOG(L_ERR,"ERROR:cpl_c:run_time_switch: attribute DTSTART"
+						",DTEND,DURATION missing or multi-present\n");
+					goto script_error;
+				}
+				/* does the recv_time match the specified interval?  */
+				j = check_tmrec( &trt, &att, 0);
+				/* restore the orig TZ */
+				if ( flags&(1<<7) )
+					set_TZ(cpl_env.orig_tz.s);
+				/* free structs that I don't need any more */
+				ac_tm_free( &att );
+				tmrec_free( &trt );
+				/* let's see the result ;-) */
+				switch  (j) {
+					case 0:
+						DBG("DEBUG:run_time_switch: matching current "
+							"TIME node\n");
+						return get_first_child(kid);
+					case -1:
+						LOG(L_ERR,"ERROR:cpl_c:run_time_switch: check_tmrec "
+							"ret. err. when testing time cond. !\n");
+						goto runtime_error;
+						break;
+					case 1:
+						DBG("DEBUG:cpl_c:run_time_switch: time cond. doesn't"
+							" match !\n");
+						break;
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl-c:run_priority_switch: unknown output node"
+					" type (%d) for PRIORITY_SWITCH node\n",NODE_TYPE(kid));
+				goto script_error;
+		} /* end switch for NODE_TYPE */
+	} /* end for for all kids */
+
+
+	/* none of the branches of TIME_SWITCH matched -> go for default */
+	ac_tm_free( &att );
+	tmrec_free( &trt );
+	return DEFAULT_ACTION;
+runtime_error:
+	if ( flags&(1<<7) )
+		set_TZ(cpl_env.orig_tz.s);
+	ac_tm_free( &att );
+	tmrec_free( &trt );
+	return CPL_RUNTIME_ERROR;
+parse_err:
+	LOG(L_ERR,"ERROR:cpl-c:run_priority_switch: error parsing attr [%d][%s]\n",
+		attr_name,attr_str?(char*)attr_str:"NULL");
+script_error:
+	if ( flags&(1<<7) )
+		set_TZ(cpl_env.orig_tz.s);
+	ac_tm_free( &att );
+	tmrec_free( &trt );
+	return CPL_SCRIPT_ERROR;
+}
+
+
+
+inline static int is_lang_tag_matching(str *range,str *cpl_tag,str *cpl_subtag)
+{
+	char *c;
+	char *end;
+	str tag = STR_NULL;
+	str subtag = STR_NULL;
+
+	c = range->s;
+	end = range->s + range->len;
+
+	while(c<end) {
+		/* eat all spaces to first letter */
+		while(c<end && (*c==' ' || *c=='\t')) c++;
+		if (c==end) goto error;
+		/* init tag and subtag */
+		tag.len = 0;
+		subtag.len = 0;
+		/* get the tag */
+		tag.s = c;
+		if (*c=='*' && (c+1==end||*(c+1)!='-')) {
+			tag.len++;
+			c++;
+		} else while (c<end && ((*c)|0x20)>='a' && ((*c)|0x20)<='z' ) {
+			/*DBG("--- tag ---> <%c>[%d]\n",*c,*c);*/
+			tag.len++;
+			c++;
+		}
+		if (tag.len==0) goto error;
+		if (c<end && *c=='-') {
+			/* go for the subtag */
+			subtag.s = ++c;
+			while (c<end && ((*c)|0x20)>='a' && ((*c)|0x20)<='z' ) {
+				/*DBG("--- subtag ---> <%c>[%d]\n",*c,*c);*/
+				subtag.len++;
+				c++;
+			}
+			if (subtag.len==0) goto error;
+		} else {
+			subtag.s = 0;
+		}
+		if (c<end && *c==';') {
+			/* eat all the params to the ',' */
+			while(c<end && *c!=',') c++;
+			if (c==end) goto no_matche;
+		}
+		while(c<end && (*c==' '||*c=='\t')) c++;
+		if (c==end || *c==',') {
+			/* do compare */
+			DBG("DEBUG:cpl-c:is_lang_tag_matching: testing range [%.*s]-[%.*s]"
+				" against tag [%.*s]-[%.*s]\n",
+				tag.len,tag.s,subtag.len,subtag.s,
+				cpl_tag->len,cpl_tag->s,cpl_subtag->len,cpl_subtag->s);
+			/* language range of "*" is ignored for the purpose of matching*/
+			if ( !(tag.len==1 && *tag.s=='*') ) {
+				/* does the language tag matches ? */
+				if (tag.len==cpl_tag->len && !strncasecmp(tag.s,cpl_tag->s,
+				tag.len)) {
+					DBG("cucu bau \n");
+					/* if the subtag of the range is void -> matche */
+					if (subtag.len==0)
+						return 1;
+					/* the subtags equals -> matche */
+					if (subtag.len==cpl_subtag->len &&
+					!strncasecmp(subtag.s,cpl_subtag->s,subtag.len) )
+						return 1;
+				}
+			}
+			/* if ',' go for the next language range */
+			if (*c==',') c++;
+		} else {
+			goto error;
+		}
+	}
+
+no_matche:
+	return 0;
+error:
+	LOG(L_ERR,"ERROR:cpl-c:is_lang_tag_matching: parse error in Accept-"
+		"Language body <%.*s> at char <%c>[%d] offset %ld!\n",
+		range->len,range->s,*c,*c,(long)(c-range->s));
+	return -1;
+}
+
+
+
+/* UPDATED + CHECKED
+ */
+static inline char *run_language_switch( struct cpl_interpreter *intr )
+{
+	char  *p;
+	char  *kid;
+	char  *not_present_node;
+	unsigned short attr_name;
+	int nr_attr;
+	int i,j;
+	str attr = STR_NULL;
+	str msg_val = STR_NULL;
+	str lang_tag = STR_NULL;
+	str lang_subtag = STR_NULL;
+
+	not_present_node = 0;
+
+	for( i=0 ; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		switch ( NODE_TYPE(kid) ) {
+			case NOT_PRESENT_NODE:
+				if (not_present_node) {
+					LOG(L_ERR,"ERROR:run_language_switch: NOT_PRESENT node "
+						"found twice!\n");
+					goto script_error;
+				}
+				not_present_node = kid;
+				break;
+			case OTHERWISE_NODE :
+				if (i!=NR_OF_KIDS(intr->ip)-1) {
+					LOG(L_ERR,"ERROR:run_language_switch: OTHERWISE node "
+						"not found as the last sub-node!\n");
+					goto script_error;
+				}
+				DBG("DEBUG:run_language_switch: matching on OTHERWISE node\n");
+				return get_first_child(kid);
+			case LANGUAGE_NODE :
+				/* check the number of attributes */
+				nr_attr = NR_OF_ATTR(kid);
+				if (nr_attr<1 || nr_attr>2) {
+					LOG(L_ERR,"ERROR:run_string_switch: incorrect nr of attrs "
+						"(%d) in LANGUAGE node (1 or 2)\n",NR_OF_ATTR(kid));
+					goto script_error;
+				}
+				/* get the attributes */
+				p = ATTR_PTR(kid);
+				lang_tag.s = lang_subtag.s = 0;
+				lang_tag.len = lang_subtag.len = 0;
+				for(j=0;j<nr_attr;j++) {
+					get_basic_attr( p, attr_name, attr.len, intr, script_error);
+					get_str_attr( p, attr.s, attr.len, intr, script_error,0);
+					if (attr_name==MATCHES_TAG_ATTR ) {
+						lang_tag = attr;
+						DBG("DEBUG:cpl-c:run_language_string: language-tag is"
+							" [%.*s]\n",attr.len,attr.s);
+					}else if (attr_name==MATCHES_SUBTAG_ATTR) {
+						lang_subtag = attr;
+						DBG("DEBUG:cpl-c:run_language_string: language-subtag"
+							" is [%.*s]\n",attr.len,attr.s);
+					}else {
+						LOG(L_ERR,"ERROR:run_language_switch: unknown attribute"
+						" (%d) in LANGUAGE node\n",attr_name);
+						goto script_error;
+					}
+				}
+				
+				/* get the value from the SIP message -> if not yet, do it now
+				 * and remember it for the next times */
+				if (!msg_val.s) {
+					if (intr->accept_language==STR_NOT_FOUND)
+						goto not_present;
+					if (!intr->accept_language) {
+						/* get the accept_language header */
+						if (!intr->msg->accept_language) {
+							if (parse_headers(intr->msg,
+							HDR_ACCEPTLANGUAGE_F,0)==-1) {
+								LOG(L_ERR,"ERROR:run_language_switch: "
+									"bad ACCEPT_LANGUAGE header\n");
+								goto runtime_error;
+							} else if (!intr->msg->accept_language) {
+								/* hdr not present */
+								intr->accept_language = STR_NOT_FOUND;
+								goto not_present;
+							}
+						}
+						intr->subject =
+							&(intr->msg->accept_language->body);
+					}
+				}
+				trim_len( msg_val.len,msg_val.s, *(intr->subject));
+				DBG("DEBUG:run_language_switch: extracted msg string is "
+					"<%.*s>\n",msg_val.len, msg_val.s);
+				
+				/* does the value from script match the one from message? */
+				if (msg_val.len && msg_val.s) {
+					j = is_lang_tag_matching(&msg_val,&lang_tag,&lang_subtag);
+					if (j==1) {
+						DBG("DEBUG:run_language_switch: matching on "
+							"LANGUAGE node\n");
+						return get_first_child(kid);
+					}else if (j==-1) {
+						goto runtime_error;
+					}
+				}
+				break;
+			default:
+				LOG(L_ERR,"ERROR:cpl_c:run_language_switch: unknown output "
+					"node type (%d) for LANGUAGE_SWITCH node\n",
+					NODE_TYPE(kid));
+				goto script_error;
+		} /* end switch for NODE_TYPE */
+	} /* end for for all kids */
+
+	return DEFAULT_ACTION;
+not_present:
+	DBG("DEBUG:run_string_switch: required hdr not present in sip msg\n");
+	if (not_present_node)
+		return get_first_child(not_present_node);
+	/* look for the NOT_PRESENT node */
+	DBG("DEBUG:run_string_switch: searching for NOT_PRESENT sub-node..\n");
+	for(; i<NR_OF_KIDS(intr->ip) ; i++ ) {
+		kid = intr->ip + KID_OFFSET(intr->ip,i);
+		check_overflow_by_ptr( kid+SIMPLE_NODE_SIZE(kid), intr, script_error);
+		if (NODE_TYPE(kid)==NOT_PRESENT_NODE)
+			return get_first_child(kid);
+	}
+	return DEFAULT_ACTION;
+runtime_error:
+	return CPL_RUNTIME_ERROR;
+script_error:
+	return CPL_SCRIPT_ERROR;
+}
+
+

+ 1237 - 0
cpl-c/cpl_time.c

@@ -0,0 +1,1237 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-06-24: file imported from tmrec (bogdan)
+ * 2003-xx-xx: file Created (daniel)
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include "../../mem/mem.h"
+#include "cpl_time.h"
+
+
+/************************ imported from "utils.h"  ***************************/
+
+static inline int strz2int(char *_bp)
+{
+	int _v;
+	char *_p;
+	if(!_bp)
+		return 0;
+	_v = 0;
+	_p = _bp;
+	while(*_p && *_p>='0' && *_p<='9')
+	{
+		_v += *_p - '0';
+		_p++;
+	}
+	return _v;
+}
+
+
+static inline char* trim(char* _s)
+{
+	int len;
+	char* end;
+
+	     /* Null pointer, there is nothing to do */
+	if (!_s) return _s;
+
+	     /* Remove spaces and tabs from the beginning of string */
+	while ((*_s == ' ') || (*_s == '\t')) _s++;
+
+	len = strlen(_s);
+
+        end = _s + len - 1;
+
+	     /* Remove trailing spaces and tabs */
+	while ((*end == ' ') || (*end == '\t')) end--;
+	if (end != (_s + len - 1)) {
+		*(end+1) = '\0';
+	}
+
+	return _s;
+}
+
+
+
+
+/************************ imported from "ac_tm.c"  ***************************/
+
+/* #define USE_YWEEK_U		// Sunday system
+ * #define USE_YWEEK_V		// ISO 8601
+ */
+#ifndef USE_YWEEK_U
+#ifndef USE_YWEEK_V
+#ifndef USE_YWEEK_W
+#define USE_YWEEK_W		// Monday system
+#endif
+#endif
+#endif
+
+#ifdef USE_YWEEK_U
+#define SUN_WEEK(t)	(int)(((t)->tm_yday + 7 - \
+				((t)->tm_wday)) / 7)
+#else
+#define MON_WEEK(t)	(int)(((t)->tm_yday + 7 - \
+				((t)->tm_wday ? (t)->tm_wday - 1 : 6)) / 7)
+#endif
+
+#define ac_get_wday_yr(t) (int)((t)->tm_yday/7)
+#define ac_get_wday_mr(t) (int)(((t)->tm_mday-1)/7)
+
+ac_tm_p ac_tm_new()
+{
+	ac_tm_p _atp = NULL;
+	_atp = (ac_tm_p)pkg_malloc(sizeof(ac_tm_t));
+	if(!_atp)
+		return NULL;
+	memset(_atp, 0, sizeof(ac_tm_t));
+	
+	return _atp;
+}
+
+int ac_tm_fill(ac_tm_p _atp, struct tm* _tm)
+{
+	if(!_atp || !_tm)
+		return -1;
+	_atp->t.tm_sec = _tm->tm_sec;       /* seconds */
+	_atp->t.tm_min = _tm->tm_min;       /* minutes */
+	_atp->t.tm_hour = _tm->tm_hour;     /* hours */
+	_atp->t.tm_mday = _tm->tm_mday;     /* day of the month */
+	_atp->t.tm_mon = _tm->tm_mon;       /* month */
+	_atp->t.tm_year = _tm->tm_year;     /* year */
+	_atp->t.tm_wday = _tm->tm_wday;     /* day of the week */
+	_atp->t.tm_yday = _tm->tm_yday;     /* day in the year */
+	_atp->t.tm_isdst = _tm->tm_isdst;   /* daylight saving time */
+	
+	_atp->mweek = ac_get_mweek(_tm);
+	_atp->yweek = ac_get_yweek(_tm);
+	_atp->ywday = ac_get_wday_yr(_tm);
+	_atp->mwday = ac_get_wday_mr(_tm);
+	DBG("---> fill = %s\n",asctime(&(_atp->t)) );
+	return 0;
+}
+
+int ac_tm_set(ac_tm_p _atp, struct tm* _tm)
+{
+	if(!_atp || !_tm)
+		return -1;
+	_atp->time = mktime(_tm);
+	return ac_tm_fill(_atp, _tm);
+}
+
+int ac_tm_set_time(ac_tm_p _atp, time_t _t)
+{
+	if(!_atp)
+		return -1;
+	_atp->time = _t;
+	return ac_tm_fill(_atp, localtime(&_t));
+}
+
+int ac_get_mweek(struct tm* _tm)
+{
+	if(!_tm)
+		return -1;
+#ifdef USE_YWEEK_U
+	return ((_tm->tm_mday-1)/7 + (7-_tm->tm_wday+(_tm->tm_mday-1)%7)/7);
+#else
+	return ((_tm->tm_mday-1)/7 + (7-(6+_tm->tm_wday)%7+(_tm->tm_mday-1)%7)/7);
+#endif
+}
+
+int ac_get_yweek(struct tm* _tm)
+{
+	int week = -1;
+#ifdef USE_YWEEK_V
+	int days;
+#endif
+	
+	if(!_tm)
+		return -1;
+	
+#ifdef USE_YWEEK_U
+	week = SUN_WEEK(_tm);
+#else
+	week = MON_WEEK(_tm);
+#endif
+
+#ifdef USE_YWEEK_V
+	days = ((_tm->tm_yday + 7 - (_tm->tm_wday ? _tm->tm_wday-1 : 6)) % 7);
+
+	if(days >= 4) 
+		week++;
+	else 
+		if(week == 0) 
+			week = 53;
+#endif
+	return week;
+}
+
+int ac_get_wkst()
+{
+#ifdef USE_YWEEK_U
+	return 0;
+#else
+	return 1;
+#endif
+}
+
+int ac_tm_reset(ac_tm_p _atp)
+{
+	if(!_atp)
+		return -1;
+	memset(_atp, 0, sizeof(ac_tm_t));
+	return 0;
+}
+
+int ac_tm_free(ac_tm_p _atp)
+{
+	if(!_atp)
+		return -1;
+	if(_atp->mv)
+		pkg_free(_atp->mv);
+	/*pkg_free(_atp);*/
+	return 0;
+}
+
+ac_maxval_p ac_get_maxval(ac_tm_p _atp)
+{
+	struct tm _tm;
+	int _v;
+	ac_maxval_p _amp = NULL;
+
+	if(!_atp)
+		return NULL;
+	_amp = (ac_maxval_p)pkg_malloc(sizeof(ac_maxval_t));
+	if(!_amp)
+		return NULL;
+	
+	// the number of the days in the year
+	_amp->yday = 365 + is_leap_year(_atp->t.tm_year+1900);
+
+	// the number of the days in the month
+	switch(_atp->t.tm_mon)
+	{
+		case 1:
+			if(_amp->yday == 366)
+				_amp->mday = 29;
+			else
+				_amp->mday = 28;
+		break;
+		case 3: case 5: case 8: case 10:
+			_amp->mday = 30;
+		break;
+		default:
+			_amp->mday = 31;
+	}
+	
+	// maximum occurrences of a week day in the year
+	memset(&_tm, 0, sizeof(struct tm));
+	_tm.tm_year = _atp->t.tm_year;
+	_tm.tm_mon = 11;
+	_tm.tm_mday = 31;
+	mktime(&_tm);
+	_v = 0;
+	if(_atp->t.tm_wday > _tm.tm_wday)
+		_v = _atp->t.tm_wday - _tm.tm_wday + 1;
+	else
+		_v = _tm.tm_wday - _atp->t.tm_wday;
+	_amp->ywday = (int)((_tm.tm_yday-_v)/7) + 1;
+	
+	// maximum number of weeks in the year
+	_amp->yweek = ac_get_yweek(&_tm) + 1;
+	
+	// maximum number of the week day in the month
+	_amp->mwday=(int)((_amp->mday-1-(_amp->mday-_atp->t.tm_mday)%7)/7)+1;
+	
+	// maximum number of weeks in the month
+	_v = (_atp->t.tm_wday + (_amp->mday - _atp->t.tm_mday)%7)%7;
+#ifdef USE_YWEEK_U
+	_amp->mweek = (int)((_amp->mday-1)/7+(7-_v+(_amp->mday-1)%7)/7)+1;
+#else
+	_amp->mweek = (int)((_amp->mday-1)/7+(7-(6+_v)%7+(_amp->mday-1)%7)/7)+1;
+#endif
+
+	_atp->mv = _amp;
+	return _amp;
+}
+
+int ac_print(ac_tm_p _atp)
+{
+	static char *_wdays[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; 
+	if(!_atp)
+	{
+		printf("\n(null)\n");
+		return -1;
+	}
+	
+	printf("\nSys time: %d\nTime: %02d:%02d:%02d\n", (int)_atp->time,
+				_atp->t.tm_hour, _atp->t.tm_min, _atp->t.tm_sec);
+	printf("Date: %s, %04d-%02d-%02d\n", _wdays[_atp->t.tm_wday],
+				_atp->t.tm_year+1900, _atp->t.tm_mon+1, _atp->t.tm_mday);
+	printf("Year day: %d\nYear week-day: %d\nYear week: %d\n", _atp->t.tm_yday,
+			_atp->ywday, _atp->yweek);
+	printf("Month week: %d\nMonth week-day: %d\n", _atp->mweek, _atp->mwday);
+	if(_atp->mv)
+	{
+		printf("Max ydays: %d\nMax yweeks: %d\nMax yweekday: %d\n",
+				_atp->mv->yday, _atp->mv->yweek, _atp->mv->ywday);;
+		printf("Max mdays: %d\nMax mweeks: %d\nMax mweekday: %d\n",
+				_atp->mv->mday, _atp->mv->mweek, _atp->mv->mwday);;
+	}
+	return 0;
+}
+
+
+
+
+
+/************************ imported from "tmrec.c"  ***************************/
+
+#define _D(c) ((c) -'0')
+
+tr_byxxx_p tr_byxxx_new()
+{
+	tr_byxxx_p _bxp = NULL;
+	_bxp = (tr_byxxx_p)pkg_malloc(sizeof(tr_byxxx_t));
+	if(!_bxp)
+		return NULL;
+	memset(_bxp, 0, sizeof(tr_byxxx_t));
+	return _bxp;
+}
+
+int tr_byxxx_init(tr_byxxx_p _bxp, int _nr)
+{
+	if(!_bxp)
+		return -1;
+	_bxp->nr = _nr;
+	_bxp->xxx = (int*)pkg_malloc(_nr*sizeof(int));
+	if(!_bxp->xxx)
+		return -1;
+	_bxp->req = (int*)pkg_malloc(_nr*sizeof(int));
+	if(!_bxp->req)
+	{
+		pkg_free(_bxp->xxx);
+		return -1;
+	}
+	
+	memset(_bxp->xxx, 0, _nr*sizeof(int));
+	memset(_bxp->req, 0, _nr*sizeof(int));
+	
+	return 0;
+}
+
+
+int tr_byxxx_free(tr_byxxx_p _bxp)
+{
+	if(!_bxp)
+		return -1;
+	if(_bxp->xxx)
+		pkg_free(_bxp->xxx);
+	if(_bxp->req)
+		pkg_free(_bxp->req);
+	pkg_free(_bxp);
+	return 0;
+}
+
+tmrec_p tmrec_new()
+{
+	tmrec_p _trp = NULL;
+	_trp = (tmrec_p)pkg_malloc(sizeof(tmrec_t));
+	if(!_trp)
+		return NULL;
+	memset(_trp, 0, sizeof(tmrec_t));
+	localtime_r(&_trp->dtstart,&(_trp->ts));
+	return _trp;
+}
+
+int tmrec_free(tmrec_p _trp)
+{
+	if(!_trp)
+		return -1;
+	
+	tr_byxxx_free(_trp->byday);
+	tr_byxxx_free(_trp->bymday);
+	tr_byxxx_free(_trp->byyday);
+	tr_byxxx_free(_trp->bymonth);
+	tr_byxxx_free(_trp->byweekno);
+
+	/*pkg_free(_trp);*/
+	return 0;
+}
+
+int tr_parse_dtstart(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->dtstart = ic_parse_datetime(_in, &(_trp->ts));
+	DBG("----->dtstart = %ld | %s\n", (long)_trp->dtstart,
+									ctime(&(_trp->dtstart)));
+	return (_trp->dtstart==0)?-1:0;
+}
+
+int tr_parse_dtend(tmrec_p _trp, char *_in)
+{
+	struct tm _tm;
+	if(!_trp || !_in)
+		return -1;
+	_trp->dtend = ic_parse_datetime(_in,&_tm);
+	DBG("----->dtend = %ld | %s\n",
+								(long)_trp->dtend,
+								ctime(&(_trp->dtend)));
+	return (_trp->dtend==0)?-1:0;
+}
+
+int tr_parse_duration(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->duration = ic_parse_duration(_in);
+	return (_trp->duration==0)?-1:0;
+}
+
+int tr_parse_until(tmrec_p _trp, char *_in)
+{
+	struct tm _tm;
+	if(!_trp || !_in)
+		return -1;
+	_trp->until = ic_parse_datetime(_in, &_tm);
+	return (_trp->until==0)?-1:0;
+}
+
+int tr_parse_freq(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	if(!strcasecmp(_in, "daily"))
+	{
+		_trp->freq = FREQ_DAILY;
+		return 0;
+	}
+	if(!strcasecmp(_in, "weekly"))
+	{
+		_trp->freq = FREQ_WEEKLY;
+		return 0;
+	}
+	if(!strcasecmp(_in, "monthly"))
+	{
+		_trp->freq = FREQ_MONTHLY;
+		return 0;
+	}
+	if(!strcasecmp(_in, "yearly"))
+	{
+		_trp->freq = FREQ_YEARLY;
+		return 0;
+	}
+
+	_trp->freq = FREQ_NOFREQ;
+	return 0;
+}
+
+int tr_parse_interval(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->interval = strz2int(_in);
+	return 0;
+}
+
+int tr_parse_byday(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->byday = ic_parse_byday(_in); 
+	return 0;
+}
+
+int tr_parse_bymday(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->bymday = ic_parse_byxxx(_in); 
+	return 0;
+}
+
+int tr_parse_byyday(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->byyday = ic_parse_byxxx(_in); 
+	return 0;
+}
+
+int tr_parse_bymonth(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->bymonth = ic_parse_byxxx(_in); 
+	return 0;
+}
+
+int tr_parse_byweekno(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->byweekno = ic_parse_byxxx(_in); 
+	return 0;
+}
+
+int tr_parse_wkst(tmrec_p _trp, char *_in)
+{
+	if(!_trp || !_in)
+		return -1;
+	_trp->wkst = ic_parse_wkst(_in);
+	return 0;
+}
+
+int tr_print(tmrec_p _trp)
+{
+	static char *_wdays[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"}; 
+	int i;
+	
+	if(!_trp)
+	{
+		printf("\n(null)\n");
+		return -1;
+	}
+	printf("Recurrence definition\n-- start time ---\n");
+	printf("Sys time: %d\n", (int)_trp->dtstart);
+	printf("Time: %02d:%02d:%02d\n", _trp->ts.tm_hour, 
+				_trp->ts.tm_min, _trp->ts.tm_sec);
+	printf("Date: %s, %04d-%02d-%02d\n", _wdays[_trp->ts.tm_wday],
+				_trp->ts.tm_year+1900, _trp->ts.tm_mon+1, _trp->ts.tm_mday);
+	printf("---\n");
+	printf("End time: %d\n", (int)_trp->dtend);
+	printf("Duration: %d\n", (int)_trp->duration);
+	printf("Until: %d\n", (int)_trp->until);
+	printf("Freq: %d\n", (int)_trp->freq);
+	printf("Interval: %d\n", (int)_trp->interval);
+	if(_trp->byday)
+	{
+		printf("Byday: ");
+		for(i=0; i<_trp->byday->nr; i++)
+			printf(" %d%s", _trp->byday->req[i], _wdays[_trp->byday->xxx[i]]);
+		printf("\n");
+	}
+	if(_trp->bymday)
+	{
+		printf("Bymday: %d:", _trp->bymday->nr);
+		for(i=0; i<_trp->bymday->nr; i++)
+			printf(" %d", _trp->bymday->xxx[i]*_trp->bymday->req[i]);
+		printf("\n");
+	}
+	if(_trp->byyday)
+	{
+		printf("Byyday:");
+		for(i=0; i<_trp->byyday->nr; i++)
+			printf(" %d", _trp->byyday->xxx[i]*_trp->byyday->req[i]);
+		printf("\n");
+	}
+	if(_trp->bymonth)
+	{
+		printf("Bymonth: %d:", _trp->bymonth->nr);
+		for(i=0; i< _trp->bymonth->nr; i++)
+			printf(" %d", _trp->bymonth->xxx[i]*_trp->bymonth->req[i]);
+		printf("\n");
+	}
+	if(_trp->byweekno)
+	{
+		printf("Byweekno: ");
+		for(i=0; i<_trp->byweekno->nr; i++)
+			printf(" %d", _trp->byweekno->xxx[i]*_trp->byweekno->req[i]);
+		printf("\n");
+	}
+	printf("Weekstart: %d\n", _trp->wkst);
+	return 0;
+}
+
+time_t ic_parse_datetime(char *_in, struct tm *_tm)
+{
+	if(!_in || !_tm)
+		return 0;
+	
+	memset(_tm, 0, sizeof(struct tm));
+	_tm->tm_year = _D(_in[0])*1000 + _D(_in[1])*100 
+			+ _D(_in[2])*10 + _D(_in[3]) - 1900;
+	_tm->tm_mon = _D(_in[4])*10 + _D(_in[5]) - 1;
+	_tm->tm_mday = _D(_in[6])*10 + _D(_in[7]);
+	_tm->tm_hour = _D(_in[9])*10 + _D(_in[10]);
+	_tm->tm_min = _D(_in[11])*10 + _D(_in[12]);
+	_tm->tm_sec = _D(_in[13])*10 + _D(_in[14]);
+	_tm->tm_isdst = -1 /*daylight*/;
+	return mktime(_tm);
+}
+
+time_t ic_parse_duration(char *_in)
+{
+	time_t _t, _ft;
+	char *_p;
+	int _fl;
+	
+	if(!_in || (*_in!='+' && *_in!='-' && *_in!='P' && *_in!='p'))
+		return 0;
+	
+	if(*_in == 'P' || *_in=='p')
+		_p = _in+1;
+	else
+	{
+		if(strlen(_in)<2 || (_in[1]!='P' && _in[1]!='p'))
+			return 0;
+		_p = _in+2;
+	}
+	
+	_t = _ft = 0;
+	_fl = 1;
+	
+	while(*_p)
+	{
+		switch(*_p)
+		{
+			case '0': case '1': case '2':
+			case '3': case '4': case '5':
+			case '6': case '7': case '8':
+			case '9':
+				_t = _t*10 + *_p - '0';
+			break;
+			
+			case 'w':
+			case 'W':
+				if(!_fl)
+					return 0;
+				_ft += _t*7*24*3600;
+				_t = 0;
+			break;
+			case 'd':
+			case 'D':
+				if(!_fl)
+					return 0;
+				_ft += _t*24*3600;
+				_t = 0;
+			break;
+			case 'h':
+			case 'H':
+				if(_fl)
+					return 0;
+				_ft += _t*3600;
+				_t = 0;
+			break;
+			case 'm':
+			case 'M':
+				if(_fl)
+					return 0;
+				_ft += _t*60;
+				_t = 0;
+			break;
+			case 's':
+			case 'S':
+				if(_fl)
+					return 0;
+				_ft += _t;
+				_t = 0;
+			break;
+			case 't':
+			case 'T':
+				if(!_fl)
+					return 0;
+				_fl = 0;
+			break;
+			default:
+				return 0;
+		}
+		_p++;
+	}
+
+	return _ft;
+}
+
+tr_byxxx_p ic_parse_byday(char *_in)
+{
+	tr_byxxx_p _bxp = NULL;
+	int _nr, _s, _v;
+	char *_p;
+
+	if(!_in)
+		return NULL;
+	_bxp = tr_byxxx_new();
+	if(!_bxp)
+		return NULL;
+	_p = _in;
+	_nr = 1;
+	while(*_p)
+	{
+		if(*_p == ',')
+			_nr++;
+		_p++;
+	}
+	if(tr_byxxx_init(_bxp, _nr) < 0)
+	{
+		tr_byxxx_free(_bxp);
+		return NULL;
+	}
+	_p = _in;
+	_nr = _v = 0;
+	_s = 1;
+	while(*_p && _nr < _bxp->nr)
+	{
+		switch(*_p)
+		{
+			case '0': case '1': case '2':
+			case '3': case '4': case '5':
+			case '6': case '7': case '8':
+			case '9':
+				_v = _v*10 + *_p - '0';
+			break;
+			
+			case 's':
+			case 'S':
+				_p++;
+				switch(*_p)
+				{
+					case 'a':
+					case 'A':
+						_bxp->xxx[_nr] = WDAY_SA;
+						_bxp->req[_nr] = _s*_v;
+					break;
+					case 'u':
+					case 'U':
+						_bxp->xxx[_nr] = WDAY_SU;
+						_bxp->req[_nr] = _s*_v;
+					break;
+					default:
+						goto error;
+				}
+				_s = 1;
+				_v = 0;
+			break;
+			case 'm':
+			case 'M':
+				_p++;
+				if(*_p!='o' && *_p!='O')
+					goto error;
+				_bxp->xxx[_nr] = WDAY_MO;
+				_bxp->req[_nr] = _s*_v;
+				_s = 1;
+				_v = 0;
+			break;
+			case 't':
+			case 'T':
+				_p++;
+				switch(*_p)
+				{
+					case 'h':
+					case 'H':
+						_bxp->xxx[_nr] = WDAY_TH;
+						_bxp->req[_nr] = _s*_v;
+					break;
+					case 'u':
+					case 'U':
+						_bxp->xxx[_nr] = WDAY_TU;
+						_bxp->req[_nr] = _s*_v;
+					break;
+					default:
+						goto error;
+				}
+				_s = 1;
+				_v = 0;
+			break;
+			case 'w':
+			case 'W':
+				_p++;
+				if(*_p!='e' && *_p!='E')
+					goto error;
+				_bxp->xxx[_nr] = WDAY_WE;
+				_bxp->req[_nr] = _s*_v;
+				_s = 1;
+				_v = 0;
+			break;
+			case 'f':
+			case 'F':
+				_p++;
+				if(*_p!='r' && *_p!='R')
+					goto error;
+				_bxp->xxx[_nr] = WDAY_FR;
+				_bxp->req[_nr] = _s*_v;
+				_s = 1;
+				_v = 0;
+			break;
+			case '-':
+				_s = -1;
+			break;
+			case '+':
+			case ' ':
+			case '\t':
+			break;
+			case ',':
+				_nr++;
+			break;
+			default:
+				goto error;
+		}
+		_p++;
+	}
+
+	return _bxp;
+
+error:
+	tr_byxxx_free(_bxp);
+	return NULL;
+}
+
+tr_byxxx_p ic_parse_byxxx(char *_in)
+{
+	tr_byxxx_p _bxp = NULL;
+	int _nr, _s, _v;
+	char *_p;
+
+	if(!_in)
+		return NULL;
+	_bxp = tr_byxxx_new();
+	if(!_bxp)
+		return NULL;
+	_p = _in;
+	_nr = 1;
+	while(*_p)
+	{
+		if(*_p == ',')
+			_nr++;
+		_p++;
+	}
+	if(tr_byxxx_init(_bxp, _nr) < 0)
+	{
+		tr_byxxx_free(_bxp);
+		return NULL;
+	}
+	_p = _in;
+	_nr = _v = 0;
+	_s = 1;
+	while(*_p && _nr < _bxp->nr)
+	{
+		switch(*_p)
+		{
+			case '0': case '1': case '2':
+			case '3': case '4': case '5':
+			case '6': case '7': case '8':
+			case '9':
+				_v = _v*10 + *_p - '0';
+			break;
+			
+			case '-':
+				_s = -1;
+			break;
+			case '+':
+			case ' ':
+			case '\t':
+			break;
+			case ',':
+				_bxp->xxx[_nr] = _v;
+				_bxp->req[_nr] = _s;
+				_s = 1;
+				_v = 0;
+				_nr++;
+			break;
+			default:
+				goto error;
+		}
+		_p++;
+	}
+	if(_nr < _bxp->nr)
+	{
+		_bxp->xxx[_nr] = _v;
+		_bxp->req[_nr] = _s;
+	}
+	return _bxp;
+
+error:
+	tr_byxxx_free(_bxp);
+	return NULL;
+}
+
+int ic_parse_wkst(char *_in)
+{
+	if(!_in || strlen(_in)!=2)
+		goto error;
+	
+	switch(_in[0])
+	{
+		case 's':
+		case 'S':
+			switch(_in[1])
+			{
+				case 'a':
+				case 'A':
+					return WDAY_SA;
+				case 'u':
+				case 'U':
+					return WDAY_SU;
+				default:
+					goto error;
+			}
+		case 'm':
+		case 'M':
+			if(_in[1]!='o' && _in[1]!='O')
+				goto error;
+			return WDAY_MO;
+		case 't':
+		case 'T':
+			switch(_in[1])
+			{
+				case 'h':
+				case 'H':
+					return WDAY_TH;
+				case 'u':
+				case 'U':
+					return WDAY_TU;
+				default:
+					goto error;
+			}
+		case 'w':
+		case 'W':
+			if(_in[1]!='e' && _in[1]!='E')
+				goto error;
+			return WDAY_WE;
+		case 'f':
+		case 'F':
+			if(_in[1]!='r' && _in[1]!='R')
+				goto error;
+			return WDAY_FR;
+		break;
+		default:
+			goto error;
+	}
+	
+error:
+#ifdef USE_YWEEK_U
+	return WDAY_SU;
+#else
+	return WDAY_MO;
+#endif
+}
+
+
+
+
+
+
+/*********************** imported from "checktr.c"  **************************/
+
+#define REC_ERR    -1
+#define REC_MATCH   0
+#define REC_NOMATCH 1
+
+#define _IS_SET(x) (((x)>0)?1:0)
+
+/*** local headers ***/
+int get_min_interval(tmrec_p);
+int check_min_unit(tmrec_p, ac_tm_p, tr_res_p);
+int check_freq_interval(tmrec_p _trp, ac_tm_p _atp);
+int check_byxxx(tmrec_p, ac_tm_p);
+
+/**
+ *
+ * return 0/REC_MATCH - the time falls in
+ *       -1/REC_ERR - error
+ *        1/REC_NOMATCH - the time falls out
+ */
+int check_tmrec(tmrec_p _trp, ac_tm_p _atp, tr_res_p _tsw)
+{
+	if(!_trp || !_atp || (!_IS_SET(_trp->duration) && !_IS_SET(_trp->dtend)))
+		return REC_ERR;
+
+	// it is before start date
+	if(_atp->time < _trp->dtstart)
+		return REC_NOMATCH;
+	
+	// compute the duration of the recurrence interval
+	if(!_IS_SET(_trp->duration))
+		_trp->duration = _trp->dtend - _trp->dtstart;
+	
+	if(_atp->time <= _trp->dtstart+_trp->duration)
+	{
+		if(_tsw)
+		{
+			if(_tsw->flag & TSW_RSET)
+			{
+				if(_tsw->rest>_trp->dtstart+_trp->duration-_atp->time)
+					_tsw->rest = _trp->dtstart+_trp->duration - _atp->time;
+			}
+			else
+			{
+				_tsw->flag |= TSW_RSET;
+				_tsw->rest = _trp->dtstart+_trp->duration - _atp->time;
+			}
+		}
+		return REC_MATCH;
+	}
+	
+	// after the bound of recurrence
+	if(_IS_SET(_trp->until) && _atp->time >= _trp->until + _trp->duration)
+		return REC_NOMATCH;
+	
+	// check if the instance of recurrence matches the 'interval'
+	if(check_freq_interval(_trp, _atp)!=REC_MATCH)
+		return REC_NOMATCH;
+
+	if(check_min_unit(_trp, _atp, _tsw)!=REC_MATCH)
+		return REC_NOMATCH;
+
+	if(check_byxxx(_trp, _atp)!=REC_MATCH)
+		return REC_NOMATCH;
+
+	return REC_MATCH;
+}
+
+
+int check_freq_interval(tmrec_p _trp, ac_tm_p _atp)
+{
+	int _t0, _t1;
+	struct tm _tm;
+	if(!_trp || !_atp)
+		return REC_ERR;
+	
+	if(!_IS_SET(_trp->freq))
+		return REC_NOMATCH;
+	
+	if(!_IS_SET(_trp->interval) || _trp->interval==1)
+		return REC_MATCH;
+	
+	switch(_trp->freq)
+	{
+		case FREQ_DAILY:
+		case FREQ_WEEKLY:
+			memset(&_tm, 0, sizeof(struct tm));
+			_tm.tm_year = _trp->ts.tm_year;
+			_tm.tm_mon = _trp->ts.tm_mon;
+			_tm.tm_mday = _trp->ts.tm_mday;
+			_t0 = (int)mktime(&_tm);
+			memset(&_tm, 0, sizeof(struct tm));
+			_tm.tm_year = _atp->t.tm_year;
+			_tm.tm_mon = _atp->t.tm_mon;
+			_tm.tm_mday = _atp->t.tm_mday;
+			_t1 = (int)mktime(&_tm);
+			if(_trp->freq == FREQ_DAILY)
+				return (((_t1-_t0)/(24*3600))%_trp->interval==0)?
+					REC_MATCH:REC_NOMATCH;
+#ifdef USE_YWEEK_U
+			_t0 -= _trp->ts.tm_wday*24*3600;
+			_t1 -= _atp->t.tm_wday*24*3600;
+#else
+			_t0 -= ((_trp->ts.tm_wday+6)%7)*24*3600;
+			_t1 -= ((_atp->t.tm_wday+6)%7)*24*3600;
+#endif
+			return (((_t1-_t0)/(7*24*3600))%_trp->interval==0)?
+					REC_MATCH:REC_NOMATCH;
+		case FREQ_MONTHLY:
+			_t0 = (_atp->t.tm_year-_trp->ts.tm_year)*12
+					+ _atp->t.tm_mon-_trp->ts.tm_mon;
+			return (_t0%_trp->interval==0)?REC_MATCH:REC_NOMATCH;
+		case FREQ_YEARLY:
+			return ((_atp->t.tm_year-_trp->ts.tm_year)%_trp->interval==0)?
+					REC_MATCH:REC_NOMATCH;
+	}
+	
+	return REC_NOMATCH;
+}
+
+int get_min_interval(tmrec_p _trp)
+{
+	if(!_trp)
+		return FREQ_NOFREQ;
+	
+	if(_trp->freq == FREQ_DAILY || _trp->byday || _trp->bymday || _trp->byyday)
+		return FREQ_DAILY;
+	if(_trp->freq == FREQ_WEEKLY || _trp->byweekno) 
+		return FREQ_WEEKLY;
+	if(_trp->freq == FREQ_MONTHLY || _trp->bymonth)
+		return FREQ_MONTHLY;
+	if(_trp->freq == FREQ_YEARLY)
+		return FREQ_YEARLY;
+	
+	return FREQ_NOFREQ;
+}
+
+int check_min_unit(tmrec_p _trp, ac_tm_p _atp, tr_res_p _tsw)
+{
+	int _v0, _v1;
+	if(!_trp || !_atp)
+		return REC_ERR;
+	switch(get_min_interval(_trp))
+	{
+		case FREQ_DAILY:
+		break;
+		case FREQ_WEEKLY:
+			if(_trp->ts.tm_wday != _atp->t.tm_wday)
+				return REC_NOMATCH;
+		break;
+		case FREQ_MONTHLY:
+			if(_trp->ts.tm_mday != _atp->t.tm_mday)
+				return REC_NOMATCH;
+		break;
+		case FREQ_YEARLY:
+			if(_trp->ts.tm_mon != _atp->t.tm_mon 
+					|| _trp->ts.tm_mday != _atp->t.tm_mday)
+				return REC_NOMATCH;
+		break;
+		default:
+			return REC_NOMATCH;
+	}
+	_v0 = _trp->ts.tm_hour*3600 + _trp->ts.tm_min*60 + _trp->ts.tm_sec;
+	_v1 = _atp->t.tm_hour*3600 + _atp->t.tm_min*60 + _atp->t.tm_sec;
+	if(_v1 >= _v0 && _v1 < _v0 + _trp->duration)
+	{
+		if(_tsw)
+		{
+			if(_tsw->flag & TSW_RSET)
+			{
+				if(_tsw->rest>_v0+_trp->duration-_v1)
+					_tsw->rest = _v0 + _trp->duration - _v1;
+			}
+			else
+			{
+				_tsw->flag |= TSW_RSET;
+				_tsw->rest = _v0 + _trp->duration - _v1;
+			}
+		}
+		return REC_MATCH;
+	}
+	
+	return REC_NOMATCH;
+}
+
+int check_byxxx(tmrec_p _trp, ac_tm_p _atp)
+{
+	int i;
+	ac_maxval_p _amp = NULL;
+	if(!_trp || !_atp)
+		return REC_ERR;
+	if(!_trp->byday && !_trp->bymday && !_trp->byyday && !_trp->bymonth 
+			&& !_trp->byweekno)
+		return REC_MATCH;
+	
+	_amp = ac_get_maxval(_atp);
+	if(!_amp)
+		return REC_NOMATCH;
+	
+	if(_trp->bymonth)
+	{
+		for(i=0; i<_trp->bymonth->nr; i++)
+		{
+			if(_atp->t.tm_mon == 
+					(_trp->bymonth->xxx[i]*_trp->bymonth->req[i]+12)%12)
+				break;
+		}
+		if(i>=_trp->bymonth->nr)
+			return REC_NOMATCH;
+	}
+	if(_trp->freq==FREQ_YEARLY && _trp->byweekno)
+	{
+		for(i=0; i<_trp->byweekno->nr; i++)
+		{
+			if(_atp->yweek == (_trp->byweekno->xxx[i]*_trp->byweekno->req[i]+
+							_amp->yweek)%_amp->yweek)
+				break;
+		}
+		if(i>=_trp->byweekno->nr)
+			return REC_NOMATCH;
+	}
+	if(_trp->byyday)
+	{
+		for(i=0; i<_trp->byyday->nr; i++)
+		{
+			if(_atp->t.tm_yday == (_trp->byyday->xxx[i]*_trp->byyday->req[i]+
+						_amp->yday)%_amp->yday)
+				break;
+		}
+		if(i>=_trp->byyday->nr)
+			return REC_NOMATCH;
+	}
+	if(_trp->bymday)
+	{
+		for(i=0; i<_trp->bymday->nr; i++)
+		{
+#ifdef EXTRA_DEBUG
+			DBG("Req:bymday: %d == %d\n", _atp->t.tm_mday,
+				(_trp->bymday->xxx[i]*_trp->bymday->req[i]+
+				_amp->mday)%_amp->mday + ((_trp->bymday->req[i]<0)?1:0));
+#endif
+			if(_atp->t.tm_mday == (_trp->bymday->xxx[i]*_trp->bymday->req[i]+
+						_amp->mday)%_amp->mday + (_trp->bymday->req[i]<0)?1:0)
+				break;
+		}
+		if(i>=_trp->bymday->nr)
+			return REC_NOMATCH;
+	}
+	if(_trp->byday)
+	{
+		for(i=0; i<_trp->byday->nr; i++)
+		{
+			if(_trp->freq==FREQ_YEARLY)
+			{
+#ifdef EXTRA_DEBUG
+				DBG("Req:byday:y: %d==%d && %d==%d\n", _atp->t.tm_wday,
+					_trp->byday->xxx[i], _atp->ywday+1, 
+					(_trp->byday->req[i]+_amp->ywday)%_amp->ywday);
+#endif
+				if(_atp->t.tm_wday == _trp->byday->xxx[i] &&
+						_atp->ywday+1 == (_trp->byday->req[i]+_amp->ywday)%
+						_amp->ywday)
+					break;
+			}
+			else
+			{
+				if(_trp->freq==FREQ_MONTHLY)
+				{
+#ifdef EXTRA_DEBUG
+					DBG("Req:byday:m: %d==%d && %d==%d\n", _atp->t.tm_wday,
+						_trp->byday->xxx[i], _atp->mwday+1, 
+						(_trp->byday->req[i]+_amp->mwday)%_amp->mwday);
+#endif
+					if(_atp->t.tm_wday == _trp->byday->xxx[i] &&
+							_atp->mwday+1==(_trp->byday->req[i]+
+							_amp->mwday)%_amp->mwday)
+						break;
+				}
+				else
+				{
+					if(_atp->t.tm_wday == _trp->byday->xxx[i])
+						break;
+				}
+			}
+		}
+		if(i>=_trp->byday->nr)
+			return REC_NOMATCH;
+	}
+
+	return REC_MATCH;
+}
+
+

+ 181 - 0
cpl-c/cpl_time.h

@@ -0,0 +1,181 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-06-24: file imported from tmrec (bogdan)
+ * 2003-xx-xx: file Created (daniel)
+ */
+
+#ifndef _CPL_TIME_H_
+#define _CPL_TIME_H_
+
+
+/************************ imported from "ac_tm.h"  ***************************/
+
+#include <time.h>
+
+
+/* USE_YWEEK_U	-- Sunday system - see strftime %U
+ * USE_YWEEK_V	-- ISO 8601 - see strftime %V
+ * USE_YWEEK_W	-- Monday system - see strftime %W
+*/
+
+#ifndef USE_YWEEK_U
+# ifndef USE_YWEEK_V
+#  ifndef USE_YWEEK_W
+#   define USE_YWEEK_W
+#  endif
+# endif
+#endif
+
+#define is_leap_year(yyyy) ((((yyyy)%400))?(((yyyy)%100)?(((yyyy)%4)?0:1):0):1)
+
+
+typedef struct _ac_maxval
+{
+	int yweek;
+	int yday;
+	int ywday;
+	int mweek;
+	int mday;
+	int mwday;
+} ac_maxval_t, *ac_maxval_p;
+
+typedef struct _ac_tm
+{
+	time_t time;
+	struct tm t;
+	int mweek;
+	int yweek;
+	int ywday;
+	int mwday;
+	ac_maxval_p mv;
+} ac_tm_t, *ac_tm_p;
+
+ac_tm_p ac_tm_new();
+
+int ac_tm_set(ac_tm_p, struct tm*);
+int ac_tm_set_time(ac_tm_p, time_t);
+
+int ac_tm_reset(ac_tm_p);
+int ac_tm_free(ac_tm_p);
+
+int ac_get_mweek(struct tm*);
+int ac_get_yweek(struct tm*);
+ac_maxval_p ac_get_maxval(ac_tm_p);
+int ac_get_wkst();
+
+int ac_print(ac_tm_p);
+
+
+
+
+/************************ imported from "tmrec.h"  ***************************/
+
+
+#define FREQ_NOFREQ  0
+#define FREQ_YEARLY  1
+#define FREQ_MONTHLY 2
+#define FREQ_WEEKLY  3
+#define FREQ_DAILY   4
+
+#define WDAY_SU 0
+#define WDAY_MO 1
+#define WDAY_TU 2
+#define WDAY_WE 3
+#define WDAY_TH 4
+#define WDAY_FR 5
+#define WDAY_SA 6
+#define WDAY_NU 7
+
+#define TSW_TSET	1
+#define TSW_RSET	2
+
+typedef struct _tr_byxxx
+{
+	int nr;
+	int *xxx;
+	int *req;
+} tr_byxxx_t, *tr_byxxx_p;
+
+typedef struct _tmrec
+{
+	time_t dtstart;
+	struct tm ts;
+	time_t dtend;
+	time_t duration;
+	time_t until;
+	int freq;
+	int interval;
+	tr_byxxx_p byday;
+	tr_byxxx_p bymday;
+	tr_byxxx_p byyday;
+	tr_byxxx_p bymonth;
+	tr_byxxx_p byweekno;
+	int wkst;
+} tmrec_t, *tmrec_p;
+
+typedef struct _tr_res
+{
+	int flag;
+	time_t rest;
+} tr_res_t, *tr_res_p;
+
+tr_byxxx_p tr_byxxx_new();
+int tr_byxxx_init(tr_byxxx_p, int);
+int tr_byxxx_free(tr_byxxx_p);
+
+tmrec_p tmrec_new();
+int tmrec_free(tmrec_p);
+
+int tr_parse_dtstart(tmrec_p, char*);
+int tr_parse_dtend(tmrec_p, char*);
+int tr_parse_duration(tmrec_p, char*);
+int tr_parse_until(tmrec_p, char*);
+int tr_parse_freq(tmrec_p, char*);
+int tr_parse_interval(tmrec_p, char*);
+int tr_parse_byday(tmrec_p, char*);
+int tr_parse_bymday(tmrec_p, char*);
+int tr_parse_byyday(tmrec_p, char*);
+int tr_parse_bymonth(tmrec_p, char*);
+int tr_parse_byweekno(tmrec_p, char*);
+int tr_parse_wkst(tmrec_p, char*);
+
+int tr_print(tmrec_p);
+time_t ic_parse_datetime(char*,struct tm*);
+time_t ic_parse_duration(char*);
+
+tr_byxxx_p ic_parse_byday(char*);
+tr_byxxx_p ic_parse_byxxx(char*);
+int ic_parse_wkst(char*);
+
+int check_tmrec(tmrec_p, ac_tm_p, tr_res_p);
+
+
+#endif
+

+ 63 - 0
cpl-c/cpl_utils.h

@@ -0,0 +1,63 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ *
+ * History:
+ * -------
+ * 2003-06-24: file created (bogdan)
+
+ */
+
+#ifndef _CPL_UTILS_H
+#define _CPL_UTILS_H
+
+#include <ctype.h>
+#include "../../str.h"
+
+
+/* looks for s2 into s1 */
+static inline char *strcasestr_str(str *s1, str *s2)
+{
+	int i,j;
+	for(i=0;i<s1->len-s2->len;i++) {
+		for(j=0;j<s2->len;j++) {
+			if ( !((s1->s[i+j]==s2->s[j]) ||
+			( isalpha((int)s1->s[i+j]) && ((s1->s[i+j])^(s2->s[j]))==0x20 )) )
+				break;
+		}
+		if (j==s2->len)
+			return s1->s+i;
+	}
+	return 0;
+}
+
+
+
+#endif
+
+
+
+

+ 4 - 0
cpl-c/doc/Makefile

@@ -0,0 +1,4 @@
+docs = cpl-c.xml
+
+docbook_dir=../../../docbook
+include $(docbook_dir)/Makefile.module

+ 97 - 0
cpl-c/doc/cpl-c.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="cpl-c" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Bogdan-Andrei</firstname>
+		<surname>Iancu</surname>
+		<affiliation><orgname>FhG FOKUS</orgname></affiliation>
+		<address>
+		    <email>[email protected]</email>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+    </sectioninfo>
+    
+    <title>CPL Module</title>
+
+    <section id="cpl-c.overview">
+	<title>Overview</title>
+	<para>
+	    cpl-c modules implements a CPL (Call Processing Language)
+	    interpreter. Support for uploading/downloading/removing scripts via
+	    SIP REGISTER method is implemented.
+	</para>
+    </section>
+    
+    <section id="cpl-c.dep">
+	<title>Dependencies</title>
+	<section id="cpl-c.modules">
+	    <title>SER Modules</title>
+	    <para>
+		The following modules must be loaded before this module:
+		<itemizedlist>
+		    <listitem>
+			<formalpara>
+			    <title>tm</title> 
+			    <para>
+				Transaction Manager, used for proxying/forking
+				requests.
+			    </para>
+			</formalpara>
+		    </listitem>
+		    <listitem>
+			<formalpara>
+			    <title>sl</title>
+			    <para>
+				StateLess module - used for sending stateless 
+				reply when responding to REGISTER request or for sending back 
+				error responses.
+			    </para>
+			</formalpara>
+		    </listitem>
+		    <listitem>
+			<formalpara>
+			    <title>usrloc</title>
+			    <para>
+				User location module - used for implementing
+				lookup("registration") (adding into location set of the
+				users' contact)
+			    </para>
+			</formalpara>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+	
+	<section id="libraries">
+	    <title>External Libraries or Applications</title>
+	    <para>
+		The following libraries or applications must be installed
+		before running SER with this module loaded:
+		<itemizedlist>
+		    <listitem>
+			<formalpara>
+			    <title>libxml2</title>
+			    <para>
+				This library contains an engine for XML
+ 				parsing, DTD validation and DOM manipulation.
+			    </para>
+			</formalpara>
+		    </listitem>
+		</itemizedlist>
+	    </para>
+	</section>
+    </section>
+
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+
+</section>

+ 135 - 0
cpl-c/doc/functions.xml

@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="cpl-c.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Functions</title>
+
+    <section id="cpl_run_script">
+	<title>
+	    <function>cpl_run_script(type,mode)</function>
+	</title>
+	<para>
+	    Starts the execution of the CPL script. The user name is fetched
+	    from new_uri or requested uri or from To header -in this order-
+	    (for incoming execution) or from FROM header (for outgoing
+	    execution).  Regarding the stateful/stateless message processing,
+	    the function is very flexible, being able to run in different modes
+	    (see below the"mode" parameter).  Normally this function will end
+	    script execution.  There is no guaranty that the CPL script
+	    interpretation ended when ser script ended also (for the same
+	    INVITE ;-)) - this can happen when the CPL script does a PROXY and
+	    the script interpretation pause after proxying and it will be
+	    resume when some reply is received (this can happen in a different
+	    process of SER).  If the function returns to script, the SIP server
+	    should continue with the normal behavior as if no script existed.
+	    When some error is returned, the function itself haven't sent any
+	    SIP error reply (this can be done from script).
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+		    <emphasis>type</emphasis> - which part of the script should
+		    be run; set it to "incoming" for having the incoming part
+		    of script executed (when an INVITE is received) or to
+		    "outgoing" for running the outgoing part of script (when a
+		    user is generating an INVITE - call).
+		</para>
+	    </listitem>
+	    <listitem>
+		<para>
+		    <emphasis>mode</emphasis> - sets the interpreter mode as
+		    stateless/stateful behavior. The following modes are
+		    accepted:
+		</para>
+		<itemizedlist>
+		    <listitem>
+			<para>
+			    <emphasis>IS_STATELESS</emphasis> - the current
+				    INVITE has no transaction created yet. All
+				    replies (redirection or deny) will be done
+				    is a stateless way. The execution will
+				    switch to stateful only when proxy is
+				    done. So, if the function returns, will be
+				    in stateless mode.
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+			    <emphasis>IS_STATEFUL</emphasis> - the current
+				    INVITE has already a transaction
+				    associated. All signaling operations
+				    (replies or proxy) will be done in stateful
+				    way.So, if the function returns, will be in
+				    stateful mode.
+			</para>
+		    </listitem>
+		    <listitem>
+			<para>
+			    <emphasis>FORCE_STATEFUL</emphasis> - the current
+				    INVITE has no transaction created yet. All
+				    signaling operations will be done is a
+				    stateful way (on signaling, the transaction
+				    will be created from within the
+				    interpreter).  So, if the function returns,
+				    will be in stateless mode.
+			</para>
+		    </listitem>
+		</itemizedlist>
+		<note>
+		    <para>
+			is_stateful is very difficult to manage from the
+			routing script (script processing can continue in
+			stateful mode); is_stateless is the fastest and
+			consumes less resources (transaction is created only if
+			proxying is done), but there is only a minimal
+			protection against retransmissions (since replies are
+			send statelessly); force_stateful is a good compromise
+			- all signaling is done stateful (retransmission
+			protection) and in the same time, if returning to
+			script, it will be in stateless mode (easy to continue
+			the routing script execution)
+		    </para>
+		</note>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>cpl_run_script</function> usage</title>
+	    <programlisting>
+...
+cpl_run_script("incoming","force_stateful");
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="cpl_process_register">
+	<title>
+	    <function moreinfo="none">cpl_process_register()</function>
+	</title>
+	<para>
+	    This function MUST be called only for REGISTER requests. It checks
+	    if the current REGISTER request is related or not with CPL script
+	    upload/download/ remove. If it is, all the needed operation will be
+	    done. For checking if the REGISTER is CPL related, the function
+	    looks fist to "Content-Type" header. If it exists and has a the
+	    mime type set to "application/cpl+xml" means this is a CPL script
+	    upload/remove operation. The distinction between to case is made by
+	    looking at "Content-Disposition" header; id its value is
+	    "script;action=store", means it's an upload; if it's
+	    "script;action=remove", means it's a remove operation; other values
+	    are considered to be errors. If no "Content-Type" header is
+	    present, the function looks to "Accept" header and if it contains
+	    the "*" or "application/cpl-xml" the request it will be consider
+	    one for downloading CPL scripts.  The functions returns to script
+	    only if the REGISTER is not related to CPL. In other case, the
+	    function will send by itself the necessary replies (stateless -
+	    using sl), including for errors.
+	</para>
+    </section>
+
+</section>

+ 186 - 0
cpl-c/doc/params.xml

@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE section PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" 
+   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<section id="cpl-c.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="cpl_db">
+	<title><varname>cpl_db</varname> (string)</title>
+	<para>
+	    A SQL URL have to be given to the module for knowing where the 
+	    database containing the table with CPL scripts is locates. If 
+	    required a user name and password can be specified for allowing 
+	    the module to connect to the database server.
+	</para>
+	<warning>
+	    <para>
+		This parameter is mandatory.
+	    </para>
+	</warning>
+	<example>
+	    <title>Set <varname>cpl_db</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","cpl_db","mysql://user:passwd@host/database")
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="cpl_table">
+	<title><varname>cpl_table</varname> (string)</title>
+	<para>
+	    Indicates the name of the table that store the CPL scripts. 
+	    This table must be locate into the database specified by 
+	    "cpl_db" parameter. For more about the format of the CPL table 
+	    please see <filename>modules/cpl-c/init.mysql</filename>.
+	</para>
+	<warning>
+	    <para>
+		This parameter is mandatory.
+	    </para>
+	</warning>
+	<example>
+	    <title>Set <varname>cpl_table</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","cpl_table","cpltable")
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="cpl_dtd_file">
+	<title><varname>cpl_dtd_file</varname> (string)</title>
+	<para>
+	    Points to the DTD file describing the CPL grammar. The file 
+	    name may include also the path to the file. This path can be 
+	    absolute or relative (be careful the path will be relative 
+	    to the starting directory of SER).
+	</para>
+	<warning>
+	    <para>
+		This parameter is mandatory.
+	    </para>
+	</warning>
+	<example>
+	    <title>Set <varname>cpl_dtd_file</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","cpl_dtd_file","/etc/ser/cpl-06.dtd")
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="log_dir">
+	<title><varname>log_dir</varname> (string)</title>
+	<para>
+	    Points to a directory where should be created all the log file 
+	    generated by the LOG CPL node. A log file per user will be 
+	    created (on demand) having the name username.log.
+	</para>
+	<note>
+	    <para>
+		If this parameter is absent, the logging will be disabled 
+		without generating error on execution.
+	    </para>
+	</note>
+	<example>
+	    <title>Set <varname>log_dir</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","log_dir","/var/log/ser/cpl")
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="proxy_recurse">
+	<title><varname>proxy_recurse</varname> (int)</title>
+	<para>
+	    Tells for how many time is allow to have recurse for PROXY CPL 
+	    node If it has value 2, when doing proxy, only twice the proxy 
+	    action will be re-triggered by  a redirect response; the third 
+	    time, the proxy execution will end by going on REDIRECTION 
+	    branch. The recurse feature can be disable by setting this 
+	    parameter to 0 
+	</para>
+	<para>
+	    Default value of this parameter is 0.
+	</para>
+	<example>
+	    <title>Set <varname>proxy_recurse</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","proxy_recurse",2)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="proxy_route">
+	<title><varname>proxy_route</varname> (int)</title>
+	<para>
+	    Before doing proxy (forward), a script route can be executed.
+	    All modifications made by that route will be reflected only for
+	    the current branch.
+	</para>
+	<para>
+	    Default value of this parameter is 0 (none).
+	</para>
+	<example>
+	    <title>Set <varname>proxy_route</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","proxy_route",1)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="nat_flag">
+	<title><varname>nat_flag</varname> (int)</title>
+	<para>
+	    Sets the flag used for marking calls via NAT. Used by lookup
+	    tag when retrieving a contact behind a NAT (this flag will be 
+	    set).
+	</para>
+	<para>
+	    Default value of this parameter is 6.
+	</para>
+	<example>
+	    <title>Set <varname>nat_flag</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","nat_flag",4)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="lookup_domain">
+	<title><varname>lookup_domain</varname> (int)</title>
+	<para>
+	    Tells if the lookup tag should use or not the domain part when
+	    doing user location search. Set it to a non zero value to force
+	    also domain matching.
+	</para>
+	<para>
+	    Default value of this parameter is 0.
+	</para>
+	<example>
+	    <title>Set <varname>lookup_domain</varname> parameter</title>
+	    <programlisting>
+...
+modparam("cpl_c","lookup_domain",1)
+...
+	    </programlisting>
+	</example>
+    </section>
+
+</section>

+ 3 - 0
cpl-c/init.mysql

@@ -0,0 +1,3 @@
+USE ser;
+CREATE TABLE IF NOT EXISTS cpl ( user VARCHAR(50) NOT NULL PRIMARY KEY,
+ cpl_xml BLOB, cpl_bin BLOB, UNIQUE (user));

+ 183 - 0
cpl-c/loc_set.h

@@ -0,0 +1,183 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+
+#ifndef _CPL_LOC_SET_H_
+#define _CPL_LOC_SET_H_
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../str.h"
+#include "../../dprint.h"
+
+
+#define CPL_LOC_DUPL    (1<<0)
+#define CPL_LOC_NATED   (1<<1)
+
+
+struct location {
+	struct address {
+		str uri;
+		unsigned int priority;
+	}addr;
+	int flags;
+	struct location *next;
+};
+
+
+
+static inline void free_location( struct location *loc)
+{
+	shm_free( loc );
+}
+
+
+
+/* insert a new location into the set maintaining order by the prio val - the
+ * list starts with the smallest prio!
+ * For locations having the same prio val, the adding order will be kept */
+static inline int add_location(struct location **loc_set, str *uri,
+											unsigned int prio, int flags)
+{
+	struct location *loc;
+	struct location *foo, *bar;
+
+	loc = (struct location*)shm_malloc(
+		sizeof(struct location)+((flags&CPL_LOC_DUPL)?uri->len+1:0) );
+	if (!loc) {
+		LOG(L_ERR,"ERROR:add_location: no more free shm memory!\n");
+		return -1;
+	}
+
+	if (flags&CPL_LOC_DUPL) {
+		loc->addr.uri.s = ((char*)loc)+sizeof(struct location);
+		memcpy(loc->addr.uri.s,uri->s,uri->len);
+		loc->addr.uri.s[uri->len] = 0;
+	} else {
+		loc->addr.uri.s = uri->s;
+	}
+	loc->addr.uri.len = uri->len;
+	loc->addr.priority = prio;
+	loc->flags = flags;
+
+	/* find the proper place for the new location */
+	foo = *loc_set;
+	bar = 0;
+	while(foo && foo->addr.priority<=prio) {
+		bar = foo;
+		foo = foo->next;
+	}
+	if (!bar) {
+		/* insert at the beginning */
+		loc->next = *loc_set;
+		*loc_set = loc;
+	} else {
+		/* insert after bar, before foo  */
+		loc->next = foo;
+		bar->next = loc;
+	}
+
+	return 0;
+}
+
+
+
+static inline void remove_location(struct location **loc_set, char *uri_s,
+																int uri_len)
+{
+	struct location *loc = *loc_set;
+	struct location *prev_loc = 0;
+
+	for( ; loc ; prev_loc=loc,loc=loc->next ) {
+		if (loc->addr.uri.len==uri_len &&
+		!strncasecmp(loc->addr.uri.s,uri_s,uri_len) )
+			break;
+	}
+
+	if (loc) {
+		DBG("DEBUG:remove_location: removing from loc_set <%.*s>\n",
+			uri_len,uri_s);
+		if (prev_loc)
+			prev_loc->next=loc->next;
+		else
+			(*loc_set)=loc->next;
+		shm_free( loc );
+	} else {
+		DBG("DEBUG:remove_location: no matching in loc_set for <%.*s>\n",
+			uri_len,uri_s);
+	}
+}
+
+
+
+static inline struct location *remove_first_location(struct location **loc_set)
+{
+	struct location *loc;
+
+	if (!*loc_set)
+		return 0;
+
+	loc = *loc_set;
+	*loc_set = (*loc_set)->next;
+	loc->next = 0;
+	DBG("DEBUG:remove_first_location: removing <%.*s>\n",
+		loc->addr.uri.len,loc->addr.uri.s);
+
+	return loc;
+}
+
+
+
+static inline void empty_location_set(struct location **loc_set)
+{
+	struct location *loc;
+
+	while (*loc_set) {
+		loc = (*loc_set)->next;
+		shm_free(*loc_set);
+		*loc_set = loc;
+	}
+	*loc_set = 0;
+}
+
+
+static inline void print_location_set(struct location *loc_set)
+{
+	while (loc_set) {
+		DBG("DEBUG:cpl_c:print_loc_set: uri=<%s> q=%d\n",loc_set->addr.uri.s,
+			loc_set->addr.priority);
+		loc_set=loc_set->next;
+	}
+}
+
+#endif
+
+

+ 23 - 0
cpl-c/ser-cpl.cfg

@@ -0,0 +1,23 @@
+#
+# Minimalistic configuration file for SER that can be used to
+# test the CPL module.
+#
+debug = 4
+fork = no
+children = 1
+log_stderror = yes
+listen=127.0.0.1
+
+loadpath "./modules"
+
+loadmodule "mysql"
+loadmodule "sl"
+loadmodule "tm"
+loadmodule "cpl-c"
+
+modparam("cpl-c", "cpl_db", "mysql://ser:heslo@localhost/ser")
+modparam("cpl-c", "cpl_table", "cpl")
+
+route {
+	break;
+}

+ 81 - 0
cpl-c/sub_list.c

@@ -0,0 +1,81 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+/* History:
+ * --------
+ *  2003-03-19  all mallocs/frees replaced w/ pkg_malloc/pkg_free (andrei)
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include "../../mem/mem.h"
+#include "sub_list.h"
+
+struct node*   append_to_list(struct node *head, char *offset, char *name)
+{
+	struct node *new_node;
+
+	new_node = pkg_malloc(sizeof(struct node));
+	if (!new_node)
+		return 0;
+	new_node->offset = offset;
+	new_node->name = name;
+	new_node->next = head;
+
+	return new_node;
+}
+
+
+
+
+char* search_the_list(struct node *head, char *name)
+{
+	struct node *n;
+
+	n = head;
+	while (n) {
+		if (strcasecmp(n->name,name)==0)
+			return n->offset;
+		n = n->next;
+	}
+	return 0;
+}
+
+
+
+
+void delete_list(struct node* head)
+{
+	struct node *n;
+;
+	while (head) {
+		n=head->next;
+		pkg_free(head);
+		head = n;
+	}
+}
+
+

+ 42 - 0
cpl-c/sub_list.h

@@ -0,0 +1,42 @@
+/*
+ * $Id$
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef _CPL_SUB_LIST_H
+#define _CPL_SUB_LIST_H
+
+struct node {
+	char         *offset;
+	char         *name;
+	struct node  *next;
+};
+
+
+struct node*  append_to_list(struct node *head, char *offdet, char *name);
+char*         search_the_list(struct node *head, char *name);
+void          delete_list(struct node *head );
+
+#endif

+ 20 - 0
dbtext/Makefile

@@ -0,0 +1,20 @@
+# $Id$
+#
+# example module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+# extra debug messages
+DEFS+=-DDBT_EXTRA_DEBUG
+ 
+include ../../Makefile.defs
+auto_gen=
+NAME=dbtext.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 282 - 0
dbtext/README

@@ -0,0 +1,282 @@
+1. Dbtext Module
+
+Daniel-Constantin Mierla
+
+   FhG FOKUS
+
+   Copyright © 2003, 2004 FhG FOKUS
+     __________________________________________________________________
+
+   1.1. Overview
+
+        1.1.1. Design of Dbtext Engine
+        1.1.2. Internal Format of a Dbtext Table
+
+   1.2. Installation And Running
+
+        1.2.1. Using Dbtext With Basic SER Configuration
+
+1.1. Overview
+
+   The module implements a simplified database engine based on text files.
+   It can be used by SER DB interface instead of other database module
+   (like MySQL).
+
+   The module is meant for use in demos or small devices that do not
+   support other DB modules. It keeps everything in memory and if you deal
+   with large amount of data you may run quickly out of memory. Also, it
+   has not implemented all standard database facilities (like order by),
+   it includes minimal functionality to work properly (who knows ?!?) with
+   SER.
+
+1.1.1. Design of Dbtext Engine
+
+   The dbtext database system architecture:
+     * A database is represented by a directory on the local file system.
+
+Note
+       When you use "dbtext" in SER, the database URL for modules must be
+       the path to the directory where the table-files are located,
+       prefixed by "dbtext://", e.g., "dbtext:///var/dbtext/ser". If there
+       is no "/" after "dbtext://" then "CFG_DIR/" is inserted at the
+       beginning of the database path. So, either you provide an absolute
+       path to database directory or a relative one to "CFG_DIR"
+       directory.
+     * A table is represented by a text file inside database directory.
+
+1.1.2. Internal Format of a Dbtext Table
+
+   First line is the definition of the columns. Each column must be
+   declared as follows:
+     * the name of column must not include white spaces.
+     * the format of a column definition is: name(type,attr).
+     * between two column definitions must be a white space, e.g.,
+       "first_name(str) last_name(str)".
+     * the type of a column can be:
+          + int - integer numbers.
+          + double - real numbers with two decimals.
+          + str - strings with maximum size of 4KB.
+     * a column can have one of the attributes:
+          + auto - only for 'int' columns, the maximum value in that
+            column is incremented and stored in this field if it is not
+            provided in queries.
+          + null - accept null values in column fields.
+          + if no attribute is set, the fields of the column cannot have
+            null value.
+     * each other line is a row with data. The line ends with "\n".
+     * the fields are separated by ":".
+     * no value between two ':' (or between ':' and start/end of a row)
+       means "null" value.
+     * next characters must be escaped in strings: "\n", "\r", "\t", ":".
+     * 0 - The zero value must be escaped too.
+
+   Example 1. Sample of a dbtext table
+...
+id(int,auto) name(str) flag(double) desc(str,null)
+1:nick:0.34:a\tgood\:friend
+2:cole:-3.75:colleague3:bob:2.50:
+...
+
+   Example 2. Minimal SER location dbtext table definition
+...
+username(str) contact(str) expires(int) q(double) callid(str) cseq(int)
+...
+
+   Example 3. Minimal SER subscriber dbtext table example
+...
+username(str) password(str) ha1(str) domain(str) ha1b(str)
+suser:supasswd:xxx:iptel.org:xxx
+...
+
+1.2. Installation And Running
+
+   Compile the module and load it instead of mysql or other DB modules.
+
+Note
+
+   When you use dbtext in SER, the database URL for modules must be the
+   path to the directory where the table-files are located, prefixed by
+   "dbtext://", e.g., "dbtext:///var/dbtext/ser". If there is no "/" after
+   "dbtext://" then "CFG_DIR/" is inserted at the beginning of the
+   database path. So, either you provide an absolute path to database
+   directory or a relative one to "CFG_DIR" directory.
+
+   Example 4. Load the dbtext module
+...
+loadmodule "/path/to/ser/modules/dbtext.so"
+...
+modparam("module_name", "db_url", "dbtext:///path/to/dbtext/database")
+...
+
+1.2.1. Using Dbtext With Basic SER Configuration
+
+   Here are the definitions for most important table as well as a basic
+   configuration file to use dbtext with SER. The table structures may
+   change in time and you will have to adjust next examples.
+
+   You have to populate the table 'subscriber' by hand with user profiles
+   in order to have authentication. To use with the given configuration
+   file, the table files must be placed in the '/tmp/serdb' directory.
+
+   Example 5. Definition of 'subscriber' table (one line)
+...
+username(str) domn(str) password(str) first_name(str) last_name(str)
+phone(str) email_address(str) datetime_created(int)
+datetime_modified(int) confirmation(str) flag(str)
+sendnotification(str) greeting(str) ha1(str) ha1b(str)
+perms(str) allow_find(str) timezone(str,null) rpid(str,null)
+uuid(str,null)
+...
+
+   Example 6. Definition of 'location' and 'aliases' tables (one line)
+...
+username(str) domain(str,null) contact(str,null) expires(int,null)
+q(double,null) callid(str,null) cseq(int,null)
+last_modified(str) replicate(int,null) state(int,null)
+flags(int) user_agent(str) received(str)
+...
+
+   Example 7. Definition of 'version' table and sample records
+...
+table_name(str) table_version(int) subscriber:3 location:6 aliases:6
+...
+
+   Example 8. Configuration file
+#
+# $Id$
+#
+# simple quick-start config script with dbtext
+#
+
+# ----------- global configuration parameters ------------------------
+
+#debug=9         # debug level (cmd line: -dddddddddd)
+#fork=yes
+#log_stderror=no        # (cmd line: -E)
+
+check_via=no    # (cmd. line: -v)
+dns=no          # (cmd. line: -r)
+rev_dns=no      # (cmd. line: -R)
+children=4
+
+listen=10.100.100.1
+port=5060
+
+fifo="/tmp/ser_fifo"
+
+alias=alpha.org
+
+# ------------------ module loading ----------------------------------
+
+# use dbtext database
+loadmodule "../sip_router/modules/dbtext/dbtext.so"
+
+loadmodule "../sip_router/modules/sl/sl.so"
+loadmodule "../sip_router/modules/tm/tm.so"
+loadmodule "../sip_router/modules/rr/rr.so"
+loadmodule "../sip_router/modules/maxfwd/maxfwd.so"
+loadmodule "../sip_router/modules/usrloc/usrloc.so"
+loadmodule "../sip_router/modules/registrar/registrar.so"
+loadmodule "../sip_router/modules/textops/textops.so"
+
+# modules for digest authentication
+loadmodule "../sip_router/modules/auth/auth.so"
+loadmodule "../sip_router/modules/auth_db/auth_db.so"
+
+# ----------------- setting module-specific parameters ---------------
+
+# -- usrloc params --
+
+# use dbtext database for persistent storage
+modparam("usrloc", "db_mode", 2)
+modparam("usrloc|auth_db", "db_url", "dbtext:///tmp/serdb")
+
+# -- auth params --
+#
+modparam("auth_db", "calculate_ha1", 1)
+modparam("auth_db", "password_column", "password")
+modparam("auth_db", "user_column", "username")
+modparam("auth_db", "domain_column", "domain")
+
+# -- rr params --
+# add value to ;lr param to make some broken UAs happy
+modparam("rr", "enable_full_lr", 1)
+
+# -------------------------  request routing logic -------------------
+
+# main routing logic
+
+route{
+    # initial sanity checks -- messages with
+    # max_forwards==0, or excessively long requests
+    if (!mf_process_maxfwd_header("10")) {
+        sl_send_reply("483","Too Many Hops");
+        break;
+    };
+    if (msg:len >=  max_len ) {
+        sl_send_reply("513", "Message too big");
+        break;
+    };
+
+    # we record-route all messages -- to make sure that
+    # subsequent messages will go through our proxy; that's
+    # particularly good if upstream and downstream entities
+    # use different transport protocol
+    if (!method=="REGISTER") record_route();
+
+    # subsequent messages withing a dialog should take the
+    # path determined by record-routing
+    if (loose_route()) {
+        # mark routing logic in request
+        append_hf("P-hint: rr-enforced\r\n");
+        route(1);
+        break;
+    };
+
+    if (!uri==myself) {
+        # mark routing logic in request
+        append_hf("P-hint: outbound\r\n");
+        route(1);
+        break;
+    };
+
+    # if the request is for other domain use UsrLoc
+    # (in case, it does not work, use the following command
+    # with proper names and addresses in it)
+    if (uri==myself) {
+        if (method=="REGISTER") {
+            # digest authentication
+            if (!www_authorize("", "subscriber")) {
+                www_challenge("", "0");
+                break;
+            };
+
+            save("location");
+            break;
+        };
+
+        lookup("aliases");
+        if (!uri==myself) {
+            append_hf("P-hint: outbound alias\r\n");
+            route(1);
+            break;
+        };
+
+        # native SIP destinations are handled using our USRLOC DB
+        if (!lookup("location")) {
+            sl_send_reply("404", "Not Found");
+            break;
+        };
+    };
+    append_hf("P-hint: usrloc applied\r\n");
+    route(1);
+}
+
+route[1]
+{
+    # send it out now; use stateful forwarding as it works reliably
+    # even for UDP2TCP
+    if (!t_relay()) {
+        sl_reply_error();
+    };
+}

+ 425 - 0
dbtext/dbt_api.c

@@ -0,0 +1,425 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-02-05 created by Daniel
+ * 
+ */
+
+#include <string.h>
+
+#include "../../str.h"
+#include "../../mem/mem.h"
+
+#include "dbt_res.h"
+#include "dbt_api.h"
+
+/*
+ * Release memory used by columns
+ */
+int dbt_free_columns(db_res_t* _r)
+{
+	if (!_r) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_free_columns: Invalid parameter\n");
+#endif
+		return -1;
+	}
+	if (RES_NAMES(_r)) 
+		pkg_free(RES_NAMES(_r));
+	if (RES_TYPES(_r)) 
+		pkg_free(RES_TYPES(_r));
+	return 0;
+}
+
+/*
+ * Release memory used by row
+ */
+int dbt_free_row(db_row_t* _r)
+{
+	if (!_r) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_free_row: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	if(ROW_VALUES(_r))
+		pkg_free(ROW_VALUES(_r));
+	return 0;
+}
+
+/*
+ * Release memory used by rows
+ */
+int dbt_free_rows(db_res_t* _r)
+{
+	int i;
+	if (!_r) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_free_rows: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	if (RES_ROWS(_r))
+	{
+		for(i = 0; i < RES_ROW_N(_r); i++) 
+		{
+			dbt_free_row(&(RES_ROWS(_r)[i]));
+		}
+		pkg_free(RES_ROWS(_r));
+	}
+	return 0;
+}
+
+/*
+ * Release memory used by a result structure
+ */
+int dbt_free_result(db_res_t* _r)
+{
+	if (!_r) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_free_result: Invalid parameter\n");
+#endif
+		return -1;
+	}
+	dbt_free_columns(_r);
+	dbt_free_rows(_r);
+	pkg_free(_r);
+	return 0;
+}
+
+
+int dbt_use_table(db_con_t* _h, const char* _t)
+{
+	if ((!_h) || (!_t))
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_use_table: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	
+	CON_TABLE(_h) = _t;
+	
+	return 0;
+}
+
+/*
+ * Create a new result structure and initialize it
+ */
+db_res_t* dbt_new_result(void)
+{
+	db_res_t* r;
+	r = (db_res_t*)pkg_malloc(sizeof(db_res_t));
+	if (!r) {
+		LOG(L_ERR, "dbt_new_result(): No memory left\n");
+		return 0;
+	}
+	RES_NAMES(r) = 0;
+	RES_TYPES(r) = 0;
+	RES_COL_N(r) = 0;
+	RES_ROWS(r) = 0;
+	RES_ROW_N(r) = 0;
+	return r;
+}
+
+
+/*
+ * Fill the structure with data from database
+ */
+int dbt_convert_result(db_con_t* _h, db_res_t* _r)
+{
+	if ((!_h) || (!_r)) {
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_convert_result: Invalid parameter\n");
+#endif
+		return -1;
+	}
+	if (dbt_get_columns(_h, _r) < 0) {
+		LOG(L_ERR, 
+			"DBT:dbt_convert_result: Error while getting column names\n");
+		return -2;
+	}
+
+	if (dbt_convert_rows(_h, _r) < 0) {
+		LOG(L_ERR, "DBT:dbt_convert_result: Error while converting rows\n");
+		dbt_free_columns(_r);
+		return -3;
+	}
+	return 0;
+}
+
+
+/*
+ * Retrieve result set
+ */
+int dbt_get_result(db_con_t* _h, db_res_t** _r)
+{
+	if ((!_h) || (!_r)) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_get_result: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+
+	if (!DBT_CON_RESULT(_h))
+	{
+		LOG(L_ERR, "DBT:dbt_get_result: error getting result\n");
+		*_r = 0;
+		return -3;
+	}
+
+	*_r = dbt_new_result();
+	if (*_r == 0) 
+	{
+		LOG(L_ERR, "DBT:dbt_get_result: No memory left\n");
+		return -2;
+	}
+
+	if (dbt_convert_result(_h, *_r) < 0) 
+	{
+		LOG(L_ERR, "DBT:dbt_get_result: Error while converting result\n");
+		pkg_free(*_r);
+		return -4;
+	}
+	
+	return 0;
+}
+
+/*
+ * Get and convert columns from a result
+ */
+int dbt_get_columns(db_con_t* _h, db_res_t* _r)
+{
+	int n, i;
+	
+	if ((!_h) || (!_r)) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_get_columns: Invalid parameter\n");
+#endif
+		return -1;
+	}
+	
+	n = DBT_CON_RESULT(_h)->nrcols;
+	if (!n) 
+	{
+		LOG(L_ERR, "DBT:dbt_get_columns: No columns\n");
+		return -2;
+	}
+	
+	RES_NAMES(_r) = (db_key_t*)pkg_malloc(sizeof(db_key_t) * n);
+	if (!RES_NAMES(_r)) 
+	{
+		LOG(L_ERR, "DBT:dbt_get_columns: No memory left\n");
+		return -3;
+	}
+
+	RES_TYPES(_r) = (db_type_t*)pkg_malloc(sizeof(db_type_t) * n);
+	if (!RES_TYPES(_r)) 
+	{
+		LOG(L_ERR, "DBT:dbt_get_columns: No memory left\n");
+		pkg_free(RES_NAMES(_r));
+		return -4;
+	}
+
+	RES_COL_N(_r) = n;
+
+	for(i = 0; i < n; i++) 
+	{
+		RES_NAMES(_r)[i] = DBT_CON_RESULT(_h)->colv[i].name.s;
+		switch( DBT_CON_RESULT(_h)->colv[i].type) 
+		{
+			case DB_INT:
+			case DB_DATETIME:
+				RES_TYPES(_r)[i] = DB_INT;
+			break;
+
+			case DB_FLOAT:
+				RES_TYPES(_r)[i] = DB_FLOAT;
+			break;
+
+			case DB_DOUBLE:
+				RES_TYPES(_r)[i] = DB_DOUBLE;
+			break;
+
+			default:
+				RES_TYPES(_r)[i] = DB_STR;
+			break;
+		}		
+	}
+	return 0;
+}
+
+/*
+ * Convert rows from internal to db API representation
+ */
+int dbt_convert_rows(db_con_t* _h, db_res_t* _r)
+{
+	int n, i;
+	dbt_row_p _rp = NULL;
+	if ((!_h) || (!_r)) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_convert_rows: Invalid parameter\n");
+#endif
+		return -1;
+	}
+	n = DBT_CON_RESULT(_h)->nrrows;
+	RES_ROW_N(_r) = n;
+	if (!n) 
+	{
+		RES_ROWS(_r) = 0;
+		return 0;
+	}
+	RES_ROWS(_r) = (struct db_row*)pkg_malloc(sizeof(db_row_t) * n);
+	if (!RES_ROWS(_r)) 
+	{
+		LOG(L_ERR, "DBT:dbt_convert_rows: No memory left\n");
+		return -2;
+	}
+	i = 0;
+	_rp = DBT_CON_RESULT(_h)->rows;
+	while(_rp)
+	{
+		DBT_CON_ROW(_h) = _rp;
+		if (!DBT_CON_ROW(_h)) 
+		{
+			LOG(L_ERR, "DBT:dbt_convert_rows: error getting current row\n");
+			RES_ROW_N(_r) = i;
+			dbt_free_rows(_r);
+			return -3;
+		}
+		if (dbt_convert_row(_h, _r, &(RES_ROWS(_r)[i])) < 0) 
+		{
+			LOG(L_ERR, "DBT:dbt_convert_rows: Error while converting"
+				" row #%d\n", i);
+			RES_ROW_N(_r) = i;
+			dbt_free_rows(_r);
+			return -4;
+		}
+		i++;
+		_rp = _rp->next;
+	}
+	return 0;
+}
+
+/*
+ * Convert a row from result into db API representation
+ */
+int dbt_convert_row(db_con_t* _h, db_res_t* _res, db_row_t* _r)
+{
+	int i;
+	if ((!_h) || (!_r) || (!_res)) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_convert_row: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+
+	ROW_VALUES(_r) = (db_val_t*)pkg_malloc(sizeof(db_val_t) * RES_COL_N(_res));
+	ROW_N(_r) = RES_COL_N(_res);
+	if (!ROW_VALUES(_r)) 
+	{
+		LOG(L_ERR, "DBT:dbt_convert_row: No memory left\n");
+		return -1;
+	}
+
+	for(i = 0; i < RES_COL_N(_res); i++) 
+	{
+		(ROW_VALUES(_r)[i]).nul = DBT_CON_ROW(_h)->fields[i].nul;
+		switch(RES_TYPES(_res)[i])
+		{
+			case DB_INT:
+				VAL_INT(&(ROW_VALUES(_r)[i])) = 
+						DBT_CON_ROW(_h)->fields[i].val.int_val;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_INT;
+			break;
+
+			case DB_FLOAT:
+				VAL_FLOAT(&(ROW_VALUES(_r)[i])) = 
+						DBT_CON_ROW(_h)->fields[i].val.float_val;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_FLOAT;
+			break;
+
+			case DB_DOUBLE:
+				VAL_DOUBLE(&(ROW_VALUES(_r)[i])) = 
+						DBT_CON_ROW(_h)->fields[i].val.double_val;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_DOUBLE;
+			break;
+
+			case DB_STRING:
+				VAL_STR(&(ROW_VALUES(_r)[i])).s = 
+						DBT_CON_ROW(_h)->fields[i].val.str_val.s;
+				VAL_STR(&(ROW_VALUES(_r)[i])).len =
+						DBT_CON_ROW(_h)->fields[i].val.str_val.len;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_STR;
+			break;
+
+			case DB_STR:
+				VAL_STR(&(ROW_VALUES(_r)[i])).s = 
+						DBT_CON_ROW(_h)->fields[i].val.str_val.s;
+				VAL_STR(&(ROW_VALUES(_r)[i])).len =
+						DBT_CON_ROW(_h)->fields[i].val.str_val.len;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_STR;
+			break;
+
+			case DB_DATETIME:
+				VAL_INT(&(ROW_VALUES(_r)[i])) = 
+						DBT_CON_ROW(_h)->fields[i].val.int_val;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_INT;
+			break;
+
+			case DB_BLOB:
+				VAL_STR(&(ROW_VALUES(_r)[i])).s =
+						DBT_CON_ROW(_h)->fields[i].val.str_val.s;
+				VAL_STR(&(ROW_VALUES(_r)[i])).len =
+						DBT_CON_ROW(_h)->fields[i].val.str_val.len;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_STR;
+			break;
+
+			case DB_BITMAP:
+				VAL_INT(&(ROW_VALUES(_r)[i])) =
+					DBT_CON_ROW(_h)->fields[i].val.bitmap_val;
+				VAL_TYPE(&(ROW_VALUES(_r)[i])) = DB_INT;
+			break;
+		}
+	}
+	return 0;
+}
+
+

+ 86 - 0
dbtext/dbt_api.h

@@ -0,0 +1,86 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-02-05 created by Daniel
+ * 
+ */
+
+
+#ifndef _DBT_API_H_
+#define _DBT_API_H_
+
+#include "../../db/db_op.h"
+#include "../../lib/srdb2/db_res.h"
+#include "../../lib/srdb2/db_con.h"
+#include "../../db/db_row.h"
+
+int dbt_free_columns(db_res_t* _r);
+
+/*
+ * Release memory used by row
+ */
+int dbt_free_row(db_row_t* _r);
+
+/*
+ * Release memory used by rows
+ */
+int dbt_free_rows(db_res_t* _r);
+
+/*
+ * Release memory used by a result structure
+ */
+int dbt_free_result(db_res_t* _r);
+
+/*
+ * Retrieve result set
+ */
+int dbt_get_result(db_con_t* _h, db_res_t** _r);
+
+/*
+ * Get and convert columns from a result
+ */
+int dbt_get_columns(db_con_t* _h, db_res_t* _r);
+
+/*
+ * Convert rows from mysql to db API representation
+ */
+int dbt_convert_rows(db_con_t* _h, db_res_t* _r);
+
+/*
+ * Convert a row from result into db API representation
+ */
+int dbt_convert_row(db_con_t* _h, db_res_t* _res, db_row_t* _r);
+
+
+int dbt_use_table(db_con_t* _h, const char* _t);
+
+#endif

+ 607 - 0
dbtext/dbt_base.c

@@ -0,0 +1,607 @@
+/*
+ * $Id$
+ *
+ * DBText module core functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText module interface
+ *  
+ * 2003-01-30 created by Daniel
+ * 
+ */
+
+#include <string.h>
+
+#include "../../str.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+ 
+#include "dbtext.h"
+#include "dbt_res.h"
+#include "dbt_api.h"
+
+#ifndef CFG_DIR
+#define CFG_DIR "/tmp"
+#endif
+
+#define DBT_ID		"dbtext://"
+#define DBT_ID_LEN	(sizeof(DBT_ID)-1)
+#define DBT_PATH_LEN	256
+/*
+ * Initialize database connection
+ */
+db_con_t* dbt_init(const char* _sqlurl)
+{
+	db_con_t* _res;
+	str _s;
+	char dbt_path[DBT_PATH_LEN];
+	
+	if (!_sqlurl) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_init: Invalid parameter value\n");
+#endif
+		return NULL;
+	}
+	_s.s = (char*)_sqlurl;
+	_s.len = strlen(_sqlurl);
+	if(_s.len <= DBT_ID_LEN || strncmp(_s.s, DBT_ID, DBT_ID_LEN)!=0)
+	{
+		LOG(L_ERR, "DBT:dbt_init: invalid database URL - should be:"
+			" <%s[/]path/to/directory>\n", DBT_ID);
+		return NULL;
+	}
+	_s.s   += DBT_ID_LEN;
+	_s.len -= DBT_ID_LEN;
+	if(_s.s[0]!='/')
+	{
+		if(sizeof(CFG_DIR)+_s.len+2 > DBT_PATH_LEN)
+		{
+			LOG(L_ERR, "DBT:dbt_init: path to database is too long\n");
+			return NULL;
+		}
+		strcpy(dbt_path, CFG_DIR);
+		dbt_path[sizeof(CFG_DIR)] = '/';
+		strncpy(&dbt_path[sizeof(CFG_DIR)+1], _s.s, _s.len);
+		_s.len += sizeof(CFG_DIR);
+		_s.s = dbt_path;
+	}
+	
+	_res = pkg_malloc(sizeof(db_con_t)+sizeof(dbt_con_t));
+	if (!_res)
+	{
+		LOG(L_ERR, "DBT:dbt_init: No memory left\n");
+		return NULL;
+	}
+	memset(_res, 0, sizeof(db_con_t) + sizeof(dbt_con_t));
+	_res->tail = (unsigned long)((char*)_res+sizeof(db_con_t));
+	
+	DBT_CON_CONNECTION(_res) = dbt_cache_get_db(&_s);
+	if (!DBT_CON_CONNECTION(_res))
+	{
+		LOG(L_ERR, "DBT:dbt_init: cannot get the link to database\n");
+		return NULL;
+	}
+
+    return _res;
+}
+
+
+/*
+ * Close a database connection
+ */
+void dbt_close(db_con_t* _h)
+{
+	if (!_h) 
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_close: Invalid parameter value\n");
+#endif
+		return;
+	}
+	
+	if (DBT_CON_RESULT(_h)) 
+		dbt_result_free(DBT_CON_RESULT(_h));
+	
+	pkg_free(_h);
+    return;
+}
+
+
+/*
+ * Free all memory allocated by get_result
+ */
+int dbt_free_query(db_con_t* _h, db_res_t* _r)
+{
+	if ((!_h) || (!_r))
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_free_query: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+
+	if(dbt_free_result(_r) < 0) 
+	{
+		LOG(L_ERR,"DBT:dbt_free_query:Unable to free result structure\n");
+		return -1;
+	}
+
+	
+	if(dbt_result_free(DBT_CON_RESULT(_h)) < 0) 
+	{
+		LOG(L_ERR, "DBT:dbt_free_query: Unable to free internal structure\n");
+		return -1;
+	}
+	DBT_CON_RESULT(_h) = NULL;
+	return 0;
+}
+
+
+/*
+ * Query table for specified rows
+ * _h: structure representing database connection
+ * _k: key names
+ * _op: operators
+ * _v: values of the keys that must match
+ * _c: column names to return
+ * _n: number of key=values pairs to compare
+ * _nc: number of columns to return
+ * _o: order by the specified column
+ */
+
+int dbt_query(db_con_t* _h, db_key_t* _k, db_op_t* _op, db_val_t* _v, 
+			db_key_t* _c, int _n, int _nc, db_key_t _o, db_res_t** _r)
+{
+	tbl_cache_p _tbc = NULL;
+	dbt_table_p _dtp = NULL;
+	dbt_row_p _drp = NULL;
+	dbt_result_p _dres = NULL;
+	
+	str stbl;
+	int *lkey=NULL, *lres=NULL;
+	
+	if ((!_h) || (!_r) || !CON_TABLE(_h))
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_query: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	
+	stbl.s = (char*)CON_TABLE(_h);
+	stbl.len = strlen(CON_TABLE(_h));
+
+	_tbc = dbt_db_get_table(DBT_CON_CONNECTION(_h), &stbl);
+	if(!_tbc)
+	{
+		DBG("DBT:dbt_query: table does not exist!\n");
+		return -1;
+	}
+
+	lock_get(&_tbc->sem);
+	_dtp = _tbc->dtp;
+
+	if(!_dtp || _dtp->nrcols < _nc)
+	{
+		DBG("DBT:dbt_query: table not loaded!\n");
+		goto error;
+	}
+	if(_k)
+	{
+		lkey = dbt_get_refs(_dtp, _k, _n);
+		if(!lkey)
+			goto error;
+	}
+	if(_c)
+	{
+		lres = dbt_get_refs(_dtp, _c, _nc);
+		if(!lres)
+			goto error;
+	}
+
+	DBG("DBT:dbt_query: new res with %d cols\n", _nc);
+	_dres = dbt_result_new(_dtp, lres, _nc);
+	
+	if(!_dres)
+		goto error;
+	
+	_drp = _dtp->rows;
+	while(_drp)
+	{
+		if(dbt_row_match(_dtp, _drp, lkey, _op, _v, _n))
+		{
+			if(dbt_result_extract_fields(_dtp, _drp, lres, _dres))
+			{
+				DBG("DBT:dbt_query: error extracting result fields!\n");
+				goto clean;
+			}
+		}
+		_drp = _drp->next;
+	}
+
+	dbt_table_update_flags(_dtp, DBT_TBFL_ZERO, DBT_FL_IGN, 1);
+	
+	lock_release(&_tbc->sem);
+
+#ifdef DBT_EXTRA_DEBUG
+	dbt_result_print(_dres);
+#endif
+	
+	DBT_CON_RESULT(_h) = _dres;
+	
+	if(lkey)
+		pkg_free(lkey);
+	if(lres)
+		pkg_free(lres);
+
+	return dbt_get_result(_h, _r);
+
+error:
+	lock_release(&_tbc->sem);
+	if(lkey)
+		pkg_free(lkey);
+	if(lres)
+		pkg_free(lres);
+	DBG("DBT:dbt_query: error while querying table!\n");
+    
+	return -1;
+
+clean:
+	lock_release(&_tbc->sem);
+	if(lkey)
+		pkg_free(lkey);
+	if(lres)
+		pkg_free(lres);
+	if(_dres)
+		dbt_result_free(_dres);
+	DBG("DBT:dbt_query: make clean\n");
+
+	return -1;
+}
+
+/*
+ * Raw SQL query -- is not the case to have this method
+ */
+int dbt_raw_query(db_con_t* _h, char* _s, db_res_t** _r)
+{
+	*_r = NULL;
+    return -1;
+}
+
+/*
+ * Insert a row into table
+ */
+int dbt_insert(db_con_t* _h, db_key_t* _k, db_val_t* _v, int _n)
+{
+	tbl_cache_p _tbc = NULL;
+	dbt_table_p _dtp = NULL;
+	dbt_row_p _drp = NULL;
+	
+	str stbl;
+	int *lkey=NULL, i, j;
+	
+	if (!_h || !CON_TABLE(_h))
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_insert: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	if(!_k || !_v || _n<=0)
+	{
+#ifdef DBT_EXTRA_DEBUG
+		DBG("DBT:dbt_insert: no key-value to insert\n");
+#endif
+		return -1;
+	}
+	
+	stbl.s = (char*)CON_TABLE(_h);
+	stbl.len = strlen(CON_TABLE(_h));
+
+	_tbc = dbt_db_get_table(DBT_CON_CONNECTION(_h), &stbl);
+	if(!_tbc)
+	{
+		DBG("DBT:db_insert: table does not exist!\n");
+		return -1;
+	}
+
+	lock_get(&_tbc->sem);
+	
+	_dtp = _tbc->dtp;
+	if(!_dtp)
+	{
+		DBG("DBT:db_insert: table does not exist!!\n");
+		goto error;
+	}
+	
+	if(_dtp->nrcols<_n)
+	{
+		DBG("DBT:db_insert: more values than columns!!\n");
+		goto error;
+	}
+	
+	if(_k)
+	{
+		lkey = dbt_get_refs(_dtp, _k, _n);
+		if(!lkey)
+			goto error;
+	}
+	_drp = dbt_row_new(_dtp->nrcols);
+	if(!_drp)
+	{
+		DBG("DBT:db_insert: no memory for a new row!!\n");
+		goto error;
+	}
+	
+	for(i=0; i<_n; i++)
+	{
+		j = (lkey)?lkey[i]:i;
+		if(dbt_is_neq_type(_dtp->colv[j]->type, _v[i].type))
+		{
+			DBG("DBT:db_insert: incompatible types v[%d] - c[%d]!\n", i, j);
+			goto clean;
+		}
+		if(dbt_row_set_val(_drp, &(_v[i]), _v[i].type, j))
+		{
+			DBG("DBT:db_insert: cannot set v[%d] in c[%d]!\n", i, j);
+			goto clean;
+		}
+		
+	}
+
+	if(dbt_table_add_row(_dtp, _drp))
+	{
+		DBG("DBT:db_insert: cannot insert the new row!!\n");
+		goto clean;
+	}
+
+#ifdef DBT_EXTRA_DEBUG
+	dbt_print_table(_dtp, NULL);
+#endif
+	
+	lock_release(&_tbc->sem);
+
+	if(lkey)
+		pkg_free(lkey);
+
+	DBG("DBT:db_insert: done!\n");
+
+    return 0;
+	
+error:
+	lock_release(&_tbc->sem);
+	if(lkey)
+		pkg_free(lkey);
+	DBG("DBT:db_insert: error inserting row in table!\n");
+    return -1;
+	
+clean:
+	lock_release(&_tbc->sem);
+	if(lkey)
+		pkg_free(lkey);
+	
+	if(_drp) // free row
+		dbt_row_free(_dtp, _drp);
+	
+	DBG("DBT:db_insert: make clean!\n");
+    return -1;
+}
+
+/*
+ * Delete a row from table
+ */
+int dbt_delete(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v, int _n)
+{
+	tbl_cache_p _tbc = NULL;
+	dbt_table_p _dtp = NULL;
+	dbt_row_p _drp = NULL, _drp0 = NULL;
+	int *lkey = NULL;
+	str stbl;
+
+	if (!_h || !CON_TABLE(_h))
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_delete: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	stbl.s = (char*)CON_TABLE(_h);
+	stbl.len = strlen(CON_TABLE(_h));
+
+	_tbc = dbt_db_get_table(DBT_CON_CONNECTION(_h), &stbl);
+	if(!_tbc)
+	{
+		DBG("DBT:dbt_delete: error loading table <%s>!\n", CON_TABLE(_h));
+		return -1;
+	}
+
+	lock_get(&_tbc->sem);
+	_dtp = _tbc->dtp;
+
+	if(!_dtp)
+	{
+		DBG("DBT:dbt_delete: table does not exist!!\n");
+		goto error;
+	}
+	
+	if(!_k || !_v || _n<=0)
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_delete: delete all values\n");
+#endif
+		dbt_table_free_rows(_dtp);
+		lock_release(&_tbc->sem);
+		return 0;
+	}
+
+	lkey = dbt_get_refs(_dtp, _k, _n);
+	if(!lkey)
+		goto error;
+	
+	_drp = _dtp->rows;
+	while(_drp)
+	{
+		_drp0 = _drp->next;
+		if(dbt_row_match(_dtp, _drp, lkey, _o, _v, _n))
+		{
+			// delete row
+			DBG("DBT:dbt_delete: deleting a row!\n");
+			if(_drp->prev)
+				(_drp->prev)->next = _drp->next;
+			else
+				_dtp->rows = _drp->next;
+			if(_drp->next)
+				(_drp->next)->prev = _drp->prev;
+			_dtp->nrrows--;
+			// free row
+			dbt_row_free(_dtp, _drp);
+		}
+		_drp = _drp0;
+	}
+
+	dbt_table_update_flags(_dtp, DBT_TBFL_MODI, DBT_FL_SET, 1);
+	
+#ifdef DBT_EXTRA_DEBUG
+	dbt_print_table(_dtp, NULL);
+#endif
+	
+	lock_release(&_tbc->sem);
+	
+	if(lkey)
+		pkg_free(lkey);
+	
+	return 0;
+	
+error:
+	lock_release(&_tbc->sem);
+	DBG("DBT:dbt_delete: error deleting from table!\n");
+    
+	return -1;
+}
+
+/*
+ * Update a row in table
+ */
+int dbt_update(db_con_t* _h, db_key_t* _k, db_op_t* _o, db_val_t* _v,
+	      db_key_t* _uk, db_val_t* _uv, int _n, int _un)
+{
+	tbl_cache_p _tbc = NULL;
+	dbt_table_p _dtp = NULL;
+	dbt_row_p _drp = NULL;
+	int i;	
+	str stbl;
+	int *lkey=NULL, *lres=NULL;
+
+	if (!_h || !CON_TABLE(_h) || !_uk || !_uv || _un <= 0)
+	{
+#ifdef DBT_EXTRA_DEBUG
+		LOG(L_ERR, "DBT:dbt_update: Invalid parameter value\n");
+#endif
+		return -1;
+	}
+	
+	stbl.s = (char*)CON_TABLE(_h);
+	stbl.len = strlen(CON_TABLE(_h));
+
+	_tbc = dbt_db_get_table(DBT_CON_CONNECTION(_h), &stbl);
+	if(!_tbc)
+	{
+		DBG("DBT:dbt_update: table does not exist!\n");
+		return -1;
+	}
+
+	lock_get(&_tbc->sem);
+	_dtp = _tbc->dtp;
+
+	if(!_dtp || _dtp->nrcols < _un)
+	{
+		DBG("DBT:dbt_update: table not loaded or more values"
+			" to update than columns!\n");
+		goto error;
+	}
+	if(_k)
+	{
+		lkey = dbt_get_refs(_dtp, _k, _n);
+		if(!lkey)
+			goto error;
+	}
+	lres = dbt_get_refs(_dtp, _uk, _un);
+	if(!lres)
+		goto error;
+	DBG("DBT:dbt_update: ---- \n");
+	_drp = _dtp->rows;
+	while(_drp)
+	{
+		if(dbt_row_match(_dtp, _drp, lkey, _o, _v, _n))
+		{ // update fields
+			for(i=0; i<_un; i++)
+			{
+				if(dbt_is_neq_type(_dtp->colv[lres[i]]->type, _uv[i].type))
+				{
+					DBG("DBT:dbt_update: incompatible types!\n");
+					goto error;
+				}
+				
+				if(dbt_row_update_val(_drp, &(_uv[i]), _uv[i].type, lres[i]))
+				{
+					DBG("DBT:dbt_update: cannot set v[%d] in c[%d]!\n",
+							i, lres[i]);
+					goto error;
+				}
+			}
+		}
+		_drp = _drp->next;
+	}
+
+	dbt_table_update_flags(_dtp, DBT_TBFL_MODI, DBT_FL_SET, 1);
+	
+#ifdef DBT_EXTRA_DEBUG
+	dbt_print_table(_dtp, NULL);
+#endif
+	
+	lock_release(&_tbc->sem);
+
+	if(lkey)
+		pkg_free(lkey);
+	if(lres)
+		pkg_free(lres);
+
+    return 0;
+
+error:
+	lock_release(&_tbc->sem);
+	if(lkey)
+		pkg_free(lkey);
+	if(lres)
+		pkg_free(lres);
+	
+	DBG("DBT:dbt_update: error while updating table!\n");
+    
+	return -1;
+}
+

+ 620 - 0
dbtext/dbt_file.c

@@ -0,0 +1,620 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-02-03 created by Daniel
+ * 
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+
+#include "dbt_util.h"
+#include "dbt_lib.h"
+
+
+/**
+ *
+ */
+dbt_table_p dbt_load_file(str *tbn, str *dbn)
+{
+	FILE *fin=NULL;
+	char path[512], buf[4096];
+	int c, crow, ccol, bp, sign, max_auto;
+	dbt_val_t dtval;
+	dbt_table_p dtp = NULL;
+	dbt_column_p colp, colp0 = NULL;
+	dbt_row_p rowp, rowp0 = NULL;
+		
+	enum {DBT_FLINE_ST, DBT_NLINE_ST, DBT_DATA_ST} state;
+	
+	DBG("DBT:dbt_load_file: request for table [%.*s]\n", tbn->len, tbn->s);
+	
+	if(!tbn || !tbn->s || tbn->len<=0 || tbn->len>=255)
+		return NULL;
+	path[0] = 0;
+	if(dbn && dbn->s && dbn->len>0)
+	{
+		DBG("DBT:dbt_load_file: db is [%.*s]\n", dbn->len, dbn->s);
+		if(dbn->len+tbn->len<511)
+		{
+			strncpy(path, dbn->s, dbn->len);
+			path[dbn->len] = '/';
+			strncpy(path+dbn->len+1, tbn->s, tbn->len);
+			path[dbn->len+tbn->len+1] = 0;
+		}
+	}
+	if(path[0] == 0)
+	{
+		strncpy(path, tbn->s, tbn->len);
+		path[tbn->len] = 0;
+	}
+	
+	DBG("DBT:dbt_load_file: loading file [%s]\n", path);
+	fin = fopen(path, "rt");
+	if(!fin)
+		return NULL;	
+	
+	dtp = dbt_table_new(tbn->s, tbn->len);
+	if(!dtp)
+		goto done;
+	
+	state = DBT_FLINE_ST;
+	crow = ccol = -1;
+	colp = colp0 = NULL;
+	rowp = rowp0 = NULL;
+	c = fgetc(fin);
+	max_auto = 0;
+	while(c!=EOF)
+	{
+		switch(state)
+		{
+			case DBT_FLINE_ST:
+				//DBG("DBT:dbt_load_file: state FLINE!\n");
+				bp = 0;
+				while(c==DBT_DELIM_C)
+					c = fgetc(fin);
+				if(c==DBT_DELIM_R && !colp0)
+					goto clean;
+				if(c==DBT_DELIM_R)
+				{
+					if(dtp->nrcols <= 0)
+						goto clean;
+					dtp->colv = (dbt_column_p*)
+							shm_malloc(dtp->nrcols*sizeof(dbt_column_p));
+					if(!dtp->colv)
+						goto clean;
+					colp0 = dtp->cols;
+					for(ccol=0; ccol<dtp->nrcols && colp0; ccol++)
+					{
+						dtp->colv[ccol] = colp0;
+						colp0 = colp0->next;
+					}
+					state = DBT_NLINE_ST;
+					break;
+				}
+				while(c!=DBT_DELIM_C && c!='(' && c!=DBT_DELIM_R)
+				{
+					if(c==EOF)
+						goto clean;
+					buf[bp++] = c;
+					c = fgetc(fin);
+				}
+				colp = dbt_column_new(buf, bp);
+				if(!colp)
+					goto clean;
+				//DBG("DBT:dbt_load_file: new col [%.*s]\n", bp, buf);
+				while(c==DBT_DELIM_C)
+					c = fgetc(fin);
+				if(c!='(')
+					goto clean;
+				c = fgetc(fin);
+				while(c==DBT_DELIM_C)
+					c = fgetc(fin);
+				
+				switch(c)
+				{
+					case 's':
+					case 'S':
+						colp->type = DB_STR;
+						DBG("DBT: column[%d] is STR!\n", ccol+1);
+					break;
+					case 'i':
+					case 'I':
+						colp->type = DB_INT;
+						DBG("DBT: column[%d] is INT!\n", ccol+1);
+					break;
+				        case 'f':
+				        case 'F':
+						colp->type = DB_FLOAT;
+						DBG("DBT: column[%d] is FLOAT!\n", ccol+1);
+					break;
+					case 'd':
+					case 'D':
+						colp->type = DB_DOUBLE;
+						DBG("DBT: column[%d] is DOUBLE!\n", ccol+1);
+					break;
+					default:
+						DBG("DBT: wrong column type!\n");
+						goto clean;
+				}
+
+				while(c!=')' && c!= ',')
+					c = fgetc(fin);
+				if(c==',')
+				{
+					//DBG("DBT: c=%c!\n", c);
+					c = fgetc(fin);
+					while(c==DBT_DELIM_C)
+						c = fgetc(fin);
+					if(c=='N' || c=='n')
+					{
+						//DBG("DBT:dbt_load_file: NULL flag set!\n");
+						colp->flag |= DBT_FLAG_NULL;
+					}
+					else if(colp->type==DB_INT && dtp->auto_col<0
+							&& (c=='A' || c=='a'))
+					{
+						//DBG("DBT:dbt_load_file: AUTO flag set!\n");
+						colp->flag |= DBT_FLAG_AUTO;
+						dtp->auto_col = ccol+1;
+					}
+					else
+						goto clean;
+					while(c!=')' && c!=DBT_DELIM_R && c!=EOF)
+						c = fgetc(fin);
+				}
+				if(c == ')')
+				{
+					//DBG("DBT: c=%c!\n", c);
+					if(colp0)
+					{
+						colp->prev = colp0;
+						colp0->next = colp;
+					}
+					else
+						dtp->cols = colp;
+					colp0 = colp;
+					dtp->nrcols++;
+					c = fgetc(fin);
+				}
+				else
+					goto clean;
+				ccol++;
+			break;
+
+			case DBT_NLINE_ST:
+				//DBG("DBT:dbt_load_file: state NLINE!\n");
+				while(c==DBT_DELIM_R)
+					c = fgetc(fin);
+				if(rowp)
+				{
+					if(dbt_table_check_row(dtp, rowp))
+						goto clean;
+
+					if(!rowp0)
+						dtp->rows = rowp;
+					else
+					{
+						rowp0->next = rowp;
+						rowp->prev = rowp0;
+					}
+					rowp0 = rowp;
+					dtp->nrrows++;
+				}
+				if(c==EOF)
+					break;
+				crow++;
+				ccol = 0;
+				rowp = dbt_row_new(dtp->nrcols);
+				if(!rowp)
+					goto clean;
+				state = DBT_DATA_ST;
+				
+			break;
+			
+			case DBT_DATA_ST:
+				//DBG("DBT:dbt_load_file: state DATA!\n");
+				//while(c==DBT_DELIM)
+				//	c = fgetc(fin);
+				if(ccol == dtp->nrcols && (c==DBT_DELIM_R || c==EOF))
+				{
+					state = DBT_NLINE_ST;
+					break;
+				}
+				if(ccol>= dtp->nrcols)
+					goto clean;
+				
+				switch(dtp->colv[ccol]->type)
+				{
+					case DB_INT:
+						//DBG("DBT:dbt_load_file: INT value!\n");
+						dtval.val.int_val = 0;
+						dtval.type = DB_INT;
+
+						if(c==DBT_DELIM || 
+								(ccol==dtp->nrcols-1
+								 && (c==DBT_DELIM_R || c==EOF)))
+							dtval.nul = 1;
+						else
+						{
+							dtval.nul = 0;
+							sign = 1;
+							if(c=='-')
+							{
+								sign = -1;
+								c = fgetc(fin);
+							}
+							if(c<'0' || c>'9')
+								goto clean;
+							while(c>='0' && c<='9')
+							{
+								dtval.val.int_val=dtval.val.int_val*10+c-'0';
+								c = fgetc(fin);
+							}
+							dtval.val.int_val *= sign;
+							//DBG("DBT:dbt_load_file: data[%d,%d]=%d\n", crow,
+							//	ccol, dtval.val.int_val);
+						}
+						if(c!=DBT_DELIM && c!=DBT_DELIM_R && c!=EOF)
+							goto clean;
+						if(dbt_row_set_val(rowp,&dtval,DB_INT,ccol))
+							goto clean;
+						if(ccol == dtp->auto_col)
+							max_auto = (max_auto<dtval.val.int_val)?
+									dtval.val.int_val:max_auto;
+					break;
+					
+					case DB_FLOAT:
+						//DBG("DBT:dbt_load_file: FLOAT value!\n");
+						dtval.val.float_val = 0.0;
+						dtval.type = DB_FLOAT;
+
+						if(c==DBT_DELIM || 
+								(ccol==dtp->nrcols-1
+								 && (c==DBT_DELIM_R || c==EOF)))
+							dtval.nul = 1;
+						else
+						{
+							dtval.nul = 0;
+							sign = 1;
+							if(c=='-')
+							{
+								sign = -1;
+								c = fgetc(fin);
+							}
+							if(c<'0' || c>'9')
+								goto clean;
+							while(c>='0' && c<='9')
+							{
+								dtval.val.float_val = dtval.val.float_val*10
+										+ c - '0';
+								c = fgetc(fin);
+							}
+							if(c=='.')
+							{
+								c = fgetc(fin);
+								bp = 1;
+								while(c>='0' && c<='9')
+								{
+									bp *= 10;
+									dtval.val.float_val+=((float)(c-'0'))/bp;
+									c = fgetc(fin);
+								}
+							}
+							dtval.val.float_val *= sign;
+							//DBG("DBT:dbt_load_file: data[%d,%d]=%10.2f\n",
+							//	crow, ccol, dtval.val.float_val);
+						}
+						if(c!=DBT_DELIM && c!=DBT_DELIM_R && c!=EOF)
+							goto clean;
+						if(dbt_row_set_val(rowp,&dtval,DB_FLOAT,ccol))
+							goto clean;
+					break;
+
+					case DB_DOUBLE:
+						//DBG("DBT:dbt_load_file: DOUBLE value!\n");
+						dtval.val.double_val = 0.0;
+						dtval.type = DB_DOUBLE;
+
+						if(c==DBT_DELIM || 
+								(ccol==dtp->nrcols-1
+								 && (c==DBT_DELIM_R || c==EOF)))
+							dtval.nul = 1;
+						else
+						{
+							dtval.nul = 0;
+							sign = 1;
+							if(c=='-')
+							{
+								sign = -1;
+								c = fgetc(fin);
+							}
+							if(c<'0' || c>'9')
+								goto clean;
+							while(c>='0' && c<='9')
+							{
+								dtval.val.double_val = dtval.val.double_val*10
+										+ c - '0';
+								c = fgetc(fin);
+							}
+							if(c=='.')
+							{
+								c = fgetc(fin);
+								bp = 1;
+								while(c>='0' && c<='9')
+								{
+									bp *= 10;
+									dtval.val.double_val+=((double)(c-'0'))/bp;
+									c = fgetc(fin);
+								}
+							}
+							dtval.val.double_val *= sign;
+							//DBG("DBT:dbt_load_file: data[%d,%d]=%10.2f\n",
+							//	crow, ccol, dtval.val.double_val);
+						}
+						if(c!=DBT_DELIM && c!=DBT_DELIM_R && c!=EOF)
+							goto clean;
+						if(dbt_row_set_val(rowp,&dtval,DB_DOUBLE,ccol))
+							goto clean;
+					break;
+					
+					case DB_STR:
+						//DBG("DBT:dbt_load_file: STR value!\n");
+						
+						dtval.val.str_val.s = NULL;
+						dtval.val.str_val.len = 0;
+						dtval.type = DB_STR;
+						
+						bp = 0;
+						if(c==DBT_DELIM || 
+								(ccol == dtp->nrcols-1
+								 && (c == DBT_DELIM_R || c==EOF)))
+							dtval.nul = 1;
+						else
+						{
+							dtval.nul = 0;
+							while(c!=DBT_DELIM && c!=DBT_DELIM_R && c!=EOF)
+							{
+								if(c=='\\')
+								{
+									c = fgetc(fin);
+									switch(c)
+									{
+										case 'n':
+											c = '\n';	
+										break;
+										case 'r':
+											c = '\r';
+										break;
+										case 't':
+											c = '\t';
+										break;
+										case '\\':
+											c = '\\';
+										break;
+										case DBT_DELIM:
+											c = DBT_DELIM;
+										break;
+										case '0':
+											c = 0;
+										break;
+										default:
+											goto clean;
+									}
+								}
+								buf[bp++] = c;
+								c = fgetc(fin);
+							}
+							dtval.val.str_val.s = buf;
+							dtval.val.str_val.len = bp;
+							//DBG("DBT:dbt_load_file: data[%d,%d]=%.*s\n",
+							///	crow, ccol, bp, buf);
+						}
+						if(c!=DBT_DELIM && c!=DBT_DELIM_R && c!=EOF)
+							goto clean;
+						if(dbt_row_set_val(rowp,&dtval,DB_STR,ccol))
+							goto clean;
+					break;
+					default:
+						goto clean;
+				}
+				if(c==DBT_DELIM)
+					c = fgetc(fin);
+				ccol++;
+			break; // state DBT_DATA_ST
+		}
+	}
+
+	if(max_auto)
+		dtp->auto_val = max_auto;
+
+done:
+	if(fin)
+		fclose(fin);
+	return dtp;
+clean:
+	/// ????? FILL IT IN - incomplete row/column
+	// memory leak?!?! with last incomplete row
+	DBG("DBT:dbt_load_file: error at row=%d col=%d c=%c\n", crow+1, ccol+1, c);
+	if(dtp)
+		dbt_table_free(dtp);
+	return NULL;
+}
+
+
+/**
+ *
+ */
+int dbt_print_table(dbt_table_p _dtp, str *_dbn)
+{
+	dbt_column_p colp = NULL;
+	dbt_row_p rowp = NULL;
+	FILE *fout = NULL;
+	int ccol;
+	char *p, path[512];
+	
+	if(!_dtp || !_dtp->name.s || _dtp->name.len <= 0)
+		return -1;
+
+	if(!_dbn || !_dbn->s || _dbn->len <= 0)
+	{
+		fout = stdout;
+		fprintf(fout, "\n Content of [%.*s]\n", _dtp->name.len, _dtp->name.s);
+	}
+	else
+	{
+		if(_dtp->name.len+_dbn->len > 510)
+			return -1;
+		strncpy(path, _dbn->s, _dbn->len);
+		path[_dbn->len] = '/';
+		strncpy(path+_dbn->len+1, _dtp->name.s, _dtp->name.len);
+		path[_dbn->len+_dtp->name.len+1] = 0;
+		fout = fopen(path, "wt");
+		if(!fout)
+			return -1;	
+	}
+	
+	colp = _dtp->cols;
+	while(colp)
+	{
+		switch(colp->type)
+		{
+			case DB_INT:
+				fprintf(fout, "%.*s(int", colp->name.len, colp->name.s);
+			break;
+			case DB_FLOAT:
+				fprintf(fout, "%.*s(float", colp->name.len, colp->name.s);
+			break;
+			case DB_DOUBLE:
+				fprintf(fout, "%.*s(double", colp->name.len, colp->name.s);
+			break;
+			case DB_STR:
+				fprintf(fout, "%.*s(str", colp->name.len, colp->name.s);
+			break;
+			default:
+				if(fout!=stdout)
+					fclose(fout);
+				return -1;
+		}
+		
+		if(colp->flag & DBT_FLAG_NULL)
+				fprintf(fout,",null");
+		else if(colp->type==DB_INT && colp->flag & DBT_FLAG_AUTO)
+					fprintf(fout,",auto");
+		fprintf(fout,")");
+		
+		colp = colp->next;
+		if(colp)
+			fprintf(fout,"%c", DBT_DELIM_C);
+	}
+	fprintf(fout, "%c", DBT_DELIM_R);
+	rowp = _dtp->rows;
+	while(rowp)
+	{
+		for(ccol=0; ccol<_dtp->nrcols; ccol++)
+		{
+			switch(_dtp->colv[ccol]->type)
+			{
+				case DB_INT:
+					if(!rowp->fields[ccol].nul)
+						fprintf(fout,"%d",
+								rowp->fields[ccol].val.int_val);
+				break;
+				case DB_FLOAT:
+					if(!rowp->fields[ccol].nul)
+						fprintf(fout, "%.2f",
+								rowp->fields[ccol].val.float_val);
+				break;
+				case DB_DOUBLE:
+					if(!rowp->fields[ccol].nul)
+						fprintf(fout, "%.2f",
+								rowp->fields[ccol].val.double_val);
+				break;
+				case DB_STR:
+					if(!rowp->fields[ccol].nul)
+					{
+						p = rowp->fields[ccol].val.str_val.s;
+						while(p < rowp->fields[ccol].val.str_val.s
+								+ rowp->fields[ccol].val.str_val.len)
+						{
+							switch(*p)
+							{
+								case '\n':
+									fprintf(fout, "\\n");
+								break;
+								case '\r':
+									fprintf(fout, "\\r");
+								break;
+								case '\t':
+									fprintf(fout, "\\t");
+								break;
+								case '\\':
+									fprintf(fout, "\\\\");
+								break;
+								case DBT_DELIM:
+									fprintf(fout, "\\%c", DBT_DELIM);
+								break;
+								case '\0':
+									fprintf(fout, "\\0");
+								break;
+								default:
+									fprintf(fout, "%c", *p);
+							}
+							p++;
+						}
+					}
+				break;
+				default:
+					if(fout!=stdout)
+						fclose(fout);
+					return -1;
+			}
+			if(ccol<_dtp->nrcols-1)
+				fprintf(fout, "%c",DBT_DELIM);
+		}
+		fprintf(fout, "%c", DBT_DELIM_R);
+		rowp = rowp->next;
+	}
+	
+	if(fout!=stdout)
+		fclose(fout);
+	
+	return 0;
+}
+

+ 548 - 0
dbtext/dbt_lib.c

@@ -0,0 +1,548 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-01-30 created by Daniel
+ * 
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+
+#include "dbt_util.h"
+#include "dbt_lib.h"
+
+static dbt_cache_p *_cachedb = NULL;
+static gen_lock_t *_cachesem = NULL;
+
+/**
+ *
+ */
+int dbt_init_cache()
+{
+	if(!_cachesem)
+	{
+	/* init locks */
+		_cachesem = lock_alloc();
+		if(!_cachesem)
+		{
+			LOG(L_CRIT,"dbtext:dbt_init_cache: could not alloc a lock\n");
+			return -1;
+		}
+		if (lock_init(_cachesem)==0)
+		{
+			LOG(L_CRIT,"dbtext:dbt_init_cache: could not initialize a lock\n");
+			lock_dealloc(_cachesem);
+			return -1;
+		}
+	}
+	/* init pointer to caches list */
+	if (!_cachedb) {
+		_cachedb = shm_malloc( sizeof(dbt_cache_p) );
+		if (!_cachedb) {
+			LOG(L_CRIT,"dbtext:dbt_init_cache: no enough shm mem\n");
+			lock_dealloc(_cachesem);
+			return -1;
+		}
+		*_cachedb = NULL;
+	}
+	
+	return 0;
+}
+
+/**
+ *
+ */
+dbt_cache_p dbt_cache_get_db(str *_s)
+{
+	dbt_cache_p _dcache=NULL;;
+	if(!_cachesem || !_cachedb)
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db:dbtext cache is not initialized!\n");
+		return NULL;
+	}
+	if(!_s || !_s->s || _s->len<=0)
+		return NULL;
+
+	DBG("DBT:dbt_cache_get_db: looking for db %.*s!\n",_s->len,_s->s);
+
+	lock_get(_cachesem);
+	
+	_dcache = *_cachedb;
+	while(_dcache)
+	{
+		lock_get(&_dcache->sem);
+		if(_dcache->dbp)
+		{
+			if(_dcache->dbp->name.len==_s->len 
+					&& !strncasecmp(_dcache->dbp->name.s, _s->s, _s->len))
+			{
+				lock_release(&_dcache->sem);
+				DBG("DBT:dbt_cache_get_db: db already cached!\n");
+				goto done;
+			}
+		}
+		lock_release(&_dcache->sem);
+		
+		_dcache = _dcache->next;
+	}
+	if(!dbt_is_database(_s))
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db: database [%.*s] does not exists!\n", 
+				_s->len, _s->s);
+		goto done;
+	}
+	DBG("DBT:dbt_cache_get_db: new db!\n");
+	
+	_dcache = (dbt_cache_p)shm_malloc(sizeof(dbt_cache_t));
+	if(!_dcache)
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db: no memory for dbt_cache_t.\n");
+		goto done;
+	}
+	
+	_dcache->dbp = (dbt_db_p)shm_malloc(sizeof(dbt_db_t));
+	if(!_dcache->dbp)
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db: no memory for dbt_db_t!\n");
+		shm_free(_dcache);
+		goto done;
+	}
+
+	_dcache->dbp->name.s = (char*)shm_malloc(_s->len*sizeof(char));
+	if(!_dcache->dbp->name.s)
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db: no memory for s!!\n");
+		shm_free(_dcache->dbp);
+		shm_free(_dcache);
+		_dcache = NULL;
+		goto done;
+	}
+	
+	memcpy(_dcache->dbp->name.s, _s->s, _s->len);
+	_dcache->dbp->name.len = _s->len;
+	_dcache->dbp->tables = NULL;
+	
+	if(!lock_init(&_dcache->sem))
+	{
+		LOG(L_ERR, "DBT:dbt_cache_get_db: no sems!\n");
+		shm_free(_dcache->dbp->name.s);
+		shm_free(_dcache->dbp);
+		shm_free(_dcache);
+		_dcache = NULL;
+		goto done;
+	}
+	
+	_dcache->prev = NULL;
+
+	if(*_cachedb)
+	{
+		_dcache->next = *_cachedb;
+		(*_cachedb)->prev = _dcache;
+	}
+	else
+		_dcache->next = NULL;
+
+	*_cachedb = _dcache;
+
+done:
+	lock_release(_cachesem);
+	return _dcache;
+}
+
+/**
+ *
+ */
+int dbt_cache_check_db(str *_s)
+{
+	dbt_cache_p _dcache=NULL;;
+	if(!_cachesem || !(*_cachedb) || !_s || !_s->s || _s->len<=0)
+		return -1;
+	
+	lock_get(_cachesem);
+	
+	_dcache = *_cachedb;
+	while(_dcache)
+	{
+		if(_dcache->dbp)
+		{
+			if(_dcache->dbp->name.len == _s->len &&
+				strncasecmp(_dcache->dbp->name.s, _s->s, _s->len))
+			{
+				lock_release(_cachesem);
+				return 0;
+			}
+		}
+		_dcache = _dcache->next;
+	}
+	
+	lock_release(_cachesem);
+	return -1;
+}
+
+/**
+ *
+ */
+int dbt_cache_del_db(str *_s)
+{
+	dbt_cache_p _dcache=NULL;;
+	if(!_cachesem || !(*_cachedb) || !_s || !_s->s || _s->len<=0)
+		return -1;
+	
+	lock_get(_cachesem);
+	
+	_dcache = *_cachedb;
+	while(_dcache)
+	{
+		if(_dcache->dbp)
+		{
+			if(_dcache->dbp->name.len == _s->len 
+					&& strncasecmp(_dcache->dbp->name.s, _s->s, _s->len))
+				break;
+		}
+		// else - delete this cell
+		_dcache = _dcache->next;
+	}
+	if(!_dcache)
+	{
+		lock_release(_cachesem);
+		return 0;
+	}
+	
+	if(_dcache->prev)
+		(_dcache->prev)->next = _dcache->next;
+	else
+		*_cachedb = _dcache->next;
+
+	if(_dcache->next)
+		(_dcache->next)->prev = _dcache->prev;
+	
+	lock_release(_cachesem);
+	
+	dbt_cache_free(_dcache);
+	
+	return 0;
+}
+
+/**
+ *
+ */
+tbl_cache_p dbt_db_get_table(dbt_cache_p _dc, str *_s)
+{
+//	dbt_db_p _dbp = NULL;
+	tbl_cache_p _tbc = NULL;
+	dbt_table_p _dtp = NULL;
+
+	if(!_dc || !_s || !_s->s || _s->len<=0)
+		return NULL;
+
+	lock_get(&_dc->sem);
+	if(!_dc->dbp)
+	{
+		lock_release(&_dc->sem);
+		return NULL;
+	}
+	
+	_tbc = _dc->dbp->tables;
+	while(_tbc)
+	{
+		if(_tbc->dtp)
+		{
+			lock_get(&_tbc->sem);
+			if(_tbc->dtp->name.len == _s->len 
+				&& !strncasecmp(_tbc->dtp->name.s, _s->s, _s->len ))
+			{
+				lock_release(&_tbc->sem);
+				lock_release(&_dc->sem);
+				return _tbc;
+			}
+			lock_release(&_tbc->sem);
+		}
+		_tbc = _tbc->next;
+	}
+
+	// new table
+	_tbc = tbl_cache_new();
+	if(!_tbc)
+	{
+		lock_release(&_dc->sem);
+		return NULL;
+	}
+	
+	_dtp = dbt_load_file(_s, &(_dc->dbp->name));
+
+#ifdef DBT_EXTRA_DEBUG
+	DBG("DTB:dbt_db_get_table: %.*s\n", _s->len, _s->s);
+	dbt_print_table(_dtp, NULL);
+#endif
+
+	if(!_dtp)
+	{
+		lock_release(&_dc->sem);
+		return NULL;
+	}
+	_tbc->dtp = _dtp;
+	
+	if(_dc->dbp->tables)
+		(_dc->dbp->tables)->prev = _tbc;
+	_tbc->next = _dc->dbp->tables;
+	_dc->dbp->tables = _tbc;
+		
+	lock_release(&_dc->sem);
+
+	return _tbc;
+}
+
+/**
+ *
+ */
+int dbt_db_del_table(dbt_cache_p _dc, str *_s)
+{
+	tbl_cache_p _tbc = NULL;
+	if(!_dc || !_s || !_s->s || _s->len<=0)
+		return -1;
+
+	lock_get(&_dc->sem);
+	if(!_dc->dbp)
+	{
+		lock_release(&_dc->sem);
+		return -1;
+	}
+
+	_tbc = _dc->dbp->tables;
+	while(_tbc)
+	{
+		if(_tbc->dtp)
+		{
+			lock_get(&_tbc->sem);
+			if(_tbc->dtp->name.len == _s->len 
+				&& !strncasecmp(_tbc->dtp->name.s, _s->s, _s->len))
+			{
+				if(_tbc->prev)
+					(_tbc->prev)->next = _tbc->next;
+				else
+					_dc->dbp->tables = _tbc->next;
+	
+				if(_tbc->next)
+					(_tbc->next)->prev = _tbc->prev;
+				break;
+			}
+			lock_release(&_tbc->sem);
+		}
+		_tbc = _tbc->next;
+	}
+
+	lock_release(&_dc->sem);
+
+	tbl_cache_free(_tbc);
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_cache_destroy()
+{
+	dbt_cache_p _dc=NULL, _dc0=NULL;
+	
+	if(!_cachesem)
+		return -1;
+	
+	lock_get(_cachesem);
+	if(	_cachedb!=NULL )
+	{
+		_dc = *_cachedb;
+		while(_dc)
+		{
+			_dc0 = _dc;
+			_dc = _dc->next;
+			dbt_cache_free(_dc0);
+		}
+		shm_free(_cachedb);
+	}
+	lock_destroy(_cachesem);
+	lock_dealloc(_cachesem);
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_cache_print(int _f)
+{
+	dbt_cache_p _dc=NULL;
+	tbl_cache_p _tbc = NULL;
+	
+	if(!_cachesem)
+		return -1;
+	
+	lock_get(_cachesem);
+	
+	_dc = *_cachedb;
+	while(_dc)
+	{
+		lock_get(&_dc->sem);
+		if(_dc->dbp)
+		{
+			if(_f)
+				fprintf(stdout, "\n--- Database [%.*s]\n", _dc->dbp->name.len,
+								_dc->dbp->name.s);
+
+			_tbc = _dc->dbp->tables;
+			while(_tbc)
+			{
+				lock_get(&_tbc->sem);
+				if(_tbc->dtp)
+				{
+					if(_f)
+					{
+						fprintf(stdout, "\n----- Table [%.*s]\n",
+								_tbc->dtp->name.len, _tbc->dtp->name.s);
+						fprintf(stdout, "-------  LA=<%d> FL=<%x> AC=<%d>"
+								" AV=<%d>\n", _tbc->dtp->mark, _tbc->dtp->flag,
+								_tbc->dtp->auto_col, _tbc->dtp->auto_val);
+						dbt_print_table(_tbc->dtp, NULL);
+					}
+					else
+					{
+						if(_tbc->dtp->flag & DBT_TBFL_MODI)
+						{
+							dbt_print_table(_tbc->dtp, &(_dc->dbp->name));
+							dbt_table_update_flags(_tbc->dtp,DBT_TBFL_MODI, 
+									DBT_FL_UNSET, 0);
+						}
+					}
+				}
+				lock_release(&_tbc->sem);
+				_tbc = _tbc->next;
+			}
+		}
+		lock_release(&_dc->sem);
+		
+		_dc = _dc->next;
+	}
+	
+	lock_release(_cachesem);
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_cache_free(dbt_cache_p _dc)
+{
+	if(!_dc)
+		return -1;
+
+	lock_get(&_dc->sem);
+
+	if(_dc->dbp)
+		dbt_db_free(_dc->dbp);
+	
+	lock_destroy(&_dc->sem);
+
+	shm_free(_dc);
+
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_db_free(dbt_db_p _dbp)
+{
+	tbl_cache_p _tbc = NULL, _tbc0=NULL;
+	if(!_dbp)
+		return -1;
+
+	_tbc = _dbp->tables;
+
+	while(_tbc)
+	{
+		_tbc0 = _tbc;
+		tbl_cache_free(_tbc0);
+		_tbc = _tbc->next;
+	}
+	
+	if(_dbp->name.s)
+		shm_free(_dbp->name.s);
+	
+	shm_free(_dbp);
+
+	return 0;
+}
+
+/**
+ *
+ */
+tbl_cache_p tbl_cache_new()
+{
+	tbl_cache_p _tbc = NULL;
+	_tbc = (tbl_cache_p)shm_malloc(sizeof(tbl_cache_t));
+	if(!_tbc)
+		return NULL;
+	if(!lock_init(&_tbc->sem))
+	{
+		shm_free(_tbc);
+		return NULL;
+	}
+	return _tbc;
+}
+
+/**
+ *
+ */
+int tbl_cache_free(tbl_cache_p _tbc)
+{
+	// FILL IT IN ?????????????
+	if(!_tbc)
+		return -1;
+	lock_get(&_tbc->sem);
+
+	if(_tbc->dtp)
+		dbt_table_free(_tbc->dtp);
+	
+	lock_destroy(&_tbc->sem);
+	shm_free(_tbc);
+	
+	return 0;
+}
+

+ 168 - 0
dbtext/dbt_lib.h

@@ -0,0 +1,168 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-01-30 created by Daniel
+ * 
+ */
+
+
+#ifndef _DBT_LIB_H_
+#define _DBT_LIB_H_
+
+#include "../../str.h"
+#include "../../db/db_val.h"
+#include "../../locking.h"
+
+#define DBT_FLAG_UNSET  0
+#define DBT_FLAG_NULL   1
+#define DBT_FLAG_AUTO   2
+
+#define DBT_TBFL_ZERO	0
+#define DBT_TBFL_MODI	1
+
+#define DBT_FL_IGN		-1
+#define DBT_FL_SET		0
+#define DBT_FL_UNSET	1
+
+#define DBT_DELIM	':'
+#define DBT_DELIM_C	' '
+#define DBT_DELIM_R	'\n'
+
+/***
+typedef struct _dbt_val
+{
+	int null;
+	union
+	{
+		int int_val;
+		double double_val;
+		str str_val;
+	} val;
+} dbt_val_t, *dbt_val_p;
+***/
+
+typedef db_val_t dbt_val_t, *dbt_val_p;
+
+typedef struct _dbt_row
+{
+	dbt_val_p fields;
+	struct _dbt_row *prev;
+	struct _dbt_row *next;
+	
+} dbt_row_t, *dbt_row_p;
+
+typedef struct _dbt_column
+{
+	str name;
+	int type;
+	int flag;
+	struct _dbt_column *prev;
+	struct _dbt_column *next;
+	
+} dbt_column_t, *dbt_column_p;
+
+
+typedef struct _dbt_table
+{
+	str name;
+	int mark;
+	int flag;
+	int auto_col;
+	int auto_val;
+	int nrcols;
+	dbt_column_p cols;
+	dbt_column_p *colv;
+	int nrrows;
+	dbt_row_p rows;
+} dbt_table_t, *dbt_table_p;
+
+typedef struct _tbl_cache
+{
+	gen_lock_t sem;
+	dbt_table_p dtp;
+	struct _tbl_cache *prev;
+	struct _tbl_cache *next;	
+} tbl_cache_t, *tbl_cache_p;
+
+typedef struct _dbt_database
+{
+	str name;
+	tbl_cache_p tables;
+} dbt_db_t, *dbt_db_p;
+
+typedef struct _dbt_cache 
+{
+	gen_lock_t sem;
+	dbt_db_p dbp;
+	struct _dbt_cache *prev;
+	struct _dbt_cache *next;
+	
+} dbt_cache_t, *dbt_cache_p;
+
+
+
+int dbt_init_cache();
+int dbt_cache_destroy();
+int dbt_cache_print(int);
+
+dbt_cache_p dbt_cache_get_db(str*);
+int dbt_cache_check_db(str*);
+int dbt_cache_del_db(str*);
+tbl_cache_p dbt_db_get_table(dbt_cache_p, str*);
+int dbt_db_del_table(dbt_cache_p, str*);
+
+int dbt_db_free(dbt_db_p);
+int dbt_cache_free(dbt_cache_p);
+
+dbt_column_p dbt_column_new(char*, int);
+dbt_row_p dbt_row_new(int);
+dbt_table_p dbt_table_new(char*, int);
+tbl_cache_p tbl_cache_new();
+
+int dbt_row_free(dbt_table_p, dbt_row_p);
+int dbt_column_free(dbt_column_p);
+int dbt_table_free_rows(dbt_table_p);
+int dbt_table_free(dbt_table_p);
+int tbl_cache_free(tbl_cache_p);
+
+
+int dbt_row_set_val(dbt_row_p, dbt_val_p, int, int);
+int dbt_row_update_val(dbt_row_p, dbt_val_p, int, int);
+int dbt_table_add_row(dbt_table_p, dbt_row_p);
+int dbt_table_check_row(dbt_table_p, dbt_row_p);
+int dbt_table_update_flags(dbt_table_p, int, int, int);
+
+dbt_table_p dbt_load_file(str *, str *);
+int dbt_print_table(dbt_table_p, str *);
+
+#endif
+

+ 576 - 0
dbtext/dbt_res.c

@@ -0,0 +1,576 @@
+/*
+ * $Id$
+ *
+ * DBText module core functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText module interface
+ * 2003-06-05 fixed bug: when comparing two values and the first was less than
+ *           the second one, the result of 'dbt_row_match' was always true,
+ *           thanks to Gabriel, (Daniel)
+ * 2003-02-04 created by Daniel
+ * 
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include "../../mem/mem.h"
+
+#include "dbt_res.h"
+
+dbt_result_p dbt_result_new(dbt_table_p _dtp, int *_lres, int _sz)
+{
+	dbt_result_p _dres = NULL;
+	int i, n;
+	char *p;
+	
+	if(!_dtp || _sz < 0)
+		return NULL;
+
+	if(!_lres)
+		_sz = _dtp->nrcols;
+	
+	_dres = (dbt_result_p)pkg_malloc(sizeof(dbt_result_t));
+	if(!_dres)
+		return NULL;
+	_dres->colv = (dbt_column_p)pkg_malloc(_sz*sizeof(dbt_column_t));
+	if(!_dres->colv)
+	{
+		DBG("DBT:dbt_result_new: no memory!\n");
+		pkg_free(_dres);
+		return NULL;
+	}
+	DBG("DBT:dbt_result_new: new res with %d cols\n", _sz);
+	for(i = 0; i < _sz; i++)
+	{
+		n = (_lres)?_dtp->colv[_lres[i]]->name.len:_dtp->colv[i]->name.len;
+		p = (_lres)?_dtp->colv[_lres[i]]->name.s:_dtp->colv[i]->name.s;
+		_dres->colv[i].name.s = (char*)pkg_malloc((n+1)*sizeof(char));
+		if(!_dres->colv[i].name.s)
+		{
+			DBG("DBT:dbt_result_new: no memory\n");
+			goto clean;
+		}
+		_dres->colv[i].name.len = n;
+		strncpy(_dres->colv[i].name.s, p, n);
+		_dres->colv[i].name.s[n] = 0;
+		_dres->colv[i].type =
+				(_lres)?_dtp->colv[_lres[i]]->type:_dtp->colv[i]->type;
+	}
+	
+	_dres->nrcols = _sz;
+	_dres->nrrows = 0;
+	_dres->rows = NULL;
+
+	return _dres;
+clean:
+	while(i>=0)
+	{
+		if(_dres->colv[i].name.s)
+			pkg_free(_dres->colv[i].name.s);
+		i--;
+	}
+	pkg_free(_dres->colv);
+	pkg_free(_dres);
+	
+	return NULL;
+}
+
+int dbt_result_free(dbt_result_p _dres)
+{
+	dbt_row_p _rp=NULL, _rp0=NULL;
+	int i;
+
+	if(!_dres)
+		return -1;
+	_rp = _dres->rows;
+	while(_rp)
+	{
+		_rp0=_rp;
+		if(_rp0->fields)
+		{
+			for(i=0; i<_dres->nrcols; i++)
+			{
+				if(_dres->colv[i].type==DB_STR 
+						&& _rp0->fields[i].val.str_val.s)
+					pkg_free(_rp0->fields[i].val.str_val.s);
+			}
+			pkg_free(_rp0->fields);
+		}
+		pkg_free(_rp0);
+		_rp=_rp->next;
+	}
+	if(_dres->colv)
+	{
+		for(i=0; i<_dres->nrcols; i++)
+		{
+			if(_dres->colv[i].name.s)
+				pkg_free(_dres->colv[i].name.s);
+		}
+		pkg_free(_dres->colv);
+	}
+
+	pkg_free(_dres);
+
+	return 0;
+}
+
+int dbt_result_add_row(dbt_result_p _dres, dbt_row_p _drp)
+{
+	if(!_dres || !_drp)
+		return -1;
+	_dres->nrrows++;
+	
+	if(_dres->rows)
+		(_dres->rows)->prev = _drp;
+	_drp->next = _dres->rows;
+	_dres->rows = _drp;
+
+	return 0;
+}
+
+int* dbt_get_refs(dbt_table_p _dtp, db_key_t* _k, int _n)
+{
+	int i, j, *_lref=NULL;
+	
+	if(!_dtp || !_k || _n < 0)
+		return NULL;
+
+	_lref = (int*)pkg_malloc(_n*sizeof(int));
+	if(!_lref)
+		return NULL;
+
+	for(i=0; i < _n; i++)
+	{
+		for(j=0; j<_dtp->nrcols; j++)
+		{
+			if(strlen(_k[i])==_dtp->colv[j]->name.len
+				&& !strncasecmp(_k[i], _dtp->colv[j]->name.s,
+						_dtp->colv[j]->name.len))
+			{
+				_lref[i] = j;
+				break;
+			}
+		}
+		if(j>=_dtp->nrcols)
+		{
+			DBG("DBT:dbt_get_refs: ERROR column <%s> not found\n", _k[i]);
+			pkg_free(_lref);
+			return NULL;
+		}
+	}
+	return _lref;
+}	
+
+
+int dbt_row_match(dbt_table_p _dtp, dbt_row_p _drp, int* _lkey,
+				 db_op_t* _op, db_val_t* _v, int _n)
+{
+	int i, res;
+	if(!_dtp || !_drp)
+		return 0;
+	if(!_lkey)
+		return 1;
+	for(i=0; i<_n; i++)
+	{
+		res = dbt_cmp_val(&_drp->fields[_lkey[i]], &_v[i]);
+		if(!_op || !strcmp(_op[i], OP_EQ))
+		{
+			if(res!=0)
+				return 0;
+		}else{
+		if(!strcmp(_op[i], OP_LT))
+		{
+			if(res!=-1)
+				return 0;
+		}else{
+		if(!strcmp(_op[i], OP_GT))
+		{
+			if(res!=1)
+				return 0;
+		}else{
+		if(!strcmp(_op[i], OP_LEQ))
+		{
+			if(res==1)
+				return 0;
+		}else{
+		if(!strcmp(_op[i], OP_GEQ))
+		{
+			if(res==-1)
+				return 0;
+		}else{
+			return 0;
+		}}}}}		
+	}
+	return 1;
+}
+
+int dbt_result_extract_fields(dbt_table_p _dtp, dbt_row_p _drp,
+				int* _lres, dbt_result_p _dres)
+{
+	dbt_row_p _rp=NULL;
+	int i, n;
+	
+	if(!_dtp || !_drp || !_dres || _dres->nrcols<=0)	
+		return -1;
+	
+	_rp = dbt_result_new_row(_dres);
+	if(!_rp)
+		return -1;
+
+	for(i=0; i<_dres->nrcols; i++)
+	{
+		n = (_lres)?_lres[i]:i;
+		if(dbt_is_neq_type(_dres->colv[i].type, _dtp->colv[n]->type))
+		{
+			DBG("DBT:dbt_result_extract_fields: wrong types!\n");
+			goto clean;
+		}
+		_rp->fields[i].nul = _drp->fields[n].nul;
+		if(_rp->fields[i].nul)
+		{
+			memset(&(_rp->fields[i].val), 0, sizeof(_rp->fields[i].val));
+			continue;
+		}
+		
+		switch(_dres->colv[i].type)
+		{
+			case DB_INT:
+			case DB_DATETIME:
+			case DB_BITMAP:
+				_rp->fields[i].type = DB_INT;
+				_rp->fields[i].val.int_val = _drp->fields[n].val.int_val;
+			break;
+			case DB_FLOAT:
+				_rp->fields[i].type = DB_FLOAT;
+				_rp->fields[i].val.float_val=_drp->fields[n].val.float_val;
+			break;
+			case DB_DOUBLE:
+				_rp->fields[i].type = DB_DOUBLE;
+				_rp->fields[i].val.double_val=_drp->fields[n].val.double_val;
+			break;
+			case DB_STRING:
+			case DB_STR:
+			case DB_BLOB:
+				_rp->fields[i].type = DB_STR;
+				_rp->fields[i].val.str_val.len =
+						_drp->fields[n].val.str_val.len;
+				_rp->fields[i].val.str_val.s =(char*)pkg_malloc(sizeof(char)*
+						(_drp->fields[n].val.str_val.len+1));
+				if(!_rp->fields[i].val.str_val.s)
+					goto clean;
+				strncpy(_rp->fields[i].val.str_val.s,
+						_drp->fields[n].val.str_val.s,
+						_rp->fields[i].val.str_val.len);
+				_rp->fields[i].val.str_val.s[_rp->fields[i].val.str_val.len]=0;
+			break;
+			default:
+				goto clean;
+		}
+	}
+
+	if(_dres->rows)
+		(_dres->rows)->prev = _rp;
+	_rp->next = _dres->rows;
+	_dres->rows = _rp;
+	_dres->nrrows++;
+
+	return 0;
+
+clean:
+	DBG("DBT:dbt_result_extract_fields: make clean!\n");
+	while(i>=0)
+	{
+		if(_rp->fields[i].type == DB_STR
+				&& !_rp->fields[i].nul
+				&& _rp->fields[i].val.str_val.s)
+			pkg_free(_rp->fields[i].val.str_val.s);
+				
+		i--;
+	}
+	pkg_free(_rp->fields);
+	pkg_free(_rp);
+
+	return -1;
+}
+
+int dbt_result_print(dbt_result_p _dres)
+{
+#ifdef DBT_EXTRA_DEBUG
+	int i;
+	FILE *fout = stdout;
+	dbt_row_p rowp = NULL;
+	char *p;
+
+	if(!_dres || _dres->nrcols<=0)
+		return -1;
+
+	fprintf(fout, "\nContent of result\n");
+	
+	for(i=0; i<_dres->nrcols; i++)
+	{
+		switch(_dres->colv[i].type)
+		{
+			case DB_INT:
+				fprintf(fout, "%.*s(int", _dres->colv[i].name.len,
+								_dres->colv[i].name.s);
+				if(_dres->colv[i].flag & DBT_FLAG_NULL)
+					fprintf(fout, ",null");
+				fprintf(fout, ") ");
+			break;
+			case DB_FLOAT:
+				fprintf(fout, "%.*s(float", _dres->colv[i].name.len,
+							_dres->colv[i].name.s);
+				if(_dres->colv[i].flag & DBT_FLAG_NULL)
+					fprintf(fout, ",null");
+				fprintf(fout, ") ");
+			break;
+			case DB_DOUBLE:
+				fprintf(fout, "%.*s(double", _dres->colv[i].name.len,
+							_dres->colv[i].name.s);
+				if(_dres->colv[i].flag & DBT_FLAG_NULL)
+					fprintf(fout, ",null");
+				fprintf(fout, ") ");
+			break;
+			case DB_STR:
+				fprintf(fout, "%.*s(str", _dres->colv[i].name.len,
+						_dres->colv[i].name.s);
+				if(_dres->colv[i].flag & DBT_FLAG_NULL)
+					fprintf(fout, ",null");
+				fprintf(fout, ") ");
+			break;
+			default:
+				return -1;
+		}
+	}
+	fprintf(fout, "\n");
+	rowp = _dres->rows;
+	while(rowp)
+	{
+		for(i=0; i<_dres->nrcols; i++)
+		{
+			switch(_dres->colv[i].type)
+			{
+				case DB_INT:
+					if(rowp->fields[i].nul)
+						fprintf(fout, "N ");
+					else
+						fprintf(fout, "%d ",
+								rowp->fields[i].val.int_val);
+				break;
+				case DB_FLOAT:
+					if(rowp->fields[i].nul)
+						fprintf(fout, "N ");
+					else
+						fprintf(fout, "%.2f ",
+								rowp->fields[i].val.float_val);
+				break;
+				case DB_DOUBLE:
+					if(rowp->fields[i].nul)
+						fprintf(fout, "N ");
+					else
+						fprintf(fout, "%.2f ",
+								rowp->fields[i].val.double_val);
+				break;
+				case DB_STR:
+					fprintf(fout, "\"");
+					if(!rowp->fields[i].nul)
+					{
+						p = rowp->fields[i].val.str_val.s;
+						while(p < rowp->fields[i].val.str_val.s
+								+ rowp->fields[i].val.str_val.len)
+						{
+							switch(*p)
+							{
+								case '\n':
+									fprintf(fout, "\\n");
+								break;
+								case '\r':
+									fprintf(fout, "\\r");
+								break;
+								case '\t':
+									fprintf(fout, "\\t");
+								break;
+								case '\\':
+									fprintf(fout, "\\\\");
+								break;
+								case '"':
+									fprintf(fout, "\\\"");
+								break;
+								case '\0':
+									fprintf(fout, "\\0");
+								break;
+								default:
+									fprintf(fout, "%c", *p);
+							}
+							p++;
+						}
+					}
+					fprintf(fout, "\" ");
+				break;
+				default:
+					return -1;
+			}
+		}
+		fprintf(fout, "\n");
+		rowp = rowp->next;
+	}
+#endif
+
+	return 0;
+}
+
+int dbt_cmp_val(dbt_val_p _vp, db_val_t* _v)
+{
+	int _l, _n;
+	if(!_vp && !_v)
+		return 0;
+	if(!_v)
+		return 1;
+	if(!_vp)
+		return -1;
+	if(_vp->nul && _v->nul)
+		return 0;
+	if(_v->nul)
+		return 1;
+	if(_vp->nul)
+		return -1;
+	
+	switch(VAL_TYPE(_v))
+	{
+		case DB_INT:
+			return (_vp->val.int_val<_v->val.int_val)?-1:
+					(_vp->val.int_val>_v->val.int_val)?1:0;
+		case DB_FLOAT:
+			return (_vp->val.float_val<_v->val.float_val)?-1:
+					(_vp->val.float_val>_v->val.float_val)?1:0;
+		case DB_DOUBLE:
+			return (_vp->val.double_val<_v->val.double_val)?-1:
+					(_vp->val.double_val>_v->val.double_val)?1:0;
+		case DB_DATETIME:
+			return (_vp->val.int_val<_v->val.time_val)?-1:
+					(_vp->val.int_val>_v->val.time_val)?1:0;
+		case DB_STRING:
+			_l = strlen(_v->val.string_val);
+			_l = (_l>_vp->val.str_val.len)?_vp->val.str_val.len:_l;
+			_n = strncasecmp(_vp->val.str_val.s, _v->val.string_val, _l);
+			if(_n)
+				return _n;
+			if(_vp->val.str_val.len == strlen(_v->val.string_val))
+				return 0;
+			if(_l==_vp->val.str_val.len)
+				return -1;
+			return 1;
+		case DB_STR:
+			_l = _v->val.str_val.len;
+			_l = (_l>_vp->val.str_val.len)?_vp->val.str_val.len:_l;
+			_n = strncasecmp(_vp->val.str_val.s, _v->val.str_val.s, _l);
+			if(_n)
+				return _n;
+			if(_vp->val.str_val.len == _v->val.str_val.len)
+				return 0;
+			if(_l==_vp->val.str_val.len)
+				return -1;
+			return 1;
+		case DB_BLOB:
+			_l = _v->val.blob_val.len;
+			_l = (_l>_vp->val.str_val.len)?_vp->val.str_val.len:_l;
+			_n = strncasecmp(_vp->val.str_val.s, _v->val.blob_val.s, _l);
+			if(_n)
+				return _n;
+			if(_vp->val.str_val.len == _v->val.blob_val.len)
+				return 0;
+			if(_l==_vp->val.str_val.len)
+				return -1;
+			return 1;
+		case DB_BITMAP:
+			return (_vp->val.int_val<_v->val.bitmap_val)?-1:
+				(_vp->val.int_val>_v->val.bitmap_val)?1:0;
+	}
+	return -2;
+}
+
+dbt_row_p dbt_result_new_row(dbt_result_p _dres)
+{
+	dbt_row_p _drp = NULL;
+	if(!_dres || _dres->nrcols<=0)
+		return NULL;
+	
+	_drp = (dbt_row_p)pkg_malloc(sizeof(dbt_row_t));
+	if(!_drp)
+		return NULL;
+	memset(_drp, 0, sizeof(dbt_row_t));
+	_drp->fields = (dbt_val_p)pkg_malloc(_dres->nrcols*sizeof(dbt_val_t));
+	if(!_drp->fields)
+	{
+		pkg_free(_drp);
+		return NULL;
+	}
+	memset(_drp->fields, 0, _dres->nrcols*sizeof(dbt_val_t));
+
+	_drp->next = _drp->prev = NULL;
+
+	return _drp;
+}
+
+int dbt_is_neq_type(db_type_t _t0, db_type_t _t1)
+{
+	// DBG("DBT:dbt_is_neq_type: t0=%d t1=%d!\n", _t0, _t1);
+	if(_t0 == _t1)
+		return 0;
+	switch(_t1)
+	{
+		case DB_INT:
+			if(_t0==DB_DATETIME || _t0==DB_BITMAP)
+				return 0;
+		case DB_DATETIME:
+			if(_t0==DB_INT)
+				return 0;
+			if(_t0==DB_BITMAP)
+				return 0;
+		case DB_FLOAT:
+			break;
+		case DB_DOUBLE:
+			break;
+		case DB_STRING:
+			if(_t0==DB_STR)
+				return 0;
+		case DB_STR:
+			if(_t0==DB_STRING || _t0==DB_BLOB)
+				return 0;
+		case DB_BLOB:
+			if(_t0==DB_STR)
+				return 0;
+		case DB_BITMAP:
+			if (_t0==DB_INT)
+				return 0;
+	}
+	return 1;
+}
+

+ 81 - 0
dbtext/dbt_res.h

@@ -0,0 +1,81 @@
+/*
+ * $Id$
+ *
+ * DBText module core functions
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText module interface
+ *  
+ * 2003-02-04 created by Daniel
+ * 
+ */
+
+
+#ifndef _DBT_RES_H_
+#define _DBT_RES_H_
+
+#include "../../db/db_op.h"
+#include "../../lib/srdb2/db_res.h"
+
+#include "dbt_lib.h"
+
+typedef struct _dbt_result
+{
+	int nrcols;
+	int nrrows;
+	dbt_column_p colv;
+	dbt_row_p rows;
+} dbt_result_t, *dbt_result_p;
+
+//typedef db_res_t dbt_result_t, *dbt_result_p;
+
+typedef struct _dbt_con
+{
+	dbt_cache_p con;
+	dbt_result_p res;
+	dbt_row_p row;
+} dbt_con_t, *dbt_con_p;
+
+#define DBT_CON_CONNECTION(db_con) (((dbt_con_p)((db_con)->tail))->con)
+#define DBT_CON_RESULT(db_con)     (((dbt_con_p)((db_con)->tail))->res)
+#define DBT_CON_ROW(db_con)        (((dbt_con_p)((db_con)->tail))->row)
+
+dbt_result_p dbt_result_new(dbt_table_p, int*, int);
+int dbt_result_free(dbt_result_p);
+int dbt_row_match(dbt_table_p _dtp, dbt_row_p _drp, int* _lkey,
+				 db_op_t* _op, db_val_t* _v, int _n);
+int dbt_result_extract_fields(dbt_table_p _dtp, dbt_row_p _drp,
+				int* lres, dbt_result_p _dres);
+int dbt_result_print(dbt_result_p _dres);
+
+int* dbt_get_refs(dbt_table_p, db_key_t*, int);
+int dbt_cmp_val(dbt_val_p _vp, db_val_t* _v);
+dbt_row_p dbt_result_new_row(dbt_result_p _dres);
+int dbt_is_neq_type(db_type_t _t0, db_type_t _t1);
+
+#endif
+

+ 471 - 0
dbtext/dbt_tb.c

@@ -0,0 +1,471 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-02-03 created by Daniel
+ * 
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../../locking.h"
+
+#include "dbt_util.h"
+#include "dbt_lib.h"
+
+
+/**
+ *
+ */
+dbt_column_p dbt_column_new(char *_s, int _l)
+{
+	dbt_column_p dcp = NULL;
+	if(!_s || _l <=0)
+		return NULL;
+	dcp = (dbt_column_p)shm_malloc(sizeof(dbt_column_t));
+	if(!dcp)
+		return NULL;
+	dcp->name.s  = (char*)shm_malloc(_l*sizeof(char));
+	if(!dcp->name.s)
+	{
+		shm_free(dcp);
+		return NULL;
+	}
+	dcp->name.len = _l;
+	strncpy(dcp->name.s, _s, _l);
+	dcp->next = dcp->prev = NULL;
+	dcp->type = 0;
+	dcp->flag = DBT_FLAG_UNSET;
+
+	return dcp;
+}
+
+/**
+ *
+ */
+int dbt_column_free(dbt_column_p dcp)
+{
+	
+	if(!dcp)
+		return -1;
+	if(dcp->name.s)
+		shm_free(dcp->name.s);
+	shm_free(dcp);
+ 
+	return 0;
+}
+
+/**
+ *
+ */
+dbt_row_p dbt_row_new(int _nf)
+{
+	int i;
+	dbt_row_p _drp = NULL;
+
+	_drp = (dbt_row_p)shm_malloc(sizeof(dbt_row_t));
+	if(!_drp)
+		return NULL;
+	
+	_drp->fields = (dbt_val_p)shm_malloc(_nf*sizeof(dbt_val_t));
+	if(!_drp->fields)
+	{
+		shm_free(_drp);
+		return NULL;
+	}
+	memset(_drp->fields, 0, _nf*sizeof(dbt_val_t));
+	for(i=0; i<_nf; i++)
+		_drp->fields[i].nul = 1;
+
+	_drp->next = _drp->prev = NULL;
+
+	return _drp;
+}
+
+/**
+ *
+ */
+int dbt_row_free(dbt_table_p _dtp, dbt_row_p _drp)
+{
+	int i;
+	
+	if(!_dtp || !_drp)
+		return -1;
+	
+	if(_drp->fields)
+	{
+		for(i=0; i<_dtp->nrcols; i++)
+			if(_dtp->colv[i]->type==DB_STR
+					&& _drp->fields[i].val.str_val.s)
+				shm_free(_drp->fields[i].val.str_val.s);
+		shm_free(_drp->fields);
+	}
+	shm_free(_drp);
+
+	return 0;
+}
+
+/**
+ *
+ */
+dbt_table_p dbt_table_new(char *_s, int _l)
+{
+	dbt_table_p dtp = NULL;
+	if(!_s || _l <= 0)
+		return NULL;
+	
+	dtp = (dbt_table_p)shm_malloc(sizeof(dbt_table_t));
+	if(!dtp)
+		goto done;
+	dtp->name.s = (char*)shm_malloc(_l*sizeof(char));
+	if(!dtp->name.s)
+	{
+		shm_free(dtp);
+		dtp = NULL;
+		goto done;
+	}
+	memcpy(dtp->name.s, _s, _l);
+	dtp->name.len = _l;
+
+	dtp->rows = NULL;
+	dtp->cols = NULL;
+	dtp->colv = NULL;
+	dtp->mark = (int)time(NULL);
+	dtp->flag = DBT_TBFL_ZERO;
+	dtp->nrrows = dtp->nrcols = dtp->auto_val = 0;
+	dtp->auto_col = -1;
+	
+done:
+	return dtp;
+}
+
+/**
+ *
+ */
+int dbt_table_free_rows(dbt_table_p _dtp)
+{
+	dbt_row_p _rp=NULL, _rp0=NULL;
+	
+	if(!_dtp || !_dtp->rows || !_dtp->colv)
+		return -1;
+	_rp = _dtp->rows;
+	while(_rp)
+	{
+		_rp0=_rp;
+		_rp=_rp->next;
+		dbt_row_free(_dtp, _rp0);
+	}
+	
+	dbt_table_update_flags(_dtp, DBT_TBFL_MODI, DBT_FL_SET, 1);
+	
+	_dtp->rows = NULL;
+	_dtp->nrrows = 0;
+
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_table_add_row(dbt_table_p _dtp, dbt_row_p _drp)
+{
+	if(!_dtp || !_drp)
+		return -1;
+	
+	if(dbt_table_check_row(_dtp, _drp))
+		return -1;
+	
+	dbt_table_update_flags(_dtp, DBT_TBFL_MODI, DBT_FL_SET, 1);
+	
+	if(_dtp->rows)
+		(_dtp->rows)->prev = _drp;
+	_drp->next = _dtp->rows;
+	_dtp->rows = _drp;
+	_dtp->nrrows++;
+
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_table_free(dbt_table_p _dtp)
+{
+	dbt_column_p _cp=NULL, _cp0=NULL;
+	
+	if(!_dtp)
+		return -1;
+
+	if(_dtp->name.s)
+		shm_free(_dtp->name.s);
+	
+	if(_dtp->rows && _dtp->nrrows>0)
+		dbt_table_free_rows(_dtp);
+	
+	_cp = _dtp->cols;
+	while(_cp)
+	{
+		_cp0=_cp;
+		_cp=_cp->next;
+		dbt_column_free(_cp0);
+	}
+	if(_dtp->colv)
+		shm_free(_dtp->colv);
+
+	shm_free(_dtp);
+
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_row_set_val(dbt_row_p _drp, dbt_val_p _vp, int _t, int _idx)
+{
+	if(!_drp || !_vp || _idx<0)
+		return -1;
+	
+	_drp->fields[_idx].nul = _vp->nul;
+	_drp->fields[_idx].type = _t;
+
+	if(!_vp->nul)
+	{
+		switch(_t)
+		{
+			case DB_STR:
+			case DB_BLOB:
+				_drp->fields[_idx].type = DB_STR;
+				_drp->fields[_idx].val.str_val.s = 
+					(char*)shm_malloc(_vp->val.str_val.len*sizeof(char));
+				if(!_drp->fields[_idx].val.str_val.s)
+				{
+					_drp->fields[_idx].nul = 1;
+					return -1;
+				}
+				memcpy(_drp->fields[_idx].val.str_val.s, _vp->val.str_val.s,
+					_vp->val.str_val.len);
+				_drp->fields[_idx].val.str_val.len = _vp->val.str_val.len;
+			break;
+			
+			case DB_STRING:
+				_drp->fields[_idx].type = DB_STR;
+				_drp->fields[_idx].val.str_val.len=strlen(_vp->val.string_val);
+				
+				_drp->fields[_idx].val.str_val.s = 
+					(char*)shm_malloc(_drp->fields[_idx].val.str_val.len
+									  *sizeof(char));
+				if(!_drp->fields[_idx].val.str_val.s)
+				{
+					_drp->fields[_idx].nul = 1;
+					return -1;
+				}
+				memcpy(_drp->fields[_idx].val.str_val.s, _vp->val.string_val,
+					_drp->fields[_idx].val.str_val.len);
+			break;
+
+			case DB_FLOAT:
+				_drp->fields[_idx].type = DB_FLOAT;
+				_drp->fields[_idx].val.float_val = _vp->val.float_val;
+			break;			
+
+			case DB_DOUBLE:
+				_drp->fields[_idx].type = DB_DOUBLE;
+				_drp->fields[_idx].val.double_val = _vp->val.double_val;
+			break;
+			
+			case DB_INT:
+				_drp->fields[_idx].type = DB_INT;
+				_drp->fields[_idx].val.int_val = _vp->val.int_val;
+			break;
+			
+			case DB_DATETIME:
+				_drp->fields[_idx].type = DB_INT;
+				_drp->fields[_idx].val.int_val = (int)_vp->val.time_val;
+			break;
+			
+			default:
+				_drp->fields[_idx].nul = 1;
+				return -1;
+		}
+	}
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_row_update_val(dbt_row_p _drp, dbt_val_p _vp, int _t, int _idx)
+{
+	if(!_drp || !_vp || _idx<0)
+		return -1;
+	
+	_drp->fields[_idx].nul = _vp->nul;
+	_drp->fields[_idx].type = _t;
+	
+	if(!_vp->nul)
+	{
+		switch(_t)
+		{
+			case DB_BLOB:
+			case DB_STR:
+				_drp->fields[_idx].type = DB_STR;
+				// free if already exists
+				if(_drp->fields[_idx].val.str_val.s)
+					shm_free(_drp->fields[_idx].val.str_val.s);
+			
+				_drp->fields[_idx].val.str_val.s = 
+					(char*)shm_malloc(_vp->val.str_val.len*sizeof(char));
+				if(!_drp->fields[_idx].val.str_val.s)
+				{
+					_drp->fields[_idx].nul = 1;
+					return -1;
+				}
+				memcpy(_drp->fields[_idx].val.str_val.s, _vp->val.str_val.s,
+					_vp->val.str_val.len);
+				_drp->fields[_idx].val.str_val.len = _vp->val.str_val.len;
+			break;
+			
+			case DB_STRING:
+				_drp->fields[_idx].type = DB_STR;
+				/* free if already exists */
+				if(_drp->fields[_idx].val.str_val.s)
+					shm_free(_drp->fields[_idx].val.str_val.s);
+
+				_drp->fields[_idx].type = DB_STR;
+				_drp->fields[_idx].val.str_val.len=strlen(_vp->val.string_val);
+				
+				_drp->fields[_idx].val.str_val.s = 
+					(char*)shm_malloc(_drp->fields[_idx].val.str_val.len
+									  *sizeof(char));
+				if(!_drp->fields[_idx].val.str_val.s)
+				{
+					_drp->fields[_idx].nul = 1;
+					return -1;
+				}
+				memcpy(_drp->fields[_idx].val.str_val.s, _vp->val.string_val,
+					_drp->fields[_idx].val.str_val.len);
+			break;
+			
+			case DB_FLOAT:
+				_drp->fields[_idx].type = DB_FLOAT;
+				_drp->fields[_idx].val.float_val = _vp->val.float_val;
+			break;
+
+			case DB_DOUBLE:
+				_drp->fields[_idx].type = DB_DOUBLE;
+				_drp->fields[_idx].val.double_val = _vp->val.double_val;
+			break;
+			
+			case DB_INT:
+				_drp->fields[_idx].type = DB_INT;
+				_drp->fields[_idx].val.int_val = _vp->val.int_val;
+			break;
+			
+			case DB_DATETIME:
+				_drp->fields[_idx].type = DB_INT;
+				_drp->fields[_idx].val.int_val = (int)_vp->val.time_val;
+			break;
+			
+			default:
+				LOG(L_ERR,"ERROR:dbtext: unsupported type %d in update\n",_t);
+				_drp->fields[_idx].nul = 1;
+				return -1;
+		}
+	}
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_table_check_row(dbt_table_p _dtp, dbt_row_p _drp)
+{
+	int i;
+	if(!_dtp || _dtp->nrcols <= 0 || !_drp)
+		return -1;
+	
+	for(i=0; i<_dtp->nrcols; i++)
+	{
+		if(!_drp->fields[i].nul &&(_dtp->colv[i]->type!=_drp->fields[i].type))
+		{
+			DBG("DBT:dbt_table_check_row: incompatible types - field %d\n",i);
+			return -1;
+		}
+		if(_dtp->colv[i]->flag & DBT_FLAG_NULL)
+			continue;
+		
+		if(!_drp->fields[i].nul)
+			continue;
+
+		if(_dtp->colv[i]->type==DB_INT
+			&& (_dtp->colv[i]->flag & DBT_FLAG_AUTO)
+			&& i==_dtp->auto_col)
+		{
+			_drp->fields[i].nul = 0;
+			_drp->fields[i].val.int_val = ++_dtp->auto_val;
+			continue;
+		}
+
+		DBG("DBT:dbt_table_check_row: NULL value not allowed - field %d\n",i);
+		return -1;
+	}
+	
+	return 0;
+}
+
+/**
+ *
+ */
+int dbt_table_update_flags(dbt_table_p _dtp, int _f, int _o, int _m)
+{
+	if(!_dtp)
+		return -1;
+	
+	if(_o == DBT_FL_SET)
+		_dtp->flag |= _f;
+	else if(_o == DBT_FL_UNSET)
+			_dtp->flag &= ~_f;
+	
+	if(_m)
+		_dtp->mark = (int)time(NULL);
+	
+	return 0;
+}
+

+ 62 - 0
dbtext/dbt_util.c

@@ -0,0 +1,62 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-01-30 created by Daniel
+ * 
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "dbt_util.h"
+
+/**
+ *
+ */
+int dbt_is_database(str *_s)
+{
+	DIR *dirp = NULL;
+	char buf[512];
+	
+	if(!_s || !_s->s || _s->len <= 0 || _s->len > 510)
+		return 0;
+	strncpy(buf, _s->s, _s->len);
+	buf[_s->len] = 0;
+	dirp = opendir(buf);
+	if(!dirp)
+		return 0;
+	closedir(dirp);
+
+	return 1;
+}
+

+ 46 - 0
dbtext/dbt_util.h

@@ -0,0 +1,46 @@
+/*
+ * $Id$
+ *
+ * DBText library
+ *
+ * Copyright (C) 2001-2003 FhG Fokus
+ *
+ * This file is part of ser, a free SIP server.
+ *
+ * ser is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version
+ *
+ * For a license to use the ser software under conditions
+ * other than those described here, or to purchase support for this
+ * software, please contact iptel.org by e-mail at the following addresses:
+ *    [email protected]
+ *
+ * ser is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License 
+ * along with this program; if not, write to the Free Software 
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/**
+ * DBText library
+ *   
+ * 2003-01-30 created by Daniel
+ * 
+ */
+
+
+#ifndef _DBT_UTIL_H_
+#define _DBT_UTIL_H_
+
+#include "../../str.h"
+
+int dbt_is_database(str *);
+
+#endif
+

部分文件因文件數量過多而無法顯示