2
0
Эх сурвалжийг харах

Merge ser modules into sip-router master branch

(merge commit 'origin/ser_modules')

* commit 'origin/ser_modules': (2777 commits)
  modules: added missing parse_to.h include
  xlog: LOG_() api update
  usrloc: fixed name conflict
  lcr: updated to the route_mode changes
  modules: tm register_tcmb api update
  ser modules ported to sip-router
  tls: ser_cert.sh portability fixes
  METHOD_OTHER is replaced with METHOD_OPTIONS, because the OPTIONS request
  cfg_size macro renamed to cfg_sizeof to avoid a conflict with one of the
  * logging API updated (see doc/logging-api.txt for details)
  When building the route set of ACKs for local UACs, only the reply is now
  Fixed compiler issues warnings over:
  Some errors are marked as DB API (framework) failures instead of MYSQL API
  The header file ds_rpc.h statically defines the doc string for some RPC
  "Route :" prefix (and separator) is used some more time across the
  permissions: fix build without -DUSE_IPV6 errors
  nathelper: added support for SDP rtcp attribute (RFC 3605)
  prefix_route: include atomic_ops.h where atomic_t is used
  Fix warnings on LP64 plarforms out there (all 64-bit amd64/emt64 unixes
  Add comma between function names and fix typo.
  ...
Andrei Pelinescu-Onciul 16 жил өмнө
parent
commit
3cc16e2873
100 өөрчлөгдсөн 25998 нэмэгдсэн , 0 устгасан
  1. 2 0
      modules_s/acc_db/.cvsignore
  2. 18 0
      modules_s/acc_db/Makefile
  3. 1040 0
      modules_s/acc_db/acc_db.c
  4. 1171 0
      modules_s/acc_db/acc_db.xml
  5. 16 0
      modules_s/acc_radius/Makefile
  6. 1093 0
      modules_s/acc_radius/acc_radius.c
  7. 2 0
      modules_s/acc_syslog/.cvsignore
  8. 16 0
      modules_s/acc_syslog/Makefile
  9. 406 0
      modules_s/acc_syslog/README
  10. 943 0
      modules_s/acc_syslog/acc_syslog.c
  11. 762 0
      modules_s/acc_syslog/acc_syslog.xml
  12. 203 0
      modules_s/acc_syslog/attrs.h
  13. 29 0
      modules_s/acc_syslog/doc/Makefile
  14. 210 0
      modules_s/acc_syslog/doc/acc_syslog.xml
  15. 126 0
      modules_s/acc_syslog/doc/functions.xml
  16. 359 0
      modules_s/acc_syslog/doc/params.xml
  17. 19 0
      modules_s/auth/Makefile
  18. 586 0
      modules_s/auth/README
  19. 110 0
      modules_s/auth/aaa_avps.h
  20. 195 0
      modules_s/auth/api.c
  21. 95 0
      modules_s/auth/api.h
  22. 685 0
      modules_s/auth/auth.xml
  23. 339 0
      modules_s/auth/auth_mod.c
  24. 59 0
      modules_s/auth/auth_mod.h
  25. 229 0
      modules_s/auth/challenge.c
  26. 51 0
      modules_s/auth/challenge.h
  27. 29 0
      modules_s/auth/doc/Makefile
  28. 75 0
      modules_s/auth/doc/auth.xml
  29. 225 0
      modules_s/auth/doc/functions.xml
  30. 595 0
      modules_s/auth/doc/params.xml
  31. 251 0
      modules_s/auth/nc.c
  32. 79 0
      modules_s/auth/nc.h
  33. 104 0
      modules_s/auth/nid.c
  34. 93 0
      modules_s/auth/nid.h
  35. 455 0
      modules_s/auth/nonce.c
  36. 232 0
      modules_s/auth/nonce.h
  37. 239 0
      modules_s/auth/ot_nonce.c
  38. 84 0
      modules_s/auth/ot_nonce.h
  39. 149 0
      modules_s/auth/rfc2617.c
  40. 102 0
      modules_s/auth/rfc2617.h
  41. 9 0
      modules_s/auth/todo.txt
  42. 1 0
      modules_s/auth_db/.cvsignore
  43. 17 0
      modules_s/auth_db/Makefile
  44. 279 0
      modules_s/auth_db/README
  45. 121 0
      modules_s/auth_db/aaa_avps.h
  46. 541 0
      modules_s/auth_db/auth_db.xml
  47. 374 0
      modules_s/auth_db/authdb_mod.c
  48. 82 0
      modules_s/auth_db/authdb_mod.h
  49. 470 0
      modules_s/auth_db/authorize.c
  50. 52 0
      modules_s/auth_db/authorize.h
  51. 29 0
      modules_s/auth_db/doc/Makefile
  52. 79 0
      modules_s/auth_db/doc/auth_db.xml
  53. 112 0
      modules_s/auth_db/doc/functions.xml
  54. 210 0
      modules_s/auth_db/doc/params.xml
  55. 20 0
      modules_s/auth_identity/Makefile
  56. 409 0
      modules_s/auth_identity/auth_crypt.c
  57. 119 0
      modules_s/auth_identity/auth_dynstr.c
  58. 739 0
      modules_s/auth_identity/auth_hdrs.c
  59. 109 0
      modules_s/auth_identity/auth_http.c
  60. 831 0
      modules_s/auth_identity/auth_identity.c
  61. 259 0
      modules_s/auth_identity/auth_identity.h
  62. 629 0
      modules_s/auth_identity/auth_identity.xml
  63. 575 0
      modules_s/auth_identity/auth_tables.c
  64. 29 0
      modules_s/auth_identity/doc/Makefile
  65. 558 0
      modules_s/auth_identity/doc/auth_identity.xml
  66. 17 0
      modules_s/auth_radius/Makefile
  67. 189 0
      modules_s/auth_radius/README
  68. 302 0
      modules_s/auth_radius/authorize.c
  69. 52 0
      modules_s/auth_radius/authorize.h
  70. 204 0
      modules_s/auth_radius/authrad_mod.c
  71. 50 0
      modules_s/auth_radius/authrad_mod.h
  72. 29 0
      modules_s/auth_radius/doc/Makefile
  73. 80 0
      modules_s/auth_radius/doc/auth_radius.xml
  74. 110 0
      modules_s/auth_radius/doc/functions.xml
  75. 76 0
      modules_s/auth_radius/doc/params.xml
  76. 283 0
      modules_s/auth_radius/sterman.c
  77. 56 0
      modules_s/auth_radius/sterman.h
  78. 15 0
      modules_s/avp/Makefile
  79. 432 0
      modules_s/avp/README
  80. 1658 0
      modules_s/avp/avp.c
  81. 543 0
      modules_s/avp/avp.xml
  82. 29 0
      modules_s/avp/doc/Makefile
  83. 59 0
      modules_s/avp/doc/avp.xml
  84. 644 0
      modules_s/avp/doc/functions.xml
  85. 37 0
      modules_s/avp/doc/params.xml
  86. 18 0
      modules_s/avp_db/Makefile
  87. 207 0
      modules_s/avp_db/README
  88. 400 0
      modules_s/avp_db/avp_db.c
  89. 31 0
      modules_s/avp_db/avp_db.h
  90. 750 0
      modules_s/avp_db/avp_db.xml
  91. 29 0
      modules_s/avp_db/doc/Makefile
  92. 71 0
      modules_s/avp_db/doc/avp_db.xml
  93. 328 0
      modules_s/avp_db/doc/avp_dialogs.cfg
  94. 25 0
      modules_s/avp_db/doc/fifo.xml
  95. 217 0
      modules_s/avp_db/doc/functions.xml
  96. 232 0
      modules_s/avp_db/doc/params.xml
  97. 540 0
      modules_s/avp_db/extra_attrs.c
  98. 23 0
      modules_s/avp_db/extra_attrs.h
  99. 18 0
      modules_s/avp_radius/Makefile
  100. 115 0
      modules_s/avp_radius/README

+ 2 - 0
modules_s/acc_db/.cvsignore

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

+ 18 - 0
modules_s/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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
modules_s/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
modules_s/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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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**)&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
modules_s/acc_syslog/.cvsignore

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

+ 16 - 0
modules_s/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

+ 406 - 0
modules_s/acc_syslog/README

@@ -0,0 +1,406 @@
+1. Acc Module
+
+Jiri Kuthan
+
+   iptel.org
+   <[email protected]>
+
+   Copyright © 2002, 2003 FhG FOKUS
+   Revision History
+   Revision $Revision$ $Date$
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Dependencies
+   1.3. Parameters
+
+        1.3.1. secret (string)
+        1.3.2. log_level (integer)
+        1.3.3. log_fmt (string)
+        1.3.4. early_media (integer)
+        1.3.5. failed_transactions (integer)
+        1.3.6. log_flag (integer)
+        1.3.7. log_missed_flag (integer)
+        1.3.8. report_ack (integer)
+        1.3.9. report_cancels (integer)
+        1.3.10. radius_config (string)
+        1.3.11. service_type (integer)
+        1.3.12. radius_flag (integer)
+        1.3.13. radius_missed_flag (integer)
+        1.3.14. db_url (string)
+        1.3.15. db_flag (integer)
+        1.3.16. db_missed_flag (integer)
+        1.3.17. diameter_flag (integer)
+        1.3.18. diameter_missed_flag (integer)
+        1.3.19. diameter_client_host (string)
+        1.3.20. 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. In particular,
+   accounting secret must match that one configured in server and proper
+   dictionary is used (one is available in ). 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
+
+   Revision History
+   Revision $Revision$ $Date$
+
+1.3.1. secret (string)
+
+   The secret string used to generate nonce. Inclusion of this string in
+   nonce ensures that only the proxy server that knows the secret string
+   will be able to generate the nonce and verify it later when received
+   from the user agent.
+
+   Default value is randomly generated string.
+
+   Example 2. Setting secret module parameter
+modparam("auth", "secret", "johndoessecretphrase")
+
+1.3.2. log_level (integer)
+
+   Log level at which accounting messages are issued to syslog.
+
+   Default value is L_NOTICE.
+
+   Example 3. log_level example
+modparam("acc", "log_level", 2)   # Set log_level to 2
+
+1.3.3. 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 4. log_fmt example
+modparam("acc", "log_fmt", "mfs")
+
+1.3.4. early_media (integer)
+
+   Should be early media (183) accounted too ?
+
+   Default value is 0 (no).
+
+   Example 5. early_media example
+modparam("acc", "early_media", 1)
+
+1.3.5. failed_transactions (integer)
+
+   This parameter controls whether failed transactions (with final reply
+   >= 300) should be accounted too.
+
+   Default value is 0 (no).
+
+   Example 6. failed_transactions example
+modparam("acc", "failed_transactions", 1)
+
+1.3.6. log_flag (integer)
+
+   Request flag which needs to be set to account a transaction.
+
+   Default value is 1.
+
+   Example 7. log_flag example
+modparam("acc", "log_flag", 2)
+
+1.3.7. log_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls.
+
+   Default value is 2.
+
+   Example 8. log_missed_flag example
+modparam("acc", "log_missed_flag", 3)
+
+1.3.8. 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 9. report_ack example
+modparam("acc", "report_ack", 0)
+
+1.3.9. 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 10. report_cancels example
+modparam("acc", "report_cancels", 1)
+
+1.3.10. 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 11. radius_config example
+modparam("acc", "radius_config", "/etc/radiusclient/radiusclient.conf")
+
+1.3.11. service_type (integer)
+
+   Radius service type used for accounting.
+
+   Default value is 15 (SIP).
+
+   Example 12. service_type example
+modparam("acc", "service_type", 16)
+
+1.3.12. radius_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- RADIUS
+   specific.
+
+   Default value is 1.
+
+   Example 13. radius_flag example
+                modparam("acc", "radius_flag", 2)
+
+1.3.13. radius_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- RADIUS
+   specific.
+
+   Default value is 2.
+
+   Example 14. radius_missed_flag example
+modparam("acc", "radius_missed_flag", 3)
+
+1.3.14. db_url (string)
+
+   SQL address -- database specific.
+
+   Default value is "mysql://ser:heslo@localhost/ser"
+
+   Example 15. db_url example
+modparam("acc", "db_url", "mysql://user:password@localhost/ser")
+
+1.3.15. db_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- database
+   specific.
+
+   Default value is 1.
+
+   Example 16. db_flag example
+modparam("acc", "db_flag", 2)
+
+1.3.16. db_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- database
+   specific.
+
+   Default value is 2.
+
+   Example 17. db_missed_flag example
+modparam("acc", "db_missed_flag", 3)
+
+1.3.17. diameter_flag (integer)
+
+   Request flag which needs to be set to account a transaction -- DIAMETER
+   specific.
+
+   Default value is 1.
+
+   Example 18. diameter_flag example
+modparam("acc", "diameter_flag", 2)
+
+1.3.18. diameter_missed_flag (integer)
+
+   Request flag which needs to be set to account missed calls -- DIAMETER
+   specific.
+
+   Default value is 2.
+
+   Example 19. diameter_missed_flag example
+modparam("acc", "diameter_missed_flag", 3)
+
+1.3.19. diameter_client_host (string)
+
+   Hostname of the machine where the DIAMETER Client is running --
+   DIAMETER specific.
+
+   Default value is "localhost".
+
+   Example 20. diameter_client_host example
+modparam("acc", "diameter_client_host", "iptel.org")
+
+1.3.20. diameter_client_port (int)
+
+   Port number where the Diameter Client is listening -- DIAMETER
+   specific.
+
+   Default value is 3000.
+
+   Example 21. diameter_client_host example
+modparam("acc", "diameter_client_port", 3000)
+
+1.4. Functions
+
+   Revision History
+   Revision $Revision$ $Date$
+
+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 22. 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 23. 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 24. 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 25. acc_diam_request usage
+...
+acc_diam_request("Some comment");
+...

+ 943 - 0
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 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);
+		append_str(buf, atr_arr[i]);
+		append(buf, A_EQ);
+		append_str(buf, *(val_arr[i]))
+	}
+
+	     /* terminating text */
+	append(buf, A_EOL);
+
+	     /* leading text */
+	buf.s = log_msg;
+	buf.len = len;
+	append(buf, ACC);
+	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
modules_s/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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 */

+ 29 - 0
modules_s/acc_syslog/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = acc_syslog
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 210 - 0
modules_s/acc_syslog/doc/acc_syslog.xml

@@ -0,0 +1,210 @@
+<?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>
+
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </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.  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>
+    

+ 126 - 0
modules_s/acc_syslog/doc/functions.xml

@@ -0,0 +1,126 @@
+<?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>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </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>

+ 359 - 0
modules_s/acc_syslog/doc/params.xml

@@ -0,0 +1,359 @@
+<?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>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="secret">
+	<title><varname>secret</varname> (string)</title>
+	<para>
+	    The secret string used to generate nonce. Inclusion of this string
+	    in nonce ensures that only the proxy server that knows the secret
+	    string will be able to generate the nonce and verify it later when
+	    received from the user agent.
+	</para>
+	<para>
+	    Default value is randomly generated string.
+	</para>
+	<example>
+	    <title>Setting secret module parameter</title>
+	    <programlisting>
+modparam("auth", "secret", "johndoessecretphrase")
+	    </programlisting>
+	</example>
+    </section>
+
+    <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>

+ 19 - 0
modules_s/auth/Makefile

@@ -0,0 +1,19 @@
+# $Id$
+#
+# auth example module makefile
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=auth.so
+LIBS=
+# DEFS:
+#       -DUSE_NC       - compile with nounce count support
+#       -DUSE_OT_NONCE - compile with one-time nonces support
+DEFS+=-DUSE_NC -DUSE_OT_NONCE
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 586 - 0
modules_s/auth/README

@@ -0,0 +1,586 @@
+1. Auth Module
+
+Jan Janak
+
+   FhG Fokus
+   <[email protected]>
+
+Juha Heinanen
+
+   Song Networks
+   <[email protected]>
+
+   Copyright © 2002, 2003 FhG FOKUS
+   Revision History
+   Revision $Revision$ $Date$
+     __________________________________________________________________
+
+   1.1. Overview
+   1.2. Dependencies
+   1.3. Parameters
+
+        1.3.1. auth_checks_register, auth_checks_no_dlg, and
+                auth_checks_in_dlg (flags)
+
+        1.3.2. qop (string)
+        1.3.3. nonce_count (boolean)
+        1.3.4. one_time_nonce (boolean)
+        1.3.5. nid_pool_no (integer)
+        1.3.6. nc_array_size (integer)
+        1.3.7. nc_array_order (integer)
+        1.3.8. otn_in_flight_no (integer)
+        1.3.9. otn_in_flight_order (integer)
+        1.3.10. secret (string)
+        1.3.11. nonce_expire (integer)
+        1.3.12. nonce_auth_max_drift (integer)
+        1.3.13. rpid_prefix (string)
+        1.3.14. rpid_suffix (string)
+
+   1.4. Functions
+
+        1.4.1. www_challenge(realm,qop)
+        1.4.2. proxy_challenge(realm, qop)
+        1.4.3. consume_credentials()
+        1.4.4. is_rpid_user_e164()
+        1.4.5. append_rpid_hf()
+        1.4.6. append_rpid_hf(prefix, suffix)
+
+1.1. Overview
+
+   This is a generic module that itself doesn't provide all functions
+   necessary for authentication but provides functions that are needed by
+   all other authentication related modules (so called authentication
+   backends).
+
+   We decided to break the authentication code into several modules
+   because there are now more than one backends (currently database
+   authentication and radius are supported). This allows us to create
+   separate packages so uses can install and load only required
+   functionality. This also allows us to avoid unnecessary dependencies in
+   the binary packages.
+
+1.2. Dependencies
+
+   The module depends on the following modules (in the other words the
+   listed modules must be loaded before this module):
+     * sl. The modules needs sl module to send stateless replies.
+
+1.3. Parameters
+
+   Revision History
+   Revision $Revision$ $Date$
+
+1.3.1.  auth_checks_register, auth_checks_no_dlg, and auth_checks_in_dlg
+(flags)
+
+   These three module parameters control which optional integrity checks
+   will be performed on the SIP message carrying digest response during
+   digest authentication. auth_check_register controls integrity checks to
+   be peformed on REGISTER messages, auth_checks_no_dlg controls which
+   optional integrity checks will be performed on SIP requests that have
+   no To header field or no To tag (in other words the requests either
+   establishing or outside dialogs). auth_checks_in_dlg controls which
+   integrity checks will be performed on SIP requests within dialogs, such
+   as BYE or re-INVITE. The default value for all three parameters is 0
+   (old behaviour, no extra checks). The set of integrity checks that can
+   be performed on REGISTERs is typically different from sets of integrity
+   checks that can be performed for other SIP request types, hence we have
+   three independent module parameters.
+
+   Without the extra checks the nonce will protect only against expired
+   values. Some reply attacks are still possible in the expire "window". A
+   possible workaround is to always force qop authentication and always
+   check the uri from the authorization header, but this would not work if
+   an upstream proxy rewrites the uri and it will also not work with a lot
+   of UA implementations.
+
+   In this case the nonce value will be used only to hold the expire time
+   (see nonce_expire) and an MD5 over it and some secret (the MD5 is used
+   to make sure that nobody tampers with the nonce expire time).
+
+   When the extra checks are enabled, the nonce will include and extra MD5
+   over the selected part/parts of the message (see below) and some other
+   secret. This will be used to check if the selected part of the message
+   is the same when an UA tries to reuse the nonce, thus protecting or
+   severely limiting reply attacks.
+
+   The possible flag values for all three parameters are: 1 for checking
+   if the message uri changed (uses the whole uri), 2 for checking the
+   callid, 4 for the from tag and 8 for the source ip (see nonce.h). For
+   example setting auth_checks_register to 6 would check if the callid or
+   the from tag changed from the REGISTER message for which the original
+   nonce was generated (this would allow nonce reuse only within the same
+   UA and for the expire time). Note that enabling the extra checks will
+   limit nonce caching by UAs, requiring extra challenges and roundtrips,
+   but will provide much better protection.
+
+   When the secret parameter is set and the extra checks are enabled, the
+   first half of the secret will be used for the expire time MD5 and the
+   other half for the extra checks MD5, so make sure you have a long
+   secret (32 chars or longer are recommended).
+
+   Example 1. Setting the auth_checks_register module parameter
+modparam("auth", "auth_checks_register", 2) # callid
+
+1.3.2. qop (string)
+
+   If set, enable qop for challenges: each challenge will include a qop
+   parameter. This is the recommended way, but some older non rfc3261
+   compliant UAs might get confused and might not authenticate properly if
+   qop is enabled.
+
+   Enabling qop together with nonce_count will provide extra-security
+   (protection against replay attacks) while still allowing credentials
+   caching at the UA side and thus not compromising performance.
+
+   The possible values are: "auth", "auth-int" and "" (unset).
+
+   The default value is not-set ("").
+
+   See also: nonce_count.
+
+   Example 2. qop example
+modparam("auth", "qop", "auth")   # set qop=auth
+
+1.3.3. nonce_count (boolean)
+
+   If enabled the received nc value is remembered and checked against the
+   older value (for a successful authentication the received nc must be
+   greater then the previously received one, see rfc2617 for more
+   details). This will provide protection against replay attacks while
+   still allowing credentials caching at the UA side.
+
+   It depends on qop being enabled (if qop is not enabled, the challenges
+   won't include qop and so the UA will probably not include the qop or nc
+   parameters in its response).
+
+   If a response doesn't include qop or nc (for example obsolete UAs that
+   don't support them) the response will be checked according to the other
+   enabled nonce checks, in this order: one_time_nonce and auth_check_*.
+   If a response includes nc only the normal nonce_expire checks and the
+   nonce_count checks will be performed, all the other checks will be
+   ignored.
+
+   The nonce_count checks work by tracking a limited number of nonces. The
+   maximum number of tracked nonces is set using the nc_array_size or
+   nc_array_order parameters. If this number is exceeded, older entries
+   will be overwritten. As long as the maximum rate of challengeable
+   messages per average response time is lower then nc_array_size, the
+   nonce_count checks should work flawlessly. For optimum performance
+   (maximum reuse of cache credentials) nc_array_size divided by
+   nid_pool_no should be lower then the message rate multiplied by the
+   desired nonce_expire.
+
+   The maximum accepted nc value is 255. If nc becomes greater then this,
+   the nonce will be considered stale and the UA will be re-challenged.
+
+   Note: nonce_count should be enabled only in stateful mode (a
+   transaction should be created prior to the authentication check to
+   absorb possible retransmissions and all the replies should be sent
+   statefuly, using t_reply()). If nonce_count and the authentication
+   checks are used in the stateless mode then all retransmissions will be
+   challenged.
+
+   The default value is 0 (off).
+
+   See also: qop, nc_array_size, nc_array_order, nid_pool_no,
+   nonce_expire. one_time_nonce.
+
+   Example 3. nonce_count example
+modparam("auth", "nonce_count", 1) # enable nonce_count support
+modparam("auth", "qop", "auth")    # enable qop=auth
+
+....
+route{
+...
+        # go stateful and catch retransmissions
+        if (!t_newtran())
+                drop; # retransmission
+        if (method=="REGISTER"){
+                if (!www_authenticate("test", "credentials")){
+                        # reply must be sent with t_reply because the
+                        # transaction is already created at this point
+                        # (we are in "stateful" mode)
+                        if ($? == -2){
+                                t_reply("500", "Internal Server Error");
+                        }else if ($? == -3){
+                                t_reply("400", "Bad Request");
+                        }else{
+                                if ($digest_challenge)
+                                        append_to_reply("%$digest_challenge");
+                                t_reply("401", "Unauthorized");
+                        }
+                        drop;
+                }
+                if (!save_noreply("location")) {
+                        t_reply("400", "Invalid REGISTER Request");
+                        drop;
+                }
+                append_to_reply("%$contact");
+                t_reply("$code", "$reason"); # no %, avps are used directly
+                drop;
+        }else{
+                if (!proxy_authenticate("my_realm", "credentials")){
+                        if ($? == -2){
+                                t_reply("500", "Internal Server Error");
+                        }else if ($? == -3){
+                                t_reply("400", "Bad Request");
+                        }else{
+                                if ($digest_challenge)
+                                        append_to_reply("%$digest_challenge");
+                                t_reply("401", "Unauthorized");
+                        }
+                        drop;
+                }
+        }
+...
+
+1.3.4. one_time_nonce (boolean)
+
+   If set to 1 nonce reuse is disabled: each nonce is allowed only once,
+   in the first reponse to a challenge. All the messages will be
+   challenged, even retransmissions. Stateful mode should be used, to
+   catch retransmissions before the authentication checks (using
+   t_newtran() before the authentication checks and sending all the
+   replies with t_reply()).
+
+   one_time_nonce provides enhanced replay protections at the cost of
+   invalidating UA side credentials caching, challenging every message
+   (and thus generating extra messages and extra round-trips) and
+   requiring stateful mode. In general qop and nonce_count should be
+   prefered (if possible) with fallback to auth_checks_*. Due to the
+   disadvantages listed above, one_time_nonce should be used only if the
+   extra checks provided by auth_checks_register, auth_checks_no_dlg and
+   auth_checks_in_dlg are deemed insufficient for a specific setup.
+
+   Compared to nonce_count, one_time_nonce provides the same protection,
+   but at a higher message cost. The only advantages are that it works
+   with user agents that do not support qop and nc and that it uses less
+   memory for the same supported number of maximum in-flight nonces (by a
+   factor of 8). one_time_nonce can be used as fallback from nonce_count,
+   when the UA doesn't support nc (it happens automatically when both of
+   them are enabled).
+
+   Like nonce_count, one_time_nonce works by tracking a limited number of
+   nonces. The maximum number of tracked nonces is set using the
+   otn_in_flight_no or otn_in_flight_order parameters. If this number is
+   exceeded, older entries will be overwritten. As long as the maximum
+   rate of challengeable messages per average response time is lower then
+   otn_in_flight_no, the one_time_nonce checks should work flawlessly.
+
+   The default value is 0 (off).
+
+   See also: otn_in_flight_no, otn_in_flight_order, nid_pool_no and
+   nonce_count.
+
+   Example 4. one_time_nonce example
+modparam("auth", "one_time_nonce", 1)
+# Note: stateful mode should be used, see the nonce_count example
+
+1.3.5. nid_pool_no (integer)
+
+   Controls the number of partitions for the nonce_count and
+   one_time_nonce arrays (it's common to both of them to reduce the nonce
+   size).
+
+   Instead of using single arrays for keeping nonce state, these arrays
+   can be divided into more partitions. Each ser process is assigned to
+   one of these partitions, allowing for higher concurrency on multi-CPU
+   machines. Besides increasing performance, increasing nid_pool_no has
+   also a negative effect: it could decrease the maximum supported
+   in-flight nonces in certain conditions. In the worst case, when only
+   one ser process receives most of the traffic (e.g. very busy tcp
+   connection between two proxies), the in-flight nonces could be limited
+   to the array size (nc_array_size for nonce_count or otn_in_flight_no
+   for one_time_nonce) divided by the partitions number (nid_pool_no).
+   However for normal traffic, when the process receiving a message is
+   either random or chosen in a round-robin fashion the maximum in-flight
+   nonces number will be very little influenced by nid_pool_no (the
+   messages will be close to equally distributed to processes using
+   different partitions).
+
+   nid_pool_no value should be one of: 1, 2, 4, 8, 16, 32 or 64 (the
+   maximum value is 64 and all values should be of the form 2^k or else
+   they will be rounded down to 2^k).
+
+   The default value is 1.
+
+   See also: nonce_count, one_time_nonce, nc_array_size and
+   otn_in_flight_no.
+
+   Example 5. nid_pool_no example
+modparam("auth", "nid_pool_no", 4)
+
+1.3.6. nc_array_size (integer)
+
+   Maximum number of in-flight nonces for nonce_count. It represents the
+   maximum nonces for which state will be kept. When this number is
+   exceeded, state for the older nonces will be discarded to make space
+   for new ones (see nonce_count for more details).
+
+   The value should be of the form 2^k. If it's not it will be rounded
+   down to 2^k (for example a value of 1000000 will be rounded down to
+   2^19=524288). nc_array_order can be used to directly specify the power
+   of 2 (e.g. nc_array_order set to 20 is equivalent to nc_array_size set
+   to 1048576).
+
+   The memory used to keep the nonce state will be nc_array_size in bytes.
+
+   The default value is 1048576 (1M in-flight nonces, using 1Mb memory).
+
+   See also: nonce_count and nid_pool_no.
+
+   Example 6. nc_array_size example
+modparam("auth", "nc_array_size", 4194304)   # 4Mb
+
+1.3.7. nc_array_order (integer)
+
+   Equivalent to nc_array_size, but instead of directly specifying the
+   size, its value is the power at which 2 should be raised
+   (log2(nc_array_size)).
+
+   nc_array_size = 2^nc_array_order. For more details see nc_array_size.
+
+   The default value is 20 (1M in-flight nonces, using 1Mb memory).
+
+   See also: nonce_count, nc_array_size and nid_pool_no.
+
+   Example 7. nc_array_order example
+modparam("auth", "nc_array_order", 22)   # 4Mb
+
+1.3.8. otn_in_flight_no (integer)
+
+   Maximum number of in-flight nonces for one_time_nonce. It represents
+   the maximum number of nonces remembered for the one-time-nonce check.
+   When this number is exceeded, information about older nonces will be
+   discarded and overwritten with information about the new generated ones
+   (see one_time_nonce for more details).
+
+   The value should be of the form 2^k. If it's not it will be rounded
+   down to 2^k (for example a value of 1000000 will be rounded down to
+   2^19=524288). otn_in_flight_no can be used to directly specify the
+   power of 2 (e.g. otn_in_flight_order set to 19 is equivalent to
+   otn_in_fligh_number set to 524288).
+
+   The memory used to keep the nonce information will be the
+   otn_in_flight_no divided by 8 (only 1 bit of state is kept per nonce).
+
+   The default value is 1048576 (1M in-flight nonces, using 128Kb memory).
+
+   See also: one_time_nonce and nid_pool_no.
+
+   Example 8. otn_in_flight_no example
+modparam("auth", "otn_in_flight_no", 8388608)   # 8 Mb (1Mb memory)
+
+1.3.9. otn_in_flight_order (integer)
+
+   Equivalent to otn_in_flight_no, but instead of directly specifying the
+   size, its value is the power at which 2 should be raised
+   (log2(otn_in_flight_no)).
+
+   otn_in_flight_no = 2^otn_in_flight_order. For more details see
+   otn_in_flight_order.
+
+   The default value is 20 (1M in-flight nonces, using 128Kb memory).
+
+   See also: one_time_nonce, otn_in_flight_no and nid_pool_no.
+
+   Example 9. otn_in_flight_order example
+modparam("auth", "otn_in_flight_order", 23)   # 8 Mb (1Mb memory)
+
+1.3.10. secret (string)
+
+   Default value is randomly generated string.
+
+   Example 10. Setting secret module parameter
+modparam("auth", "secret", "johndoessecretphrase")
+
+1.3.11. nonce_expire (integer)
+
+   Nonces have limited lifetime. After a given period of time nonces will
+   be considered invalid. This is to protect replay attacks. Credentials
+   containing a stale nonce will be not authorized, but the user agent
+   will be challenged again. This time the challenge will contain stale
+   parameter which will indicate to the client that it doesn't have to
+   disturb user by asking for username and password, it can recalculate
+   credentials using existing username and password.
+
+   The value is in seconds and default value is 300 seconds.
+
+   Example 11. nonce_expire example
+modparam("auth", "nonce_expire", 600)   # Set nonce_expire to 600s
+
+1.3.12. nonce_auth_max_drift (integer)
+
+   Maximum difference in seconds between a nonce creation time and the
+   current time, if the nonce creation time appears to be in the future.
+
+   In some cases, like shortly after a system time backward adjustment or
+   when the current proxy is part of a cluster which is not
+   time-synchronized, it's possible to receive a nonce with creation time
+   in the future. In this case if the difference is greater then
+   nonce_auth_max_drift seconds, consider the nonce stale and re-challenge
+   (otherwise after a dramatic time change backwards, it might happen that
+   some previously generated nonces will be valid for too much time).
+
+   The default value is 3 seconds
+
+   See also: nonce_expire.
+
+   Example 12. nonce_auth_max_drift example
+modparam("auth", "nonce_auth_max_drift", 1)   # set max drift to 1 s
+
+1.3.13. rpid_prefix (string)
+
+   Prefix to be added to Remote-Party-ID header field just before the URI
+   returned from either radius or database.
+
+   Default value is "" (empty string).
+
+   Example 13. rpid_prefix
+modparam("auth", "rpid_prefix", "Whatever <")
+
+1.3.14. rpid_suffix (string)
+
+   Suffix to be added to Remote-Party-ID header field after the URI
+   returned from either radius or database.
+
+   Default value is ";party=calling;id-type=subscriber;screen=yes".
+
+   Example 14. rpid_suffix
+modparam("auth", "rpid_suffix", "@1.2.3.4>")
+
+1.4. Functions
+
+   Revision History
+   Revision $Revision$ $Date$
+
+1.4.1. www_challenge(realm,qop)
+
+   The function challenges a user agent. It will generate a WWW-Authorize
+   header field containing a digest challenge, it will put the header
+   field into a response generated from the request the server is
+   processing and send the reply. Upon reception of such a reply the user
+   agent should compute credentials and retry the request. For more
+   information regarding digest authentication see RFC2617.
+
+   Meaning of the parameters 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.
+     * qop - Value of this parameter can be either "1" or "0". When set to
+       1 then the server will put qop parameter in the challenge. When set
+       to 0 then the server will not put qop parameter in the challenge.
+       It is strongly recommended to use qop parameter, however there are
+       still some user agents that cannot handle qop parameter properly so
+       we made this optional. On the other hand there are still some user
+       agents that cannot handle request without qop parameter too.
+
+   Example 15. www_challenge usage
+...
+if (www_authorize("iptel.org", "subscriber")) {
+    www_challenge("iptel.org", "1");
+};
+...
+
+1.4.2. proxy_challenge(realm, qop)
+
+   The function challenges a user agent. It will generate a
+   Proxy-Authorize header field containing a digest challenge, it will put
+   the header field into a response generated from the request the server
+   is processing and send the reply. Upon reception of such a reply the
+   user agent should compute credentials and retry the request. For more
+   information regarding digest authentication see RFC2617.
+
+   Meaning of the parameters 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.
+     * qop - Value of this parameter can be either "1" or "0". When set to
+       1 then the server will put qop parameter in the challenge. When set
+       to 0 then the server will not put qop parameter in the challenge.
+       It is strongly recommended to use qop parameter, however there are
+       still some user agents that cannot handle qop parameter properly so
+       we made this optional. On the other hand there are still some user
+       agents that cannot handle request without qop parameter too.
+
+   Example 16. proxy_challenge usage
+...
+if (!proxy_authorize("", "subscriber)) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+
+1.4.3. consume_credentials()
+
+   This function removes previously authorized credentials from the
+   message being processed by the server. That means that the downstream
+   message will not contain credentials there were used by this server.
+   This ensures that the proxy will not reveal information about
+   credentials used to downstream elements and also the message will be a
+   little bit shorter. The function must be called after www_authorize or
+   proxy_authorize.
+
+   Example 17. consume_credentials example
+...
+if (www_authorize("", "subscriber)) {
+    consume_credentials();
+};
+...
+
+1.4.4. is_rpid_user_e164()
+
+   The function checks if the SIP URI received from the database or radius
+   server and will potentially be used in Remote-Party-ID header field
+   contains an E164 number (+ followed by up to 15 decimal digits) in its
+   user part. Check fails, if no such SIP URI exists (i.e. radius server
+   or database didn't provide this information).
+
+   Example 18. is_rpid_user_e164 usage
+...
+if (is_rpid_user_e164()) {
+    # do something here
+};
+...
+
+1.4.5. append_rpid_hf()
+
+   Appends to the message a Remote-Party-ID header that contains header
+   'Remote-Party-ID: ' followed by the saved value of the SIP URI received
+   from the database or radius server followed by the value of module
+   parameter radius_rpid_suffix. The function does nothing if no saved SIP
+   URI exists.
+
+   Example 19. append_rpid_hf usage
+...
+append_rpid_hf();  # Append Remote-Party-ID header field
+...
+
+1.4.6. append_rpid_hf(prefix, suffix)
+
+   This function is the same as the function described in Section 1.4.5,
+   "append_rpid_hf()". The only difference is that it accepts two
+   parameters, prefix and suffix to be added to Remote-Party-ID header
+   field. This function ignores rpid_prefix and rpid_suffix parameters,
+   instead of that allows to set them for every call.
+
+   Meaning of the parameters is as follows:
+     * prefix - Prefix of the Remote-Party-ID URI. The string will be
+       added at the begining of body of the header field, just before the
+       URI.
+     * suffix - Suffix of the Remote-Party-ID header field. The string
+       will be appended at the end of the header field. It can be used to
+       set various URI parameters, for example.
+
+   Example 20. append_rpid_hf(prefix, suffix) usage
+...
+append_rpid_hf("", ";party=calling;id-type=subscriber;screen=yes");  # Append Re
+mote-Party-ID header field
+...

+ 110 - 0
modules_s/auth/aaa_avps.h

@@ -0,0 +1,110 @@
+/*
+ * $Id$
+ *
+ * Common functions for Digest Authentication and Accounting Modules
+ *
+ * Copyright (C) 2001-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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _SER_AAA_AVPS_H_
+#define _SER_AAA_AVPS_H_
+
+#include "../../mem/mem.h"
+#include "../../parser/parser_f.h"
+#include "../../dprint.h"
+
+#include <string.h>
+
+/*
+ * Parse list of tokens separated by some char and put each token
+ * into result array. Caller frees result array!
+ */
+static inline int
+parse_token_list(char *p, char *pend, char separator, str **result)
+{
+	int i;
+	
+	i = 0;
+	*result = NULL;
+	while ((pend - p) > 0) {
+		*result = pkg_realloc(*result, sizeof(**result) * (i + 1));
+		if (*result == NULL)
+			return -1;
+		(*result)[i].s = p;
+		p = eat_token2_end(p, pend, separator) + 1;
+		(*result)[i].len = p - (*result)[i].s - 1;
+		i++;
+	}
+	return i;
+}
+
+
+/*
+ * Parse the list of AVP names separated by '|' into an array
+ * of names, each element of the array is str string
+ */
+static int
+aaa_avps_init(str *avp_list, str **parsed_avps, int *avps_n)
+{
+	int errcode, i;
+	char *cp;
+
+	if (!avp_list->s || !avp_list->len) {
+		     /* AVPs disabled, nothing to do */
+		*avps_n = 0;
+		return 1;
+	}
+
+	cp = pkg_malloc(avp_list->len + 1);
+	if (cp == NULL) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't allocate memory\n");
+		errcode = -1;
+		goto bad;
+	}
+	memcpy(cp, avp_list->s, avp_list->len);
+	*avps_n = parse_token_list(cp, cp + avp_list->len, '|', parsed_avps);
+	if (*avps_n == -1) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't parse avps_column_int "
+		    "parameter\n");
+		errcode = -2;
+		pkg_free(cp);
+		goto bad;
+	}
+
+	for (i = 0; i < *avps_n; i++) {
+		(*parsed_avps)[i].s[(*parsed_avps)[i].len] = '\0';
+	}
+
+	return 0;
+bad:
+	if (*parsed_avps != NULL) {
+		pkg_free((*parsed_avps)[0].s);
+		pkg_free(*parsed_avps);
+	}
+
+	return errcode;
+}
+
+#endif

+ 195 - 0
modules_s/auth/api.c

@@ -0,0 +1,195 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication Module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ *  ...
+ * 2008-07-01 set c->stale in auth_check_hdr_md5 (andrei)
+ */
+
+#include <string.h>
+#include "api.h"
+#include "../../dprint.h"
+#include "../../parser/digest/digest.h"
+#include "../../sr_module.h"
+#include "../../ut.h"
+#include "auth_mod.h"
+#include "nonce.h"
+
+static int auth_check_hdr_md5(struct sip_msg* msg, auth_body_t* auth_body, auth_result_t* auth_res);
+
+/*
+ * Purpose of this function is to find credentials with given realm,
+ * do sanity check, validate credential correctness and determine if
+ * we should really authenticate (there must be no authentication for
+ * ACK and CANCEL
+ * @param hdr output param where the Authorize headerfield will be returned.
+ * @param check_hdr  pointer to the function checking Authorization header field
+ */
+auth_result_t pre_auth(struct sip_msg* msg, str* realm, hdr_types_t hftype,
+						struct hdr_field**  hdr, check_auth_hdr_t check_auth_hdr)
+{
+	int ret;
+	auth_body_t* c;
+	check_auth_hdr_t check_hf;
+	auth_result_t    auth_rv;
+
+	static str prack = STR_STATIC_INIT("PRACK");
+
+	     /* ACK and CANCEL must be always authenticated, there is
+	      * no way how to challenge ACK and CANCEL cannot be
+	      * challenged because it must have the same CSeq as
+	      * the request to be canceled
+	      */
+
+	if ((msg->REQ_METHOD == METHOD_ACK) ||  (msg->REQ_METHOD == METHOD_CANCEL)) return AUTHENTICATED;
+	     /* PRACK is also not authenticated */
+	if ((msg->REQ_METHOD == METHOD_OTHER)) {
+		if (msg->first_line.u.request.method.len == prack.len &&
+		    !memcmp(msg->first_line.u.request.method.s, prack.s, prack.len))
+			return AUTHENTICATED;
+	}
+
+	     /* Try to find credentials with corresponding realm
+	      * in the message, parse them and return pointer to
+	      * parsed structure
+	      */
+	ret = find_credentials(msg, realm, hftype, hdr);
+	if (ret < 0) {
+		LOG(L_ERR, "auth:pre_auth: Error while looking for credentials\n");
+		return ERROR;
+	} else if (ret > 0) {
+		DBG("auth:pre_auth: Credentials with realm '%.*s' not found\n", realm->len, ZSW(realm->s));
+		return NOT_AUTHENTICATED;
+	}
+
+	     /* Pointer to the parsed credentials */
+	c = (auth_body_t*)((*hdr)->parsed);
+
+	    /* digest headers are in c->digest */
+	DBG("auth: digest-algo: %.*s parsed value: %d\n", c->digest.alg.alg_str.len, c->digest.alg.alg_str.s, c->digest.alg.alg_parsed);
+
+	    /* check authorization header field's validity */
+	if (check_auth_hdr == NULL) {
+		check_hf = auth_check_hdr_md5;
+	}
+	else {	/* use check function of external authentication module */
+		check_hf = check_auth_hdr;
+	}
+	/* use the right function */
+	if (!check_hf(msg, c, &auth_rv)) {
+		return auth_rv;
+	}
+	
+	return DO_AUTHENTICATION;
+}
+
+/**
+ * TODO move it to rfc2617.c 
+ * 
+ * @param auth_res return value of authentication. Maybe the it will be not affected.
+ * @result if authentication should continue (1) or not (0)
+ * 
+ */
+static int auth_check_hdr_md5(struct sip_msg* msg, auth_body_t* auth, auth_result_t* auth_res)
+{
+	int ret;
+	
+	    /* Check credentials correctness here */
+	if (check_dig_cred(&auth->digest) != E_DIG_OK) {
+		LOG(L_ERR, "auth:pre_auth: Credentials are not filled properly\n");
+		*auth_res = BAD_CREDENTIALS;
+		return 0;
+	}
+
+	ret = check_nonce(auth, &secret1, &secret2, msg);
+	if (ret!=0){
+		if (ret==3 || ret==4){
+			/* failed auth_extra_checks or stale */
+			auth->stale=1; /* we mark the nonce as stale 
+			 				(hack that makes our life much easier) */
+		} else {
+			DBG("auth:pre_auth: Invalid nonce value received\n");
+			*auth_res = NOT_AUTHENTICATED;
+			return 0;
+		}
+	}
+	return 1;
+}
+
+/*
+ * Purpose of this function is to do post authentication steps like
+ * marking authorized credentials and so on.
+ */
+auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr)
+{
+	int res = AUTHENTICATED;
+	auth_body_t* c;
+
+	c = (auth_body_t*)((hdr)->parsed);
+
+	if (c->stale ) {
+		if ((msg->REQ_METHOD == METHOD_ACK) || 
+		    (msg->REQ_METHOD == METHOD_CANCEL)) {
+			     /* Method is ACK or CANCEL, we must accept stale
+			      * nonces because there is no way how to challenge
+			      * with new nonce (ACK has no response associated 
+			      * and CANCEL must have the same CSeq as the request 
+			      * to be canceled)
+			      */
+		} else {
+			c->stale = 1;
+			res = NOT_AUTHENTICATED;
+		}
+	}
+
+	if (mark_authorized_cred(msg, hdr) < 0) {
+		LOG(L_ERR, "auth:post_auth: Error while marking parsed credentials\n");
+		res = ERROR;
+	}
+
+	return res;
+}
+
+
+int bind_auth(auth_api_t* api)
+{
+	if (!api) {
+		LOG(L_ERR, "bind_auth: Invalid parameter value\n");
+		return -1;
+	}
+
+	api->pre_auth = pre_auth;
+	api->post_auth = post_auth;
+	api->build_challenge = build_challenge_hf;
+	api->qop = &qop;
+	api->calc_HA1 = calc_HA1;
+	api->calc_response = calc_response;
+	return 0;
+}

+ 95 - 0
modules_s/auth/api.h

@@ -0,0 +1,95 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication Module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef API_H
+#define API_H
+
+
+#include "../../parser/msg_parser.h"
+#include "../../parser/digest/digest.h"
+#include "../../usr_avp.h"
+#include "../../parser/hf.h"
+#include "../../str.h"
+#include "challenge.h"
+#include "rfc2617.h"
+
+
+typedef enum auth_result {
+	ERROR = -2 ,        /* Error occurred, a reply has been sent out -> return 0 to the ser core */
+	NOT_AUTHENTICATED,  /* Don't perform authentication, credentials missing */
+	DO_AUTHENTICATION,  /* Perform digest authentication */
+	AUTHENTICATED,      /* Authenticated by default, no digest authentication necessary */
+	BAD_CREDENTIALS,    /* Digest credentials are malformed */
+	CREATE_CHALLENGE,   /* when AKAv1-MD5 is used first request does not contain credentials,
+	                     * only usename, realm and algorithm. Server should get Authentication
+	                     * Vector from AuC/HSS, create challenge and send it to the UE. */
+	DO_RESYNCHRONIZATION   /* When AUTS is received we need do resynchronization
+	                        * of sequnce numbers with mobile station. */
+} auth_result_t;
+
+typedef int (*check_auth_hdr_t)(struct sip_msg* msg, auth_body_t* auth_body, auth_result_t* auth_res);
+int check_auth_hdr(struct sip_msg* msg, auth_body_t* auth_body, auth_result_t* auth_res);
+
+/*
+ * Purpose of this function is to find credentials with given realm,
+ * do sanity check, validate credential correctness and determine if
+ * we should really authenticate (there must be no authentication for
+ * ACK and CANCEL
+ */
+typedef auth_result_t (*pre_auth_t)(struct sip_msg* msg, str* realm,
+				    hdr_types_t hftype, struct hdr_field** hdr, check_auth_hdr_t check_auth_hdr);
+auth_result_t pre_auth(struct sip_msg* msg, str* realm, hdr_types_t hftype,
+		       struct hdr_field** hdr, check_auth_hdr_t check_auth_hdr);
+
+
+/*
+ * Purpose of this function is to do post authentication steps like
+ * marking authorized credentials and so on.
+ */
+typedef auth_result_t (*post_auth_t)(struct sip_msg* msg, struct hdr_field* hdr);
+auth_result_t post_auth(struct sip_msg* msg, struct hdr_field* hdr);
+
+
+/*
+ * Auth module API
+ */
+typedef struct auth_api {
+    pre_auth_t pre_auth;                  /* The function to be called before authentication */
+    post_auth_t post_auth;                /* The function to be called after authentication */
+    build_challenge_hf_t build_challenge; /* Function to build digest challenge header */
+    struct qp* qop;                       /* qop module parameter */
+	calc_HA1_t      calc_HA1;
+	calc_response_t calc_response;
+} auth_api_t;
+
+typedef int (*bind_auth_t)(auth_api_t* api);
+int bind_auth(auth_api_t* api);
+
+
+#endif /* API_H */

+ 685 - 0
modules_s/auth/auth.xml

@@ -0,0 +1,685 @@
+<?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 id="module.auth"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>auth</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>auth</refname>
+    <refpurpose>Basic Functionality for Digest Authentication</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>auth</command> SER module provides basic functionality
+      for digest authentication. However, it does not provide access to
+      the user information and therefore relies on another module. These
+      modules are <serdoc:module>auth_db</serdoc:module>,
+      <serdoc:module>auth_diameter</serdoc:module>,
+      <serdoc:module>auth_identity</serdoc:module>, and
+      <serdoc:module>auth_radius</serdoc:module>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.consume_credentials">
+      <title>
+        <function>consume_credentials</function> ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>consume_credentials()</function> function
+        removes previously authorized credentials from the request.
+        This means that forwarded or relayed requests
+        will not contain credentials that were checked by this server
+        and ensures that the proxy will not reveal information about
+        credentials used to downstream elements. In addition, the
+        message will be shorter.
+      </para>
+      <para>
+        The function must be called after a call to an authorization
+        function such as
+        <serdoc:func>www_authenticate</serdoc:func>.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.auth.auth_checks_in_dlg">
+      <title><parameter>auth_checks_in_dlg</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>0</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>auth_checks_in_dlg</parameter> parameter controls
+        which integrity checks are performed on requests within a dialog,
+        ie., requests that carry a To tag. The default value of
+        <literal>0</literal> turns off any checks. Otherwise, the value
+        indicates which checks are run.
+      </para>
+      <para>
+        See <serdoc:link linkend="module.auth.integrity_checks">Additional
+        Integrity Checks</serdoc:link> below for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.auth_checks_no_dlg">
+      <title><parameter>auth_checks_no_dlg</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>0</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>auth_checks_no_dlg</parameter> parameter controls
+        which integrity checks are performed on out-of-dialog requests,
+        ie., requests that do not carry a To tag. The default value of
+        <literal>0</literal> turns off any checks. Otherwise, the value
+        indicates which checks are run.
+      </para>
+      <para>
+        See <serdoc:link linkend="module.auth.integrity_checks">Additional
+          Integrity Checks</serdoc:link> below for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.auth_checks_register">
+      <title><parameter>auth_checks_register</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>0</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>auth_checks_register</parameter> parameter
+        controls which integrity checks are performed on a REGISTER
+        message. If the parameter is changed from its default value
+        of <literal>0</literal>, it activates those checks and defines,
+        which checks are run.
+      </para>
+      <para>
+        See <serdoc:link linkend="module.auth.integrity_checks">Additional
+          Integrity Checks</serdoc:link> below for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.challenge_attr">
+      <title><parameter>challenge_attr</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>attriubte name</serdoc:paramtype>
+        <serdoc:paramdefault>$digest_challenge</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>challenge_attr</parameter> parameter names the
+        attribute which will contain the complete header field with the
+        authentication challenge. The content if this attribute has to
+        be added to the response in order to transmit the challenge
+        to the client.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nc_array_order">
+      <title><parameter>nc_array_order</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>20</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nc_array_order</parameter> parameter is an
+        alternative way to set the maximum number of nonces for the nonce
+        check enabled by the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        parameter. Its meaning is identical to that of the
+        <serdoc:modparam module="auth">nc_array_size</serdoc:modparam>
+        parameter but the size is given by stating the power of 2.
+        See <serdoc:modparam module="auth">nc_array_size</serdoc:modparam>
+        and <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        for more details.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nc_array_size">
+      <title><parameter>nc_array_size</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>1048576</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nc_array_size</parameter> parameter sets the
+        maximum number of in-flight nonces for the nonce
+        count check enabled by the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        parameter. It represents the maximum number of nonces for which
+        state will be kept. When this number is exceeded, state for older
+        nonces will be discarded to make space for new ones. See
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam> for
+        more details.
+      </para>
+      <para>
+        The value should be a power of 2. If it is not, it will be rounded
+        down to such a number. For example a value of 1000000 will be
+        rounded down to 524288. The
+        <serdoc:modparam module="auth">nc_array_order</serdoc:modparam>
+        parameter can be used to directly specify the power of 2. For
+        instance, a value for 
+        <serdoc:modparam module="auth">nc_array_order</serdoc:modparam>
+        of 20 is equivalent to a <parameter>nc_array_size</parameter>
+        of 1048576.
+      </para>
+      <para>
+        The memory used to keep the nonce state will be
+        equal to the value of <parameter>nc_array_size</parameter> in
+        bytes.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nid_pool_no">
+      <title><parameter>nid_pool_no</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>1</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nid_pool_no</parameter> parameter controls the number
+        of partitions for the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam> and
+        <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>
+        arrays. It is common to both of them to reduce the nonce size.
+      </para>
+      <para>
+        Instead of using single arrays for keeping nonce state, these arrays
+        can be divided into several partitions. Each SER process is assigned
+        to one of these partitions, allowing for higher concurrency on
+        multi-CPU machines. Besides increasing performance, increasing
+        <parameter>nid_pool_no</parameter> has also a negative effect: it
+        could decrease the maximum supported in-flight nonces in certain
+        conditions.  In the worst case, when only one SER process receives
+        most of the traffic (e.g. very busy TCP connection between two
+        proxies), the in-flight nonces could be limited to the array size
+        (<serdoc:modparam module="auth">nc_array_size</serdoc:modparam>
+        for <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        and <serdoc:modparam module="auth">otn_in_flight_no</serdoc:modparam>
+        for <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>)
+        divided by the partitions number
+        (<parameter>nid_pool_no</parameter>). However for normal traffic,
+        where
+        the process receiving a message is either random or chosen in a
+        round-robin fashion, the maximum number of in-flight nonces will be
+        influenced to a very small degree by
+        <parameter>nid_pool_no</parameter>, since the messages
+        will be close to equally distributed to processes using different
+        partitions.
+      </para>
+      <para>
+        The <parameter>nid_pool_no</parameter> value should be one of:
+        1, 2, 4, 8, 16, 32 or 64 (the maximum value is 64 and all values
+        should be a power of 2 or else they will be rounded down to such
+        a value).
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nonce_auth_max_drift">
+      <title><parameter>nonce_auth_max_drift</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>3</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nonce_auth_max_drift</parameter> parameter
+        specifies the maximum difference in seconds between a nonce
+        creation time and the current time, if the nonce creation time
+        appears to be in the future.
+      </para>
+      <para>
+        In some cases, like shortly after a system time backward
+        adjustment or when the current proxy is part of a cluster which is
+        not time-synchronized, it's possible to receive a nonce with
+        a creation time in the future. In this case if the difference is
+        greater then the value of <parameter>nonce_auth_max_drift</parameter>
+        in seconds, the nonce is  considered stale and the request
+        re-challenged. If this were not done, a dramatic time change
+        backwards may lead to nonces having been generated before the
+        cange being valid for too long.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nonce_count">
+      <title><parameter>nonce_count</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nonce_count</parameter> parameter controls, whether
+        tracking of nounce count is enabled. If it is set to
+        <literal>yes</literal>, the received <parameter>nc</parameter> value
+        is remembered and checked against the value used in the previous
+        digest response. For a successful authentication the received
+        <parameter>nc</parameter> must be greater then the previously
+        received one (See RFC 2617 for more details). This will	provide
+        protection against replay attacks while still allowing credentials
+        caching at the UA side.
+      </para>
+      <para>
+        In order for this to work, the
+        <serdoc:modparam module="auth">qop</serdoc:modparam> parameter
+        must not be set to an empty string. It it is, the challenges won’t
+        include the <parameter>qop</parameter> parameter and the user agent
+        will probably not include the <parameter>qop</parameter> or
+        <parameter>nc</parameter> parameters in its response.
+      </para>
+      <para>
+        If a response doesn’t include <parameter>qop</parameter> or
+        <parameter>nc</parameter> (for example obsolete UAs that don't
+        support them) the response will be checked according to the other
+        enabled nonce checks, in this order:
+        <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam> and
+        the applicable of the additional integrity checks. If a response
+        includes <parameter>nc</parameter> only the normal
+        <serdoc:modparam module="auth">nonce_expire</serdoc:modparam>
+        checks and the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam> checks
+        will be performed, all the other checks will be ignored.
+      </para>
+      <para>
+        The <parameter>nonce_count</parameter> checks work by tracking a
+        limited number of nonces. The maximum number of tracked nonces is
+        set using the
+        <serdoc:modparam module="auth">nc_array_size</serdoc:modparam> or
+        <serdoc:modparam module="auth">nc_array_order</serdoc:modparam>
+        parameters. If this number is exceeded, older entries will be
+        overwritten. As long as the maximum rate of challengeable messages
+        per average response time is lower then
+        <serdoc:modparam module="auth">nc_array_size</serdoc:modparam>,
+        the nonce count check should work flawlessly. For optimal
+        performance (maximum reuse of cache credentials)
+        <serdoc:modparam module="auth">nc_array_size</serdoc:modparam>
+        divided by
+        <serdoc:modparam module="auth">nid_pool_no</serdoc:modparam>
+        should be lower then the message rate multiplied by the desired
+        <serdoc:modparam module="auth">nonce_expire</serdoc:modparam>.
+        <!-- XXX: Give an example with default nonce_expire and typical
+                  RTT. -->
+      </para>
+      <para>
+        The maximum accepted <parameter>nc</parameter> value is 255. If
+        <parameter>nc</parameter> becomes greater then this, the nonce will
+        be considered stale and the UA will be re-challenged.
+      </para>
+      <para>
+        <emphasis>Note:</emphasis> If the <parameter>nonce_count</parameter>
+        parameter is enabled, all challenges should be done in stateful
+        mode, ie., a transaction should be created prior to the
+        authentication check, for instance by using the function
+        <serdoc:func>t_newtran</serdoc:func>, to absorb possible
+        retransmissions. All the replies need to be sent statefuly,
+        using <serdoc:func>t_reply()</serdoc:func>). Otherwise,
+        all retransmissions will be challenged which may cause the user
+        agent to believe the password supplied by the user to be wrong.
+      </para>
+      <para>
+        For more information on stateful processing, see
+        <serdoc:module>tm</serdoc:module>. An example on how to implement
+        a stateful registrar can be found in
+        <serdoc:module>registrar</serdoc:module>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.nonce_expire">
+      <title><parameter>nonce_expire</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>300</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>nonce_exipre</parameter> parameter determines the
+        time in seconds before a nonce is considered stale.
+      </para>
+      <para>
+        Nonces have a limited lifetime after which they 
+        will be considered stale.
+        This is to protect replay attacks. Credentials containing a stale
+        nonce will be not authorized. The user agent will be challenged
+        again instead. This new challenge will not only contain a newly
+        generated nonce, but also the <parameter>stale</parameter>
+        parameter which indicates to the client the failure was not due
+        to a wrong username or password and it doesn't need to disturb
+        the user asking for them. It can simply recalculate its answer 
+	using existing username and password.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.one_time_nonce">
+      <title><parameter>one_time_nonce</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>one_time_nonce</parameter> controls whether digist
+        responses with a reused nonce will be rejected. If it is set to
+        <literal>yes</literal>, nonces cannot be reused by user agents.
+      </para>
+      <para>
+        This results in each request being challenged. To avoid retransmits
+        being challenged, it should only be used in stateful mode, ie., by
+        creating a transaction before authentication, for instance trough
+        a call to the <serdoc:func>t_newtran</serdoc:func> function. For
+        more information on stateful mode see
+        <serdoc:module>tm</serdoc:module>. An example on how to implement
+        a stateful registrar can be found in
+        <serdoc:module>registrar</serdoc:module>.
+      </para>
+      <para>
+        The one time nonce provides enhanced replay protections at the cost
+        of invalidating UA side credentials
+        caching and causing extra round trips. Therefore, the nonce chount
+        check enabled through the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        parameter should be preferred.
+      </para>
+      <para>
+        The one time nonce provides the same protection as the nonce count
+        check but requiring more messages. The advantages are that it uses
+        less memory for the same supported number of maximum in-flight
+        nonces (by a factor of 8) and that it works
+        with user agents that do not support <parameter>qop</parameter>
+        and <parameter>nc</parameter>. It shoud thus be used if such
+        user agents are in use and the checks described in section
+        <serdoc:link linkend="module.auth.integrity_checks">Additional
+          Integrity Checks</serdoc:link> are not deemed sufficient.
+      </para>
+      <para>
+        Like the nonce count check, one time nonces works by tracking a
+        limited number of nonces. The maximum number of tracked nonces is
+        set using the
+        <serdoc:modparam module="auth">otn_in_flight_no</serdoc:modparam>
+        or
+        <serdoc:modparam module="auth">otn_in_flight_order</serdoc:modparam>
+        parameters. If this number is exceeded, older entries will be
+        overwritten. As long as the maximum rate of challengeable messages
+        per average response time is lower then
+        <serdoc:modparam module="auth">otn_in_flight_no</serdoc:modparam>,
+        the one time nonce check should work flawlessly.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.otn_in_flight_no">
+      <title><parameter>otn_in_flight_no</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>1048576</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>otn_in_flight_no</parameter> parameter specifies
+        the maximum number of in-flight nonces for the one time nonce
+        check enabled by the
+        <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>
+        parameter. It represents the maximum number of nonces remembered
+        for the one time nonce check. When this number is exceeded,
+        information about older nonces will be discarded and overwritten
+        with information about newly generated nonces. See
+        <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>
+        for more details.
+      </para>
+      <para>
+        The value should be a power of 2. If it is not, it will be rounded
+        down to such a value. The
+        <serdoc:modparam module="auth">otn_in_flight_order</serdoc:modparam>
+        parameter can be used to directly specify the power of 2.
+      </para>
+      <para>
+        The memory used to keep the nonce information will be identical to
+        the value of <parameter>otn_in_flight_no</parameter> divided by 8
+        since only 1 bit of state is kept per nonce.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.otn_in_flight_order">
+      <title><parameter>otn_in_flight_order</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>20</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>otn_in_flight_order</parameter> parameter is an
+        alternative way to set the maximum number of nonces for the
+        one time nonce check enabled by the
+        <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>
+        parameter. Its meaning is identical to that of the
+        <serdoc:modparam module="auth">otn_in_flight_no</serdoc:modparam>
+        parameter but the size is given by stating the power of 2.
+        See <serdoc:modparam module="auth">otn_in_flight_no</serdoc:modparam>
+        and <serdoc:modparam module="auth">one_time_nonce</serdoc:modparam>
+        for more details.
+      </para>
+    </refsect2>
+
+
+    <refsect2 xml:id="module.auth.protect_contacts">
+      <title><parameter>protect_contacts</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        If the <parameter>protect_contacts</parameter> parameter is set
+        to <literal>yes</literal>, the contacts are included when creating
+        a nonce.
+      </para>
+      <!-- XXX What does this exactly mean? -->
+    </refsect2>
+
+
+    <refsect2 xml:id="module.auth.proxy_challenge_header">
+      <title><parameter>proxy_challenge_header</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"Proxy-Authenticate"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>proxy_challenge_header</parameter> parameter
+        contains the name of the header field that should be used to
+        include the proxy-to-user challenge.
+      </para>
+    </refsect2> 
+
+    <refsect2 xml:id="module.auth.qop">
+      <title><parameter>qop</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"auth"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>qop</parameter> parameter determines, which value
+        for the <parameter>qop</parameter> parameter should be included
+        in a challenge. The parameter was optional in the SIP revision
+        described by RFC 2543. The current version from RFC 3261 states,
+        however, that the parameter must be included in all challenges.
+      </para>
+      <para>
+        In digest authentication, the <parameter>qop</parameter>
+        parameter, short for ‘quality of protection’, indicates which
+        parts of a request should be used for the calculation of the
+        digest response. If the value is <literal>auth</literal>,
+        only authentication should be done and the response is solely
+        calculated using information from the challenge plus the user’s
+        password. A value of <literal>auth-int</literal> indicates that
+        in addition the integrity of the request body is to be verified.
+        For this purpose, a hash over the body is additionally included
+        in the calculation.
+      </para>
+      <para>
+        In a challenge the parameter indicates, which of these methods
+        the server supports. This can either be one of them or both,
+        separated by a comma. For challenges generated by SER, this
+        is given by the <parameter>qop</parameter> module. It may also
+        be an empty string, in which case no <parameter>qop</parameter>
+        parameter will be present in challenges created by SER. This may
+        be necessary for compatibility with some old clients but will
+        make your SER not comply with RFC 3261.
+      </para>
+      <para>
+        Protection against nonce replay attacks can be achieved by
+        additionally enabling the
+        <serdoc:modparam module="auth">nonce_count</serdoc:modparam>
+        parameter. See there for more information.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.secret">
+      <title><parameter>secret</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>random value</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>secret</parameter> parameter
+        sets the secret used for the calculation of the nonce and for
+        message integrity checks. If not set, a random secret will be
+        chosen upon start.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth.www_challenge_header">
+      <title><parameter>www_challenge_header</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"WWW-Authenticate</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>www_challenge_header</parameter> parameter
+        contains the name of the header field that should be used to
+        transmit a user-to-user challenge for authentication.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 id="module.auth.integrity_checks">
+    <title>Additional Integrity Checks</title>
+    <para>
+      The auth module can perform additional integrity checks on the
+      authentication response inclued in a request by the client.
+    </para>
+    <para>
+      Without the extra checks, the nonce value will be used only to
+      store the expire time (see <parameter>nonce_expire</parameter>),
+      plus an MD5 hash over it and some secret to prevent tampering
+      with the expire time. With this arrangement, some replay attacks
+      are still possible in the expire "window".
+    </para>
+    <para>
+      A
+      possible workaround is to always force qop authentification and
+      always check the URI from the authorization header, but this does not
+      work if an upstream proxy rewrites the URI and it will also not work
+      with a lot of UA implementations.
+    </para>
+    <para>
+      When the extra checks are enabled, the nonce will include and extra
+      MD5 hash over some selected parts of the message and another secret.
+      This will be used to check if these parts of the message are the same
+      when a UA retries the request with the authentication response, thus 
+      protecting from replay attacks or at least severly limiting their
+      possibility.
+    </para>
+    <para>
+      Three module parameters control which parts of the message will be
+      included for different types of requests. The parameter
+      <parameter>auth_checks_register</parameter> sets them for REGISTER
+      requests. The parameter <parameter>auth_checks_no_dlg</parameter> 
+      does the same for requests that to not have a
+      <parameter>tag</parameter> parameter in the To header field or have
+      no To header field altogether (in other words requests sent outside
+      of an existing dialog). And, finally, the parameter
+      <parameter>auth_checks_in_dlg</parameter> controls the checks for
+      SIP requests sent within dialogs, such as BYEs or re-INVITEs.
+    </para>
+    <para>
+      The default value for all three parameters is 0, in which case no
+      extra checks are performed and the module behaves as it did before
+      their introduction. Otherwise the parameter is a bit field. The
+      message parts to be included in the check can be determined by adding
+      any of the following values together:
+    </para>
+    <variablelist>
+      <varlistentry>
+        <term><literal>1</literal></term>
+        <listitem>
+          <para>
+            check the full Request-URI,
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><literal>2</literal></term>
+        <listitem>
+          <para>
+            check the Call-ID,
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><literal>4</literal></term>
+        <listitem>
+          <para>
+            check the <parameter>tag</parameter> parameter of the From
+            header field,
+          </para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><literal>9</literal></term>
+        <listitem>
+          <para>
+            check the source IP address the request was received from.
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+    <para> 
+      For example, setting <parameter>auth_checks_register</parameter> to
+      <literal>6</literal> would check if the Call-ID or the From tag
+      have changed from the REGISTER message for which the nonce was
+      generated. This allows nonce reuse only within the same UA and
+      for the expire time.  Note that enabling the extra checks will
+      limit nonce caching by UAs, requiring extra challenges and
+      round trips, but will provide much better protection.
+    </para>
+    <para>
+      When the <parameter>secret</parameter> parameter is set and the
+      extra checks are enabled, the first half of the secret will be used
+      for the expire time hash and the other half for the hash on the
+      extra checks, so make sure you have a long secret. At least 32 
+      characterss are recommended.
+    </para>
+  </refsect1> 
+
+  <refsect1>
+    <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>auth_db</serdoc:module></member>
+      <member><serdoc:module>auth_diameter</serdoc:module></member>
+      <member><serdoc:module>auth_identity</serdoc:module></member>
+      <member><serdoc:module>auth_radius</serdoc:module></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+
+<!-- vim:sw=2 sta et sts=2 ai tw=76
+  -->

+ 339 - 0
modules_s/auth/auth_mod.c

@@ -0,0 +1,339 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication Module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * --------
+ * 2003-02-26 checks and group moved to separate modules (janakj)
+ * 2003-03-10 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)
+ * 2003-04-28 rpid contributed by Juha Heinanen added (janakj)
+ * 2007-10-19 auth extra checks: longer nonces that include selected message
+ *            parts to protect against various reply attacks without keeping
+ *            state (andrei)
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include "../../sr_module.h"
+#include "../../dprint.h"
+#include "../../mem/mem.h"
+#include "../../parser/digest/digest.h"
+#include "../../data_lump.h"
+#include "../../error.h"
+#include "../../ut.h"
+#include "../sl/sl.h"
+#include "auth_mod.h"
+#include "challenge.h"
+#include "api.h"
+#include "nid.h"
+#include "nc.h"
+#include "ot_nonce.h"
+
+MODULE_VERSION
+
+#define RAND_SECRET_LEN 32
+
+
+/*
+ * Module destroy function prototype
+ */
+static void destroy(void);
+
+/*
+ * Module initialization function prototype
+ */
+static int mod_init(void);
+
+/*
+ * Remove used credentials from a SIP message header
+ */
+int consume_credentials(struct sip_msg* msg, char* s1, char* s2);
+
+
+/*
+ * Module parameter variables
+ */
+char* sec_param    = 0;     /* If the parameter was not used, the secret phrase will be auto-generated */
+int   nonce_expire = 300;   /* Nonce lifetime */
+/*int   auth_extra_checks = 0;  -- in nonce.c */
+int   protect_contacts = 0; /* Do not include contacts in nonce by default */
+
+str secret1;
+str secret2;
+char* sec_rand1 = 0;
+char* sec_rand2 = 0;
+
+str challenge_attr = STR_STATIC_INIT("$digest_challenge");
+avp_ident_t challenge_avpid;
+
+str proxy_challenge_header = STR_STATIC_INIT("Proxy-Authenticate");
+str www_challenge_header = STR_STATIC_INIT("WWW-Authenticate");
+
+struct qp qop = {
+    STR_STATIC_INIT("auth"),
+    QOP_AUTH
+};
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+    {"consume_credentials", consume_credentials,     0, 0, REQUEST_ROUTE},
+    {"bind_auth",           (cmd_function)bind_auth, 0, 0, 0            },
+    {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+    {"secret",                 PARAM_STRING, &sec_param             },
+    {"nonce_expire",           PARAM_INT,    &nonce_expire          },
+	{"nonce_auth_max_drift",   PARAM_INT,    &nonce_auth_max_drift  },
+    {"protect_contacts",       PARAM_INT,    &protect_contacts      },
+    {"challenge_attr",         PARAM_STR,    &challenge_attr        },
+    {"proxy_challenge_header", PARAM_STR,    &proxy_challenge_header},
+    {"www_challenge_header",   PARAM_STR,    &www_challenge_header  },
+    {"qop",                    PARAM_STR,    &qop.qop_str           },
+	{"auth_checks_register",   PARAM_INT,    &auth_checks_reg       },
+	{"auth_checks_no_dlg",     PARAM_INT,    &auth_checks_ood       },
+	{"auth_checks_in_dlg",     PARAM_INT,    &auth_checks_ind       },
+	{"nonce_count"  ,          PARAM_INT,    &nc_enabled            },
+	{"nc_array_size",          PARAM_INT,    &nc_array_size         },
+	{"nc_array_order",         PARAM_INT,    &nc_array_k            },
+	{"one_time_nonce"  ,       PARAM_INT,    &otn_enabled           },
+	{"otn_in_flight_no",       PARAM_INT,    &otn_in_flight_no      },
+	{"otn_in_flight_order",    PARAM_INT,    &otn_in_flight_k       },
+	{"nid_pool_no",            PARAM_INT,    &nid_pool_no            },
+    {0, 0, 0}
+};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+    "auth",
+    cmds,
+    0,          /* RPC methods */
+    params,
+    mod_init,   /* module initialization function */
+    0,          /* response function */
+    destroy,    /* destroy function */
+    0,          /* oncancel function */
+    0           /* child initialization function */
+};
+
+
+/*
+ * Secret parameter was not used so we generate
+ * a random value here
+ */
+static inline int generate_random_secret(void)
+{
+    int i;
+    
+    sec_rand1 = (char*)pkg_malloc(RAND_SECRET_LEN);
+    sec_rand2 = (char*)pkg_malloc(RAND_SECRET_LEN);
+    if (!sec_rand1 || !sec_rand2) {
+	LOG(L_ERR, "auth:generate_random_secret: No memory left\n");
+	if (sec_rand1){
+		pkg_free(sec_rand1);
+		sec_rand1=0;
+	}
+	return -1;
+    }
+    
+    /* srandom(time(0));  -- seeded by core */
+    
+    for(i = 0; i < RAND_SECRET_LEN; i++) {
+	sec_rand1[i] = 32 + (int)(95.0 * rand() / (RAND_MAX + 1.0));
+    }
+    
+    secret1.s = sec_rand1;
+    secret1.len = RAND_SECRET_LEN;
+	
+    for(i = 0; i < RAND_SECRET_LEN; i++) {
+	sec_rand2[i] = 32 + (int)(95.0 * rand() / (RAND_MAX + 1.0));
+    }
+    
+    secret2.s = sec_rand2;
+    secret2.len = RAND_SECRET_LEN;
+    
+	 /* DBG("Generated secret: '%.*s'\n", secret.len, secret.s); */
+    
+    return 0;
+}
+
+
+static int mod_init(void)
+{
+    str attr;
+    
+    DBG("auth module - initializing\n");
+    
+	/* If the parameter was not used */
+    if (sec_param == 0) {
+		/* Generate secret using random generator */
+		if (generate_random_secret() < 0) {
+			LOG(L_ERR, "auth:mod_init: Error while generating random secret\n");
+			return -3;
+		}
+    } else {
+		/* Otherwise use the parameter's value */
+		secret1.s = sec_param;
+		secret1.len = strlen(secret1.s);
+		
+		if (auth_checks_reg || auth_checks_ind || auth_checks_ood) {
+			/* divide the secret in half: one half for secret1 and one half for
+			 *  secret2 */
+			secret2.len = secret1.len/2;
+			secret1.len -= secret2.len;
+			secret2.s = secret1.s + secret1.len;
+			if (secret2.len < 16) {
+				WARN("auth: consider a longer secret when extra auth checks are"
+					 " enabled (the config secret is divided in 2!)\n");
+			}
+		}
+    }
+    
+    if ((!challenge_attr.s || challenge_attr.len == 0) ||
+		challenge_attr.s[0] != '$') {
+		ERR("auth: Invalid value of challenge_attr module parameter\n");
+		return -1;
+    }
+    
+    attr.s = challenge_attr.s + 1;
+    attr.len = challenge_attr.len - 1;
+    
+    if (parse_avp_ident(&attr, &challenge_avpid) < 0) {
+		ERR("auth: Error while parsing value of challenge_attr module parameter\n");
+		return -1;
+    }
+	
+    parse_qop(&qop);
+	switch(qop.qop_parsed){
+		case QOP_OTHER:
+			ERR("auth: Unsupported qop parameter value\n");
+			return -1;
+		case QOP_AUTH:
+		case QOP_AUTHINT:
+			if (nc_enabled){
+#ifndef USE_NC
+				WARN("auth: nounce count support enabled from config, but"
+					" disabled at compile time (recompile with -DUSE_NC)\n");
+				nc_enabled=0;
+#else
+				if (nid_crt==0)
+					init_nonce_id();
+				if (init_nonce_count()!=0)
+					return -1;
+#endif
+			}
+#ifdef USE_NC
+			else{
+				INFO("auth: qop set, but nonce-count (nc_enabled) support"
+						" disabled\n");
+			}
+#endif
+			break;
+		default:
+			if (nc_enabled){
+				WARN("auth: nonce-count support enabled, but qop not set\n");
+				nc_enabled=0;
+			}
+			break;
+	}
+	if (otn_enabled){
+#ifdef USE_OT_NONCE
+		if (nid_crt==0) init_nonce_id();
+		if (init_ot_nonce()!=0) 
+			return -1;
+#else
+		WARN("auth: one-time-nonce support enabled from config, but "
+				"disabled at compile time (recompile with -DUSE_OT_NONCE)\n");
+		otn_enabled=0;
+#endif /* USE_OT_NONCE */
+	}
+
+    return 0;
+}
+
+
+static void destroy(void)
+{
+    if (sec_rand1) pkg_free(sec_rand1);
+    if (sec_rand2) pkg_free(sec_rand2);
+#ifdef USE_NC
+	destroy_nonce_count();
+#endif
+#ifdef USE_OT_NONCE
+	destroy_ot_nonce();
+#endif
+#if defined USE_NC || defined USE_OT_NONCE
+	destroy_nonce_id();
+#endif
+}
+
+
+/*
+ * Remove used credentials from a SIP message header
+ */
+int consume_credentials(struct sip_msg* msg, char* s1, char* s2)
+{
+    struct hdr_field* h;
+    int len;
+    
+    get_authorized_cred(msg->authorization, &h);
+    if (!h) {
+		get_authorized_cred(msg->proxy_auth, &h);
+		if (!h) { 
+			if (msg->REQ_METHOD != METHOD_ACK 
+				&& msg->REQ_METHOD != METHOD_CANCEL) {
+				LOG(L_ERR, "auth:consume_credentials: No authorized "
+					"credentials found (error in scripts)\n");
+			}
+			return -1;
+		}
+    }
+    
+    len = h->len;
+    
+    if (del_lump(msg, h->name.s - msg->buf, len, 0) == 0) {
+		LOG(L_ERR, "auth:consume_credentials: Can't remove credentials\n");
+		return -1;
+    }
+    
+    return 1;
+}
+

+ 59 - 0
modules_s/auth/auth_mod.h

@@ -0,0 +1,59 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication Module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * --------
+ * 2003-04-28 rpid contributed by Juha Heinanen added (janakj)
+ * 2007-10-19 auth extra checks: longer nonces that include selected message
+ *            parts to protect against various reply attacks without keeping
+ *            state (andrei)
+ */
+
+#ifndef AUTH_MOD_H
+#define AUTH_MOD_H
+
+#include "../../str.h"
+#include "../sl/sl.h"
+#include "../../parser/msg_parser.h"    /* struct sip_msg */
+#include "../../parser/digest/digest.h"
+#include "nonce.h" /* auth_extra_checks & AUTH_CHECK flags */
+
+/*
+ * Module parameters variables
+ */
+extern str secret1;            /* secret phrase used to generate nonce */
+extern str secret2;            /* secret phrase used to generate nonce */
+extern int nonce_expire;      /* nonce expire interval */
+extern int protect_contacts;  /* Enable/disable contact hashing in nonce */
+extern sl_api_t sl;
+extern avp_ident_t challenge_avpid;
+extern str proxy_challenge_header;
+extern str www_challenge_header;
+extern struct qp qop;
+
+#endif /* AUTH_MOD_H */

+ 229 - 0
modules_s/auth/challenge.c

@@ -0,0 +1,229 @@
+/*
+ * $Id$
+ *
+ * Challenge related 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * --------
+ * 2003-01-20 snprintf in build_auth_hf replaced with memcpy to avoid
+ *            possible issues with too small buffer
+ * 2003-01-26 consume_credentials no longer complains about ACK/CANCEL(jiri)
+ * 2007-10-19 auth extra checks: longer nonces that include selected message
+ *            parts to protect against various reply attacks without keeping
+ *            state (andrei)
+ * 2008-07-08 nonce-count (nc) support (andrei)
+ */
+
+#include "../../data_lump.h"
+#include "../../mem/mem.h"
+#include "../../parser/digest/digest.h"
+#include "../../usr_avp.h"
+#include "../../ut.h"
+#include "../../ser_time.h"
+#include "auth_mod.h"
+#include "challenge.h"
+#include "nonce.h"
+#include "api.h"
+#include "nc.h"
+#include "ot_nonce.h"
+
+#define QOP_PARAM_START   ", qop=\""
+#define QOP_PARAM_START_LEN (sizeof(QOP_PARAM_START)-1)
+#define QOP_PARAM_END     "\""
+#define QOP_PARAM_END_LEN (sizeof(QOP_PARAM_END)-1)
+#define STALE_PARAM	  ", stale=true"
+#define STALE_PARAM_LEN	  (sizeof(STALE_PARAM)-1)
+#define DIGEST_REALM	  ": Digest realm=\""
+#define DIGEST_REALM_LEN  (sizeof(DIGEST_REALM)-1)
+#define DIGEST_NONCE	  "\", nonce=\""
+#define DIGEST_NONCE_LEN  (sizeof(DIGEST_NONCE)-1)
+#define DIGEST_MD5	  ", algorithm=MD5"
+#define DIGEST_MD5_LEN	  (sizeof(DIGEST_MD5)-1)
+#define DIGEST_ALGORITHM     ", algorithm="
+#define DIGEST_ALGORITHM_LEN (sizeof(DIGEST_ALGORITHM)-1)
+
+
+/**
+ * Create {WWW,Proxy}-Authenticate header field
+ * @param nonce nonce value
+ * @param algorithm algorithm value
+ * @return -1 on error, 0 on success
+ * 
+ * The result is stored in an attribute.
+ * If nonce is not null that it is used, instead of call calc_nonce.
+ * If algorithm is not null that it is used irrespective of _PRINT_MD5
+ * 
+ * Major usage of nonce and algorithm params is AKA authentication. 
+ */
+int build_challenge_hf(struct sip_msg* msg, int stale, str* realm, str* nonce, str* algorithm, int hftype)
+{
+    char *p;
+    str* hfn, hf;
+    avp_value_t val;
+    int nonce_len, l, cfg;
+	int t;
+#if defined USE_NC || defined USE_OT_NONCE
+	unsigned int n_id;
+	unsigned char pool;
+#endif
+    
+    if (realm) {
+        DEBUG("build_challenge_hf: realm='%.*s'\n", realm->len, realm->s);
+    }
+    if (nonce) {
+        DEBUG("build_challenge_hf: nonce='%.*s'\n", nonce->len, nonce->s);
+    }
+    if (algorithm) {
+        DEBUG("build_challenge_hf: algorithm='%.*s'\n", algorithm->len, algorithm->s);
+    }
+    
+    if (hftype == HDR_PROXYAUTH_T) {
+		hfn = &proxy_challenge_header;
+    } else {
+		hfn = &www_challenge_header;
+    }
+    
+	cfg = get_auth_checks(msg);
+
+    nonce_len = get_nonce_len(cfg, nc_enabled || otn_enabled);
+
+    hf.len = hfn->len;
+    hf.len += DIGEST_REALM_LEN
+	+ realm->len
+	+ DIGEST_NONCE_LEN;
+    if (nonce) {
+    	hf.len += nonce->len
+    	          + 1; /* '"' */
+    }
+    else {
+    	hf.len += nonce_len
+    	          + 1; /* '"' */
+    }
+	hf.len += ((stale) ? STALE_PARAM_LEN : 0);
+    if (algorithm) {
+    	hf.len += DIGEST_ALGORITHM_LEN + algorithm->len;
+    }
+    else {
+    	hf.len += 0
+#ifdef _PRINT_MD5
+	+DIGEST_MD5_LEN
+#endif
+		;
+    }
+    
+    if (qop.qop_parsed != QOP_UNSPEC) {
+		hf.len += QOP_PARAM_START_LEN + qop.qop_str.len + QOP_PARAM_END_LEN;
+    }
+	hf.len += CRLF_LEN;
+    p = hf.s = pkg_malloc(hf.len);
+    if (!hf.s) {
+		ERR("auth: No memory left (%d bytes)\n", hf.len);
+		return -1;
+    }
+    
+    memcpy(p, hfn->s, hfn->len); p += hfn->len;
+    memcpy(p, DIGEST_REALM, DIGEST_REALM_LEN); p += DIGEST_REALM_LEN;
+    memcpy(p, realm->s, realm->len); p += realm->len;
+    memcpy(p, DIGEST_NONCE, DIGEST_NONCE_LEN); p += DIGEST_NONCE_LEN;
+    if (nonce) {
+        memcpy(p, nonce->s, nonce->len); p += nonce->len;
+    }
+    else {
+        l=nonce_len;
+		t=ser_time(0);
+#if defined USE_NC || defined USE_OT_NONCE
+		if (nc_enabled || otn_enabled){
+			pool=nid_get_pool();
+			n_id=nid_inc(pool);
+#ifdef USE_NC
+			if (nc_enabled){
+				nc_new(n_id, pool);
+				pool|=  NF_VALID_NC_ID;
+			}
+#endif
+#ifdef USE_OT_NONCE
+			if (otn_enabled){
+				otn_new(n_id, pool);
+				pool|= NF_VALID_OT_ID;
+			}
+#endif
+		}else{
+			pool=0;
+			n_id=0;
+		}
+		if (calc_nonce(p, &l, cfg, t, t + nonce_expire, n_id, pool,
+						&secret1, &secret2, msg) != 0)
+#else  /* USE_NC || USE_OT_NONCE*/
+		if (calc_nonce(p, &l, cfg, t, t + nonce_expire, 
+						&secret1, &secret2, msg) != 0) 
+#endif /* USE_NC || USE_OT_NONCE */
+		{
+            ERR("auth: calc_nonce failed (len %d, needed %d)\n",
+                 nonce_len, l);
+            pkg_free(hf.s);
+            return -1;
+        }
+        p += l;
+    }
+    *p = '"'; p++;
+
+    if (qop.qop_parsed != QOP_UNSPEC) {
+		memcpy(p, QOP_PARAM_START, QOP_PARAM_START_LEN);
+		p += QOP_PARAM_START_LEN;
+		memcpy(p, qop.qop_str.s, qop.qop_str.len);
+		p += qop.qop_str.len;
+		memcpy(p, QOP_PARAM_END, QOP_PARAM_END_LEN);
+		p += QOP_PARAM_END_LEN;
+    }
+    if (stale) {
+		memcpy(p, STALE_PARAM, STALE_PARAM_LEN);
+		p += STALE_PARAM_LEN;
+    }
+	if (algorithm) {
+		memcpy(p, DIGEST_ALGORITHM, DIGEST_ALGORITHM_LEN); p += DIGEST_ALGORITHM_LEN;
+		memcpy(p, algorithm->s, algorithm->len); p += algorithm->len;
+	}
+	else {
+#ifdef _PRINT_MD5
+    memcpy(p, DIGEST_MD5, DIGEST_MD5_LEN ); p += DIGEST_MD5_LEN;
+#endif
+	}
+    memcpy(p, CRLF, CRLF_LEN); p += CRLF_LEN;
+	hf.len=(int)(p-hf.s); /* fix len, it might be smaller due to a smaller
+							 nonce */
+    
+    DBG("auth: '%.*s'\n", hf.len, ZSW(hf.s));
+	
+    val.s = hf;
+    if (add_avp(challenge_avpid.flags | AVP_VAL_STR, challenge_avpid.name, val) < 0) {
+		ERR("auth: Error while creating attribute\n");
+		pkg_free(hf.s);
+		return -1;
+    }
+	pkg_free(hf.s);
+
+    return 0;
+}

+ 51 - 0
modules_s/auth/challenge.h

@@ -0,0 +1,51 @@
+/*
+ * $Id$
+ *
+ * Challenge related 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef CHALLENGE_H
+#define CHALLENGE_H
+
+#include "../../str.h"
+
+/**
+ * Create {WWW,Proxy}-Authenticate header field
+ * @param nonce nonce value
+ * @param algorithm algorithm value
+ * @return -1 on error, 0 on success
+ * 
+ * The result is stored in an attribute.
+ * If nonce is not null that it is used, instead of call calc_nonce.
+ * If algorithm is not null that it is used irrespective of _PRINT_MD5
+ * 
+ * Major usage of nonce and algorithm params is AKA authentication. 
+ */
+typedef int (*build_challenge_hf_t)(struct sip_msg* msg, int stale, str* realm, str* nonce, str* algorithm, int hftype);
+int build_challenge_hf(struct sip_msg* msg, int stale, str* realm, str* nonce, str* algorithm, int hftype);
+
+#endif /* CHALLENGE_H */

+ 29 - 0
modules_s/auth/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = auth
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 75 - 0
modules_s/auth/doc/auth.xml

@@ -0,0 +1,75 @@
+<?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" 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>
+	</authorgroup>
+
+	<copyright>
+	    <year>2002</year>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Auth Module</title>
+    
+    <section id="auth.overview">
+	<title>Overview</title>
+	<para>
+	    This is a generic module that itself doesn't provide all functions
+	    necessary for authentication but provides functions that are needed
+	    by all other authentication related modules (so called
+	    authentication backends).
+	</para>
+	<para>
+	    We decided to break the authentication code into several modules
+	    because there are now more than one backends (currently database
+	    authentication and radius are supported). This allows us to create
+	    separate packages so uses can install and load only required
+	    functionality. This also allows us to avoid unnecessary
+	    dependencies in the binary packages.
+	</para>
+    </section>
+
+    <section id="auth.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>sl</title>
+			<para>The modules needs sl module to send stateless replies.</para>
+		    </formalpara>
+		</listitem>
+	    </itemizedlist>
+	</para>
+    </section>
+    
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+
+</section>

+ 225 - 0
modules_s/auth/doc/functions.xml

@@ -0,0 +1,225 @@
+<?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.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Functions</title>
+    
+    <section id="www_challenge">
+	<title><function>www_challenge(realm,qop)</function></title>
+	<para>
+	    The function challenges a user agent. It will generate a
+	    WWW-Authorize header field containing a digest challenge, it will
+	    put the header field into a response generated from the request the
+	    server is processing and send the reply. Upon reception of such a
+	    reply the user agent should compute credentials and retry the
+	    request. For more information regarding digest authentication see
+	    <ulink url="http://www.ietf.org/rfc/rfc2617.txt">RFC2617</ulink>.
+	</para>
+	<para>Meaning of the parameters 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>
+	    <listitem>
+		<para>
+		    <emphasis>qop</emphasis> - Value of this parameter can be
+		    either "1" or "0". When set to 1 then the server will put
+		    qop parameter in the challenge. When set to 0 then the
+		    server will not put qop parameter in the challenge. It is
+		    strongly recommended to use qop parameter, however there
+		    are still some user agents that cannot handle qop parameter
+		    properly so we made this optional. On the other hand there
+		    are still some user agents that cannot handle request
+		    without qop parameter too.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>www_challenge usage</title>
+	    <programlisting>
+...
+if (www_authorize("iptel.org", "subscriber")) {
+    www_challenge("iptel.org", "1");
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="proxy_challenge">
+	<title><function>proxy_challenge(realm, qop)</function></title>
+	<para>
+	    The function challenges a user agent. It will generate a
+	    Proxy-Authorize header field containing a digest challenge, it will
+	    put the header field into a response generated from the request the
+	    server is processing and send the reply. Upon reception of such a
+	    reply the user agent should compute credentials and retry the
+	    request. For more information regarding digest authentication see
+	    <ulink url="http://www.ietf.org/rfc/rfc2617.txt">RFC2617</ulink>.
+	</para>
+	<para>Meaning of the parameters 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>
+	    <listitem>
+		<para>
+		    <emphasis>qop</emphasis> - Value of this parameter can be
+		    either "1" or "0". When set to 1 then the server will put
+		    qop parameter in the challenge. When set to 0 then the
+		    server will not put qop parameter in the challenge. It is
+		    strongly recommended to use qop parameter, however there
+		    are still some user agents that cannot handle qop parameter
+		    properly so we made this optional. On the other hand there
+		    are still some user agents that cannot handle request
+		    without qop parameter too.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>proxy_challenge usage</title>
+	    <programlisting>
+...
+if (!proxy_authorize("", "subscriber)) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="consume_credentials">
+	<title><function>consume_credentials()</function></title>
+	<para>
+	    This function removes previously authorized credentials from the
+	    message being processed by the server. That means that the
+	    downstream message will not contain credentials there were used by
+	    this server. This ensures that the proxy will not reveal
+	    information about credentials used to downstream elements and also
+	    the message will be a little bit shorter. The function must be
+	    called after <function>www_authorize</function> or
+	    <function>proxy_authorize</function>.
+	</para>
+	<example>
+	    <title>consume_credentials example</title>
+	    <programlisting>
+...
+if (www_authorize("", "subscriber)) {
+    consume_credentials();
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="is_rpid_user_e164">
+	<title><function>is_rpid_user_e164()</function></title>
+	<para>
+	    The function checks if the SIP URI received from the database or
+	    radius server and will potentially be used in Remote-Party-ID
+	    header field contains an E164 number (+ followed by up to 15
+	    decimal digits) in its user part.  Check fails, if no such SIP URI
+	    exists (i.e. radius server or database didn't provide this
+	    information).
+	</para>
+	<example>
+	    <title>is_rpid_user_e164 usage</title>
+	    <programlisting>
+...
+if (is_rpid_user_e164()) {
+    # do something here
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="append_rpid_hf0">
+	<title><function>append_rpid_hf()</function></title>
+	<para>
+	    Appends to the message a Remote-Party-ID header that contains
+	    header 'Remote-Party-ID: ' followed by the saved value of the SIP
+	    URI received from the database or radius server followed by the
+	    value of module parameter radius_rpid_suffix.  The function does
+	    nothing if no saved SIP URI exists.
+	</para>
+	<example>
+	    <title>append_rpid_hf usage</title>
+	    <programlisting>
+...
+append_rpid_hf();  # Append Remote-Party-ID header field
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="append_rpid_hf2">
+	<title><function>append_rpid_hf(prefix, suffix)</function></title>
+	<para>
+	    This function is the same as the function described in <xref
+		linkend="append_rpid_hf0"/>. The only difference is that it
+		accepts two parameters, prefix and suffix to be added to
+		Remote-Party-ID header field. This function ignores rpid_prefix
+		and rpid_suffix parameters, instead of that allows to set them
+		for every call.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+		    <emphasis>prefix</emphasis> - Prefix of the Remote-Party-ID
+		    URI. The string will be added at the begining of body of
+		    the header field, just before the URI.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para>
+		    <emphasis>suffix</emphasis> - Suffix of the Remote-Party-ID
+		    header field. The string will be appended at the end of the
+		    header field. It can be used to set various URI parameters,
+		    for example.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>append_rpid_hf(prefix, suffix) usage</title>
+	    <programlisting>
+...
+append_rpid_hf("", ";party=calling;id-type=subscriber;screen=yes");  # Append Remote-Party-ID header field
+...
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 595 - 0
modules_s/auth/doc/params.xml

@@ -0,0 +1,595 @@
+<?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.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Parameters</title>
+    
+    <section id="auth.auth_checks">
+	<title>
+	  <varname>auth_checks_register</varname>,
+	  <varname>auth_checks_no_dlg</varname>, and
+	  <varname>auth_checks_in_dlg</varname> (flags)
+	</title>
+	<para>
+	    These three module parameters control which optional integrity
+         checks will be performed on the SIP message carrying digest response
+        during digest authentication. <varname>auth_check_register</varname>
+        controls integrity checks to be peformed on REGISTER messages,
+        <varname>auth_checks_no_dlg</varname> controls which optional
+        integrity checks will be performed on SIP requests that have no To
+        header field or no To tag (in other words the requests either
+        establishing or outside
+        dialogs). <varname>auth_checks_in_dlg</varname> controls which
+        integrity checks will be performed on SIP requests within dialogs,
+        such as BYE or re-INVITE. The default value for all three parameters
+        is 0 (old behaviour, no extra checks). The set of integrity checks
+        that can be performed on REGISTERs is typically different from sets of
+        integrity checks that can be performed for other SIP request types,
+        hence we have three independent module parameters.
+	</para>
+	<para>
+		Without the extra checks the nonce will protect only against expired
+		values. Some reply attacks are still possible in the expire "window".
+		A possible workaround is to always force qop 
+		 authentication and always check the uri from the authorization
+		 header, but this would not work if an upstream proxy rewrites the uri
+		 and it will also not work with a lot of UA implementations.
+	</para>
+	<para>
+		In this case the nonce value will be used only to hold
+		the expire time (see <varname>nonce_expire</varname>) and an MD5
+		over it and some secret (the MD5 is used to make sure that nobody
+		tampers with the nonce expire time).
+	</para>
+	<para>
+		When the extra checks are enabled, the nonce will include and extra
+		MD5 over the selected part/parts of the message (see below) and some 
+		other secret. This will be used to check if the selected part of 
+		the message is the same when an UA tries to reuse the nonce, thus 
+		protecting or severely limiting reply attacks.
+	</para>
+	<para>
+		The possible flag values for all three parameters are: 1 for checking
+		if the message uri changed (uses the whole uri), 2 for checking the
+		callid, 4 for the from tag and 8 for the source ip (see nonce.h). For
+		example setting
+		 <varname>auth_checks_register</varname> to 6 would check if the
+		 callid or the from tag changed from the REGISTER message for which
+		 the original nonce was generated (this would allow nonce reuse only
+		 within the same UA and for the expire time).  Note that enabling
+		 the extra checks will limit nonce caching by UAs, requiring extra
+		 challenges and roundtrips, but will provide much better protection.
+	</para>
+	<para>
+		When the <varname>secret</varname> parameter is set and the extra
+		checks are enabled, the first half of the <varname>secret</varname> 
+		will be used for the expire time MD5 and the other half for the extra
+		checks MD5, so make sure you have a long secret (32 chars or longer are
+		recommended).
+	</para>
+	<example>
+	    <title>Setting the <varname>auth_checks_register</varname> module 
+				parameter</title>
+	    <programlisting>
+modparam("auth", "auth_checks_register", 2) # callid
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="qop">
+	<title><varname>qop</varname> (string)</title>
+	<para>
+		If set, enable <emphasis>qop</emphasis> for challenges: each challenge
+		will include a <emphasis>qop</emphasis> parameter. This is the 
+		recommended way, but some older non rfc3261 compliant UAs might get
+		confused and might not authenticate properly if <varname>qop</varname>
+		is enabled.
+	</para>
+	<para>
+		Enabling <varname>qop</varname> together with
+		<varname>nonce_count</varname> will provide extra-security 
+		(protection against replay attacks) while still allowing 
+		credentials caching at the UA side and thus not compromising 
+		performance.
+	</para>
+	<para>
+		The possible values are: "auth", "auth-int" and "" (unset).
+	</para>
+	<para>
+	    The default value is not-set ("").
+	</para>
+	<para>
+		See also:
+			<varname>nonce_count</varname>.
+	</para>
+	<example>
+	    <title>qop example</title>
+	    <programlisting>
+modparam("auth", "qop", "auth")   # set qop=auth
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="nonce_count">
+	<title><varname>nonce_count</varname> (boolean)</title>
+	<para>
+		If enabled the received <emphasis>nc</emphasis> value is remembered 
+		and checked against the older value (for a successful authentication
+		the received <emphasis>nc</emphasis> must be greater then the 
+		previously received one, see rfc2617 for more details). This will 
+		provide protection against replay attacks while still allowing
+		credentials caching at the UA side.
+	</para>
+	<para>
+		It depends on <varname>qop</varname> being enabled (if 
+		<varname>qop</varname> is not enabled, the challenges won't include
+		<emphasis>qop</emphasis> and so the UA will probably not include the
+		<emphasis>qop</emphasis> or <emphasis>nc</emphasis> parameters in its
+		response).
+	</para>
+	<para>
+		If a response doesn't include <emphasis>qop</emphasis> or 
+		<emphasis>nc</emphasis> (for example obsolete UAs that don't support
+		them) the response will be checked according to the other enabled
+		nonce checks, in this order: one_time_nonce and auth_check_*.
+		If a response includes <emphasis>nc</emphasis> only the normal
+		<varname>nonce_expire</varname> checks and the 
+		<varname>nonce_count</varname> checks will be performed, all
+		the other checks will be ignored.
+	</para>
+	<para>
+		The <varname>nonce_count</varname> checks work by tracking a limited
+		number of nonces. The maximum number of tracked nonces is set using
+		the <varname>nc_array_size</varname> or 
+		<varname>nc_array_order</varname> parameters. If this number is 
+		exceeded, older entries will be overwritten. As long as the maximum
+		rate of challengeable messages per average response time is lower then
+		<varname>nc_array_size</varname>, the <varname>nonce_count</varname>
+		checks should work flawlessly. For optimum performance (maximum reuse
+		of cache credentials) <varname>nc_array_size</varname> divided by
+		<varname>nid_pool_no</varname> should be lower then the message rate
+		 multiplied by the desired <varname>nonce_expire</varname>.
+	</para>
+	<para>
+		The maximum accepted <emphasis>nc</emphasis> value is 255. If 
+		<emphasis>nc</emphasis> becomes greater then this, the nonce will be
+		considered stale and the UA will be re-challenged.
+	</para>
+	<para>
+		<emphasis>Note:</emphasis> <varname>nonce_count</varname> should be 
+		enabled only in stateful mode (a transaction should be created prior
+		to the authentication check to absorb possible retransmissions and all
+		the replies should be sent statefuly, using 
+		<function>t_reply()</function>).
+		If <varname>nonce_count</varname> and the authentication checks are
+		used in the stateless mode then all retransmissions will be 
+		challenged.
+	</para>
+	<para>
+		The default value is 0 (off).
+	</para>
+	<para>
+		See also: 
+					<varname>qop</varname>,
+					<varname>nc_array_size</varname>,
+					<varname>nc_array_order</varname>,
+					<varname>nid_pool_no</varname>,
+					<varname>nonce_expire</varname>.
+					<varname>one_time_nonce</varname>.
+	</para>
+	<example>
+	    <title>nonce_count example</title>
+	    <programlisting>
+modparam("auth", "nonce_count", 1) # enable nonce_count support
+modparam("auth", "qop", "auth")    # enable qop=auth
+
+....
+route{
+...
+	# go stateful and catch retransmissions
+	if (!t_newtran())
+		drop; # retransmission
+	if (method=="REGISTER"){
+		if (!www_authenticate("test", "credentials")){
+			# reply must be sent with t_reply because the 
+			# transaction is already created at this point 
+			# (we are in "stateful" mode)
+			if ($? == -2){
+				t_reply("500", "Internal Server Error");
+			}else if ($? == -3){
+				t_reply("400", "Bad Request");
+			}else{
+				if ($digest_challenge) 
+					append_to_reply("%$digest_challenge");
+				t_reply("401", "Unauthorized");
+			}
+			drop;
+		}
+		if (!save_noreply("location")) {
+			t_reply("400", "Invalid REGISTER Request");
+			drop;
+		}
+		append_to_reply("%$contact");
+		t_reply("$code", "$reason"); # no %, avps are used directly 
+		drop;
+	}else{
+		if (!proxy_authenticate("my_realm", "credentials")){
+			if ($? == -2){
+				t_reply("500", "Internal Server Error");
+			}else if ($? == -3){
+				t_reply("400", "Bad Request");
+			}else{
+				if ($digest_challenge) 
+					append_to_reply("%$digest_challenge");
+				t_reply("401", "Unauthorized");
+			}
+			drop;
+		}
+	}
+...
+	    </programlisting>
+	</example>
+    </section>
+
+	<section id="one_time_nonce">
+	<title><varname>one_time_nonce</varname> (boolean)</title>
+	<para>
+		If set to 1 nonce reuse is disabled: each nonce is allowed only once,
+		in the first reponse to a challenge. All the messages will be 
+		challenged, even retransmissions. Stateful mode should be used, to
+		catch retransmissions before the authentication checks (using
+		 <function>t_newtran()</function> before the authentication checks
+		 and sending all the replies with <function>t_reply()</function>).
+	</para>
+	<para>
+		<varname>one_time_nonce</varname> provides enhanced replay protections
+		at the cost of invalidating UA side credentials caching, challenging
+		every message (and thus generating extra messages and extra 
+		round-trips) and requiring stateful mode. In general 
+		<varname>qop</varname> and <varname>nonce_count</varname> should
+		be prefered (if possible) with fallback to 
+		<varname>auth_checks_*</varname>. Due to the disadvantages listed 
+		above, <varname>one_time_nonce</varname> should be used only if the 
+		extra checks provided by <varname>auth_checks_register</varname>,
+		<varname>auth_checks_no_dlg</varname> and
+		<varname>auth_checks_in_dlg</varname> are deemed insufficient for a
+		specific setup.
+	</para>
+	<para>
+		Compared to <varname>nonce_count</varname>, 
+		<varname>one_time_nonce</varname> provides the same protection, but at
+		a higher message cost. The only advantages are that it works with
+		user agents that do not support <emphasis>qop</emphasis> and 
+		<emphasis>nc</emphasis> and that it uses less memory for the same
+		supported number of maximum in-flight nonces (by a factor of 8).
+		<varname>one_time_nonce</varname> can be used as fallback from
+		<varname>nonce_count</varname>, when the UA doesn't support 
+		<emphasis>nc</emphasis> (it happens automatically when both of them
+		are enabled).
+	</para>
+	<para>
+		Like <varname>nonce_count</varname>, <varname>one_time_nonce</varname>
+		works by tracking a limited number of nonces. The maximum number of 
+		tracked nonces is set using the <varname>otn_in_flight_no</varname> or
+		<varname>otn_in_flight_order</varname> parameters. If this number is 
+		exceeded, older entries will be overwritten. As long as the maximum
+		rate of challengeable messages per average response time is lower then
+		<varname>otn_in_flight_no</varname>, the 
+		<varname>one_time_nonce</varname> checks should work flawlessly.
+	</para>
+	<para>
+	    The default value is 0 (off).
+	</para>
+	<para>
+		See also:
+			<varname>otn_in_flight_no</varname>,
+			<varname>otn_in_flight_order</varname>,
+			<varname>nid_pool_no</varname> and
+			<varname>nonce_count</varname>.
+	</para>
+	<example>
+	    <title>one_time_nonce example</title>
+	    <programlisting>
+modparam("auth", "one_time_nonce", 1)
+# Note: stateful mode should be used, see the nonce_count example
+	    </programlisting>
+	</example>
+	</section>
+
+    <section id="nid_pool_no">
+	<title><varname>nid_pool_no</varname> (integer)</title>
+	<para>
+		Controls the number of partitions for the 
+		<varname>nonce_count</varname> and <varname>one_time_nonce</varname>
+		arrays (it's common to both of them to reduce the nonce size).
+	</para>
+	<para>
+		Instead of using single arrays for keeping nonce state, these arrays
+		can be divided into more partitions. Each ser process is assigned to
+		one of these partitions, allowing for higher concurrency on multi-CPU
+		machines. Besides increasing performance, increasing 
+		<varname>nid_pool_no</varname> has also a negative effect: it could
+		decrease the maximum supported in-flight nonces in certain conditions.
+		In the worst case, when only one ser process receives most of the 
+		traffic (e.g. very busy tcp connection between two proxies), the 
+		in-flight nonces could be limited to the array size 
+		(<varname>nc_array_size</varname> for <varname>nonce_count</varname>
+		 or <varname>otn_in_flight_no</varname> for 
+		 <varname>one_time_nonce</varname>) divided by the partitions number
+		 (<varname>nid_pool_no</varname>). However for normal traffic, when
+		 the process receiving a message is either random or chosen in 
+		 a round-robin fashion the maximum in-flight nonces number will be 
+		 very little influenced by <varname>nid_pool_no</varname> (the 
+		 messages will be close to equally distributed to processes using
+		 different partitions).
+	</para>
+	<para>
+		<varname>nid_pool_no</varname> value should be one of: 1, 2, 4, 8, 16,
+		32 or 64 (the maximum value is 64 and all values should be of the 
+		form 2^k or else they will be rounded down to 2^k).
+	</para>
+	<para>
+	    The default value is 1.
+	</para>
+	<para>
+		See also:
+			<varname>nonce_count</varname>,
+			<varname>one_time_nonce</varname>,
+			<varname>nc_array_size</varname> and
+			<varname>otn_in_flight_no</varname>.
+	</para>
+	<example>
+	    <title>nid_pool_no example</title>
+	    <programlisting>
+modparam("auth", "nid_pool_no", 4)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="nc_array_size">
+	<title><varname>nc_array_size</varname> (integer)</title>
+	<para>
+		Maximum number of in-flight nonces for <varname>nonce_count</varname>.
+		It represents the maximum nonces for which state will be kept. When
+		this number is exceeded, state for the older nonces will be 
+		discarded to make space for new ones (see 
+		<varname>nonce_count</varname> for more details).
+	</para>
+	<para>
+		The value should be of the form 2^k. If it's not it will be rounded
+		down to 2^k (for example a value of 1000000 will be rounded down to
+		2^19=524288). <varname>nc_array_order</varname> can be used to 
+		directly specify the power of 2 (e.g. 
+		<varname>nc_array_order</varname> set to 20 is equivalent to
+		<varname>nc_array_size</varname> set to 1048576).
+	</para>
+	<para>
+		The memory used to keep the nonce state will be 
+		<varname>nc_array_size</varname> in bytes.
+	</para>
+	<para>
+	    The default value is 1048576 (1M in-flight nonces, using 1Mb memory).
+	</para>
+	<para>
+		See also:
+			<varname>nonce_count</varname> and
+			<varname>nid_pool_no</varname>.
+	</para>
+	<example>
+	    <title>nc_array_size example</title>
+	    <programlisting>
+modparam("auth", "nc_array_size", 4194304)   # 4Mb
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="nc_array_order">
+	<title><varname>nc_array_order</varname> (integer)</title>
+	<para>
+		Equivalent to <varname>nc_array_size</varname>, but instead of 
+		directly specifying the size, its value is the power at which 2 should
+		be raised (log2(<varname>nc_array_size</varname>)).
+	</para>
+	<para>
+		<varname>nc_array_size</varname> = 2^<varname>nc_array_order</varname>.			For more details see <varname>nc_array_size</varname>.
+	</para>
+	<para>
+	    The default value is 20 (1M in-flight nonces, using 1Mb memory).
+	</para>
+	<para>
+		See also:
+			<varname>nonce_count</varname>,
+			<varname>nc_array_size</varname> and
+			<varname>nid_pool_no</varname>.
+	</para>
+	<example>
+	    <title>nc_array_order example</title>
+	    <programlisting>
+modparam("auth", "nc_array_order", 22)   # 4Mb
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="otn_in_flight_no">
+	<title><varname>otn_in_flight_no</varname> (integer)</title>
+	<para>
+		Maximum number of in-flight nonces for 
+		<varname>one_time_nonce</varname>. It represents the maximum number
+		of nonces remembered for the one-time-nonce check. When this 
+		number is exceeded, information about older nonces will be discarded
+		and overwritten with information about the new generated ones (see
+		<varname>one_time_nonce</varname> for more details).
+	</para>
+	<para>
+		The value should be of the form 2^k. If it's not it will be rounded
+		down to 2^k (for example a value of 1000000 will be rounded down to
+		2^19=524288). <varname>otn_in_flight_no</varname> can be used to 
+		directly specify the power of 2 (e.g. 
+		<varname>otn_in_flight_order</varname> set to 19 is equivalent to
+		<varname>otn_in_fligh_number</varname> set to 524288).
+	</para>
+	<para>
+		The memory used to keep the nonce information will be the
+		<varname>otn_in_flight_no</varname> divided by 8 (only 1 bit of state
+		is kept per nonce).
+	</para>
+	<para>
+		The default value is 1048576 (1M in-flight nonces, using 
+		128Kb memory).
+	</para>
+	<para>
+		See also:
+			<varname>one_time_nonce</varname> and
+			<varname>nid_pool_no</varname>.
+	</para>
+	<example>
+	    <title>otn_in_flight_no example</title>
+	    <programlisting>
+modparam("auth", "otn_in_flight_no", 8388608)   # 8 Mb (1Mb memory)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="otn_in_flight_order">
+	<title><varname>otn_in_flight_order</varname> (integer)</title>
+	<para>
+		Equivalent to <varname>otn_in_flight_no</varname>, but instead of
+		directly specifying the size, its value is the power at which 2 should
+		be raised (log2(<varname>otn_in_flight_no</varname>)).
+	</para>
+	<para>
+		<varname>otn_in_flight_no</varname> = 
+		2^<varname>otn_in_flight_order</varname>.
+		For more details see <varname>otn_in_flight_order</varname>.
+	</para>
+	<para>
+		The default value is 20 (1M in-flight nonces, using 128Kb memory).
+	</para>
+	<para>
+		See also:
+			<varname>one_time_nonce</varname>,
+			<varname>otn_in_flight_no</varname> and
+			<varname>nid_pool_no</varname>.
+	</para>
+	<example>
+	    <title>otn_in_flight_order example</title>
+	    <programlisting>
+modparam("auth", "otn_in_flight_order", 23)   # 8 Mb (1Mb memory)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="auth.secret">
+	<title><varname>secret</varname> (string)</title>
+	<para>
+	    Default value is randomly generated string.
+	</para>
+	<example>
+	    <title>Setting secret module parameter</title>
+	    <programlisting>
+modparam("auth", "secret", "johndoessecretphrase")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="nonce_expire">
+	<title><varname>nonce_expire</varname> (integer)</title>
+	<para>
+	    Nonces have limited lifetime. After a given period of time nonces
+	    will be considered invalid. This is to protect replay
+	    attacks. Credentials containing a stale nonce will be not
+	    authorized, but the user agent will be challenged again. This time
+	    the challenge will contain <varname>stale</varname> parameter which
+	    will indicate to the client that it doesn't have to disturb user by
+	    asking for username and password, it can recalculate credentials
+	    using existing username and password.
+	</para>
+	<para>
+	    The value is in seconds and default value is 300 seconds.
+	</para>
+	<example>
+	    <title>nonce_expire example</title>
+	    <programlisting>
+modparam("auth", "nonce_expire", 600)   # Set nonce_expire to 600s
+	    </programlisting>
+	</example>
+    </section>
+
+	<section id="nonce_auth_max_drift">
+	<title><varname>nonce_auth_max_drift</varname> (integer)</title>
+	<para>
+		Maximum difference in seconds between a nonce creation time and the
+		current time, if the nonce creation time appears to be in the future.
+	</para>
+	<para>
+		In some cases, like shortly after a system time backward adjustment 
+		or when the current proxy is part of a cluster which is not
+		time-synchronized, it's possible to receive a nonce with creation time
+		in the future. In this case if the difference is greater then
+		<varname>nonce_auth_max_drift</varname> seconds, consider the nonce
+		stale and re-challenge (otherwise after a dramatic time change
+		backwards, it might happen that some previously generated nonces will
+		be valid for too much time).
+	</para>
+	<para>
+		The default value is 3 seconds
+	</para>
+	<para>
+		See also: <varname>nonce_expire</varname>.
+	</para>
+	<example>
+	    <title>nonce_auth_max_drift example</title>
+	    <programlisting>
+modparam("auth", "nonce_auth_max_drift", 1)   # set max drift to 1 s
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="rpid_prefix">
+	<title><varname>rpid_prefix</varname> (string)</title>
+	<para>
+	    Prefix to be added to Remote-Party-ID header field just before the
+	    URI returned from either radius or database.
+	</para>
+	<para>
+	    Default value is "" (empty string).
+	</para>
+	<example>
+	    <title>rpid_prefix</title>
+	    <programlisting>
+<![CDATA[
+modparam("auth", "rpid_prefix", "Whatever <")
+]]>
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="rpid_suffix">
+	<title><varname>rpid_suffix</varname> (string)</title>
+	<para>
+	    Suffix to be added to Remote-Party-ID header field after the URI
+	    returned from either radius or database.
+	</para>
+	<para>
+	    Default value is ";party=calling;id-type=subscriber;screen=yes".
+	</para>
+	<example>
+	    <title>rpid_suffix</title>
+	    <programlisting>
+<![CDATA[
+modparam("auth", "rpid_suffix", "@1.2.3.4>")
+]]>
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 251 - 0
modules_s/auth/nc.c

@@ -0,0 +1,251 @@
+/*
+ * $Id$
+ *
+ * nonce-count (nc) tracking
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-04  initial version (andrei)
+ */
+
+int nc_enabled=0;
+unsigned nc_array_k;    /* array size bits (k in 2^k) */
+unsigned nc_array_size; /* 2^k == 1<<nc_array_bits (in nc_t and not 
+					  unsigned ints)*/
+
+#ifdef USE_NC
+
+#include "nc.h"
+#include "nid.h"
+#include "../../dprint.h"
+#include "../../bit_scan.h"
+#include "../../atomic_ops.h"
+#include "../../ut.h" /* ROUNDUP...*/
+#include "../../mem/shm_mem.h" /* shm_available() */
+#include <stdlib.h> /* random() */
+#include <string.h> /* memset() */
+#include <assert.h>
+
+static unsigned int* nc_array=0;
+
+
+unsigned nc_partition_size; /* array partition == nc_array_size/nc_pool_no*/
+unsigned nc_partition_k;    /* k such that 2^k==nc_partition_size */
+unsigned nc_partition_mask; /* mask for computing the real idx. inside
+							   one partition */
+
+
+/* returns -1 on error, 0 on success */
+int init_nonce_count()
+{
+	unsigned long size;
+	unsigned long max_mem;
+	unsigned orig_array_size;
+
+
+	if (nid_crt==0){
+		BUG("auth: init_nonce_count: nonce index must be "
+				"initialized first (see init_nonce_id())\n");
+		return -1;
+	}
+	orig_array_size=nc_array_size;
+	if (nc_array_k==0){
+		if (nc_array_size==0){
+			nc_array_size=DEFAULT_NC_ARRAY_SIZE;
+		}
+		nc_array_k=bit_scan_reverse32(nc_array_size);
+	}
+	size=1UL<<nc_array_k; /* ROUNDDOWN to 2^nc_array_k */
+	if (size < MIN_NC_ARRAY_SIZE){
+		WARN("auth: nonce-count in.flight nonces is very low (%d),"
+				" consider increasing nc_array_size to at least %d\n",
+				orig_array_size, MIN_NC_ARRAY_SIZE);
+	}
+	if (size > MAX_NC_ARRAY_SIZE){
+		WARN("auth: nonce-count in flight nonces is too high (%d),"
+				" consider decreasing nc_array_size to at least %d\n",
+				orig_array_size, MAX_NC_ARRAY_SIZE);
+	}
+	if (size!=nc_array_size){
+		if (orig_array_size!=0)
+			INFO("auth: nc_array_size rounded down to %ld\n", size);
+		else
+			INFO("auth: nc_array_size set to %ld\n", size);
+	}
+	max_mem=shm_available();
+	if (size*sizeof(nc_t) >= max_mem){
+		ERR("auth: nc_array_size (%ld) is too big for the configured "
+				"amount of shared memory (%ld bytes <= %ld bytes)\n",
+				size, max_mem, size*sizeof(nc_t));
+		return -1;
+	}else if (size*sizeof(nc_t) >= max_mem/2){
+		WARN("auth: the currently configured nc_array_size (%ld)  "
+				"would use more then 50%% of the available shared"
+				" memory(%ld bytes)\n", size, max_mem);
+	}
+	nc_array_size=size;
+	
+	if (nid_pool_no>=nc_array_size){
+		ERR("auth: nid_pool_no (%d) too high for the configured "
+				"nc_array_size (%d)\n", nid_pool_no, nc_array_size);
+		return -1;
+	}
+	nc_partition_size=nc_array_size >> nid_pool_k;
+	nc_partition_k=nc_array_k-nid_pool_k;
+	nc_partition_mask=(1<<nc_partition_k)-1;
+	assert(nc_partition_size == nc_array_size/nid_pool_no);
+	assert(1<<(nc_partition_k+nid_pool_k) == nc_array_size);
+	
+	if ((nid_t)nc_partition_size >= ((nid_t)(-1)/NID_INC)){
+		ERR("auth: nc_array_size too big, try decreasing it or increasing"
+				"the number of pools/partitions\n");
+		return -1;
+	}
+	if (nc_partition_size  < MIN_NC_ARRAY_PARTITION){
+		WARN("auth: nonce-count in-flight nonces very low,"
+				" consider either decreasing nc_pool_no (%d) or "
+				" increasing nc_array_size (%d) such that "
+				"nc_array_size/nid_pool_no >= %d\n",
+				nid_pool_no, orig_array_size, MIN_NC_ARRAY_PARTITION);
+	}
+	
+	
+	/*  array size should be multiple of sizeof(unsigned int) since we
+	 *  access it as an uint array */
+	nc_array=shm_malloc(sizeof(nc_t)*ROUND_INT(nc_array_size));
+	if (nc_array==0){
+		ERR("auth: init_nonce_count: memory allocation failure, consider"
+				" either decreasing nc_array_size of increasing the"
+				" the shared memory ammount\n");
+		goto error;
+	}
+	/* int the nc_array with the max nc value to avoid replay attacks after
+	 * ser restarts (because the nc is already maxed out => no received
+	 * nc will be accepted, until the corresponding cell is reset) */
+	memset(nc_array, 0xff, sizeof(nc_t)*ROUND_INT(nc_array_size));
+	return 0;
+error:
+	destroy_nonce_count();
+	return -1;
+}
+
+
+
+void destroy_nonce_count()
+{
+	if (nc_array){
+		shm_free(nc_array);
+		nc_array=0;
+	}
+}
+
+/* given the nonce id i and pool/partition p, produces an index in the
+ * nc array corresponding to p.
+ * WARNING: the result is  an index in the nc_array converted to nc_t
+ * (unsigned char by default), to get the index of the unsigned int in which
+ * nc is packed, call 
+ */
+#define get_nc_array_raw_idx(i,p) \
+	(((i)&nc_partition_mask)+((p)<<nc_partition_k))
+
+/* get the real array cell corresponding to a certain index
+ * (the index refers to stored nc, but several ncs are stored
+ * inside an int => several nc_t values inside and array cell;
+ * for example if nc_t is uchar => each real array cell holds 4 nc_t).
+ * pos is the "raw" index (e.g. obtained by get_nc_array_raw_idx(i,p)))
+ * and the result is the index of the unsigned int cell in which the pos nc
+ * is packed.
+ */
+#define get_nc_array_uint_idx(pos) \
+	((pos)/(sizeof(unsigned int)/sizeof(nc_t)))
+
+/* get position inside an int nc_array cell for the raw index pos
+ * (pos can be obtained from a nonce id with get_nc_array_raw_idx(i, p),
+ *  see above) */
+#define get_nc_int_pos(pos) \
+	((pos)%(sizeof(unsigned int)/sizeof(nc_t)))
+
+/* returns true if the crt_idx > idx with at least  nc_partition_size
+ * WARNING: NID_INC * nc_partition_size must fit inside an nidx_t*/
+#define  nc_id_check_overflow(id,  pool) \
+	((nid_t)(nid_get((pool))-(id)) >= \
+	 	((nid_t)NID_INC*nc_partition_size))
+
+/* re-init the stored nc for nonce id in pool p */
+nid_t nc_new(nid_t id, unsigned char p)
+{
+	unsigned int i;
+	unsigned  n, r;
+	unsigned int v, new_v;
+	
+	n=get_nc_array_raw_idx(id, p); /* n-th nc_t */
+	i=get_nc_array_uint_idx(n);  /* aray index i, corresponding to n */
+	r=get_nc_int_pos(n);  /* byte/short inside the uint corresponding to n */
+	/* reset corresponding value to 0 */
+	do{
+		v=atomic_get_int(&nc_array[i]);
+		/* new_value = old_int with the corresponding byte or short zeroed*/
+		new_v=v & ~(((1<<(sizeof(nc_t)*8))-1)<< (r*8));
+	}while(atomic_cmpxchg_int((int*)&nc_array[i], v, new_v)!=v);
+	return id;
+}
+
+
+
+/* check if nonce-count nc w/ index i is expected/valid and if so it 
+ * updated the stored nonce-count
+ * returns: 0 - ok, < 0 some error:
+ * NC_INV_POOL      (pool number is invalid/corrupted)
+ * NC_ID_OVERFLOW (crt_id has overflowed with partition size since the
+ *                   id was generated)
+ * NC_TOO_BIG       (nc value got too big and cannot be held anymore)
+ * NC_REPLAY        (nc value is <= the current stored one)
+ */
+enum nc_check_ret nc_check_val(nid_t id, unsigned pool, unsigned int nc)
+{
+	unsigned int i;
+	unsigned n, r;
+	unsigned int v, crt_nc, new_v;
+	
+	if (unlikely(pool>=nid_pool_no))
+		return NC_INV_POOL;
+	if (unlikely(nc_id_check_overflow(id, pool)))
+		return NC_ID_OVERFLOW;
+	if (unlikely(nc>=(1U<<(sizeof(nc_t)*8))))
+		return NC_TOO_BIG;
+	n=get_nc_array_raw_idx(id, pool); /* n-th nc_t */
+	i=get_nc_array_uint_idx(n);  /* aray index i, corresponding to n */
+	r=get_nc_int_pos(n); /* byte/short inside the uint corresponding to n */
+	do{
+		v=atomic_get_int(&nc_array[i]);
+		/* get current (stored) nc value */
+		crt_nc=(v>>(r*8)) & ((1U<<(sizeof(nc_t)*8))-1);
+		if (crt_nc>=nc)
+			return NC_REPLAY;
+		/* set corresponding array cell byte/short to new nc */
+		new_v=(v & ~(((1U<<(sizeof(nc_t)*8))-1)<< (r*8)) )|
+				(nc << (r*8));
+	}while(atomic_cmpxchg_int((int*)&nc_array[i], v, new_v)!=v);
+	return 0;
+}
+
+#endif /* USE_NC */

+ 79 - 0
modules_s/auth/nc.h

@@ -0,0 +1,79 @@
+/*
+ * $Id$
+ *
+ * nonce-count (nc) tracking
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * Defines: 
+ *  USE_NC   - if not defined no NC specific code will be compiled
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-04  initial version (andrei)
+ */
+
+
+#ifndef _nc_h
+#define _nc_h
+
+extern int nc_enabled;
+
+/* instead of storing only the 2^k size we store also k
+ * for faster operations */
+extern unsigned nc_array_k;    /* array size bits (k in 2^k) */
+extern unsigned nc_array_size; /* 2^k == 1<<nc_array_bits */
+
+#ifdef USE_NC
+
+#include "nid.h" /* nid_t */
+#include "../../atomic_ops.h"
+
+
+/* default number of maximum in-flight nonces */
+#define DEFAULT_NC_ARRAY_SIZE 1024*1024 /* uses 1Mb of memory */
+#define MIN_NC_ARRAY_SIZE        102400 /* warn if size < 100k */
+#define MAX_NC_ARRAY_SIZE 1024*1024*1024 /* warn if size > 1Gb */
+
+#define MIN_NC_ARRAY_PARTITION   65536 /* warn if a partition is < 65k */
+
+
+typedef unsigned char nc_t;
+
+int init_nonce_count();
+void destroy_nonce_count();
+
+
+enum nc_check_ret{ 
+	NC_OK=0, NC_INV_POOL=-1, NC_ID_OVERFLOW=-2, NC_TOO_BIG=-3, 
+	NC_REPLAY=-4 
+};
+
+/* check if nonce-count nc w/ index i is expected/valid and record its
+ * value */
+enum nc_check_ret nc_check_val(nid_t i, unsigned pool, unsigned int nc);
+
+/* re-init the stored nc for nonce id in pool pool_no */
+nid_t nc_new(nid_t id, unsigned char pool_no);
+
+#endif /* USE_NC */
+#endif /* _nc_h */
+

+ 104 - 0
modules_s/auth/nid.c

@@ -0,0 +1,104 @@
+/*
+ * $Id$
+ *
+ * nonce id and pool management (stuff common to nonce-count and one
+ * time nonces)
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * Defines: 
+ *  USE_NC, USE_OT_NONCE  - if neither of them defined no code will be 
+ *                          compiled
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-08  initial version (andrei)
+ */
+
+unsigned int nid_pool_no; /* number of index pools, 2^k */
+
+#if defined USE_NC || defined USE_OT_NONCE
+
+#include "nid.h"
+#include "../../dprint.h"
+#include "../../bit_scan.h"
+
+struct pool_index* nid_crt=0;
+
+
+/* instead of storing only the 2^k size we store also k
+ * for faster operations */
+unsigned int nid_pool_k; /* pools no in bits (k in 2^k) */
+unsigned int nid_pool_mask; /* mask for computing the current pool*/
+
+
+
+/* returns -1 on error, 0 on success */
+int init_nonce_id()
+{
+	unsigned pool_no, r;
+	
+	
+	if (nid_crt!=0)
+		return 0; /* already init */
+	if (nid_pool_no==0){
+		nid_pool_no=DEFAULT_NID_POOL_SIZE;
+	}
+	if (nid_pool_no>MAX_NID_POOL_SIZE){
+		WARN("auth: nid_pool_no too big, truncating to %d\n",
+				MAX_NID_POOL_SIZE);
+		nid_pool_no=MAX_NID_POOL_SIZE;
+	}
+	nid_pool_k=bit_scan_reverse32(nid_pool_no);
+	nid_pool_mask=(1<<nid_pool_k)-1;
+	pool_no=1UL<<nid_pool_k; /* ROUNDDOWN to 2^k */
+	if (pool_no!=nid_pool_no){
+		INFO("auth: nid_pool_no rounded down to %d\n", pool_no);
+	}
+	nid_pool_no=pool_no;
+	
+	nid_crt=shm_malloc(sizeof(*nid_crt)*nid_pool_no);
+	if (nid_crt==0){
+		ERR("auth: init_nonce_id: memory allocation failure\n");
+		return -1;
+	}
+	/*  init nc_crt_id with random values */
+	for (r=0; r<nid_pool_no; r++)
+		atomic_set(&nid_crt[r].id, random());
+	return 0;
+/*
+error:
+	destroy_nonce_id();
+	return -1;
+*/
+}
+
+
+
+void destroy_nonce_id()
+{
+	if (nid_crt){
+		shm_free(nid_crt);
+		nid_crt=0;
+	}
+}
+
+#endif  /*if  defined USE_NC || defined USE_OT_NONCE */

+ 93 - 0
modules_s/auth/nid.h

@@ -0,0 +1,93 @@
+/*
+ * $Id$
+ *
+ * nonce id and pool management (stuff common to nonce-count and one
+ * time nonces)
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * Defines: 
+ *  USE_NC, USE_OT_NONCE  - if neither of them defined no code will be 
+ *                          compiled
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-08  initial version (andrei)
+ */
+
+#ifndef _nid_h
+#define _nid_h
+
+extern unsigned nid_pool_no; /* number of index pools */
+
+#if defined USE_NC || defined USE_OT_NONCE
+
+#include "../../atomic_ops.h"
+#include "../../pt.h" /* process_no */
+
+/* id incremenet, to avoid cacheline ping-pong and cover all the
+ * array locations it should be a number prime with the array size and
+ * bigger then the cacheline. Since this is used also for onetime nonces
+ * => NID_INC/8 > CACHELINE
+ * This number also limit the maximum pool/partition size, since the
+ * id overlfow checks check if crt_id - nonce_id >= partition_size*NID_INC
+ * => maximum partition size is (nid_t)(-1)/NID_INC*/
+#define NID_INC 257
+
+#define DEFAULT_NID_POOL_SIZE 1
+#define MAX_NID_POOL_SIZE    64 /* max. 6 bits used for the pool no*/
+
+#define CACHELINE_SIZE 256 /* more then most real-word cachelines */
+
+/* if larger tables are needed (see NID_INC comments above), consider
+ * switching to unsigned long long */
+typedef unsigned int nid_t;
+
+struct pool_index{
+	atomic_t id;
+	char pad[CACHELINE_SIZE-sizeof(atomic_t)];/* padding to cacheline size */
+};
+
+extern struct pool_index* nid_crt;
+
+
+/* instead of storing only the 2^k size we store also k
+ * for faster operations */
+extern unsigned int nid_pool_k; /* pools no in bits (k in 2^k) */
+extern unsigned int nid_pool_mask; /* mask for computing the current pool*/
+
+int init_nonce_id();
+void destroy_nonce_id();
+
+
+/* get current index in pool p */
+#define nid_get(p) \
+	atomic_get(&nid_crt[(p)].id)
+
+/* get pool for the current process */
+#define nid_get_pool()  (process_no & nid_pool_mask)
+
+/* inc the specified index and return its new value */
+#define nid_inc(pool) \
+	((nid_t)atomic_add(&nid_crt[(pool)].id, NID_INC))
+
+#endif /* #if defined USE_NC || defined USE_OT_NONCE */
+#endif /* _nid_h */

+ 455 - 0
modules_s/auth/nonce.c

@@ -0,0 +1,455 @@
+/*
+ * $Id$
+ *
+ * Nonce related 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ *            ...
+ * 2007-10-19 auth extra checks: longer nonces that include selected message
+ *            parts to protect against various reply attacks without keeping
+ *            state (andrei)
+ * 2008-07-01 switched to base64 for nonces; check staleness in check_nonce
+ *            (andrei)
+ * 2008-07-04 nonce-count support (andrei)
+ */
+
+
+#include <time.h>
+#include <string.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include "../../compiler_opt.h"
+#include "../../md5global.h"
+#include "../../md5.h"
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_from.h"
+#include "../../ip_addr.h"
+#include "nonce.h"
+#include "../../globals.h"
+#include "../../ser_time.h"
+#include <assert.h>
+#ifdef USE_NC
+#include "nc.h"
+#endif
+#ifdef USE_OT_NONCE
+#include "ot_nonce.h"
+#endif
+
+
+int auth_checks_reg = 0;
+int auth_checks_ood = 0;
+int auth_checks_ind = 0;
+
+/* maximum time drift accepted for the nonce creation time
+ * (e.g. nonce generated by another proxy in the same cluster with the
+ * clock slightly in the future)
+ */
+unsigned int nonce_auth_max_drift = 3; /* in s */
+
+/** Select extra check configuration based on request type.
+ * This function determines which configuration variable for
+ * extra authentication checks is to be used based on the
+ * type of the request. It returns the value of auth_checks_reg
+ * for REGISTER requests, the value auth_checks_ind for requests
+ * that contain valid To tag and the value of auth_checks_ood
+ * otherwise.
+ */
+int get_auth_checks(struct sip_msg* msg)
+{
+	str tag;
+
+	if (msg == NULL) return 0;
+
+	if (msg->REQ_METHOD == METHOD_REGISTER) {
+		return auth_checks_reg;
+	}
+		
+	if (!msg->to && parse_headers(msg, HDR_TO_F, 0) == -1) {
+		DBG("auth: Error while parsing To header field\n");
+		return auth_checks_ood;
+	}
+	if (msg->to) {
+		tag = get_to(msg)->tag_value;
+		if (tag.s && tag.len > 0) return auth_checks_ind;
+	}
+	return auth_checks_ood;
+}
+
+
+
+/* takes a pre-filled bin_nonce union (see BIN_NONCE_PREPARE), fills the
+ * MD5s and returns the length of the binary nonce (cannot return error).
+ * See calc_nonce below for more details.*/
+inline static int calc_bin_nonce_md5(union bin_nonce* b_nonce, int cfg,
+										str* secret1, str* secret2,
+										struct sip_msg* msg)
+{
+	MD5_CTX ctx;
+	
+	str* s;
+	int len;
+
+	MD5Init(&ctx);
+	
+	MD5Update(&ctx, &b_nonce->raw[0], 4 + 4);
+	if (cfg && msg){
+		/* auth extra checks => 2 md5s */
+		len = 4 + 4 + 16 + 16;
+#if defined USE_NC  || defined USE_OT_NONCE
+		if (b_nonce->n.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)){
+			/* if extra auth checks enabled, nid & pf are after the 2nd md5 */
+			MD5Update(&ctx, (unsigned char*)&b_nonce->n.nid_i, 
+							nonce_nid_extra_size);
+			len+=nonce_nid_extra_size;
+		}
+#endif /* USE_NC || USE_OT_NONCE */
+		MD5Update(&ctx, secret1->s, secret1->len);
+		MD5Final(&b_nonce->n.md5_1[0], &ctx);
+		/* second MD5(auth_extra_checks) */
+		MD5Init(&ctx);
+		if (cfg & AUTH_CHECK_FULL_URI) {
+			s = GET_RURI(msg);
+			MD5Update(&ctx, s->s, s->len);
+		}
+		if ((cfg & AUTH_CHECK_CALLID) && 
+			!(parse_headers(msg, HDR_CALLID_F, 0) < 0 || msg->callid == 0)) {
+			MD5Update(&ctx, msg->callid->body.s, msg->callid->body.len);
+		}
+		if ((cfg & AUTH_CHECK_FROMTAG) &&
+			!(parse_headers(msg, HDR_FROM_F, 0) < 0 || msg->from == 0)) {
+			MD5Update(&ctx, get_from(msg)->tag_value.s, 
+					  get_from(msg)->tag_value.len);
+		}
+		if (cfg & AUTH_CHECK_SRC_IP) {
+			MD5Update(&ctx, msg->rcv.src_ip.u.addr, msg->rcv.src_ip.len);
+		}
+		MD5Update(&ctx, secret2->s, secret2->len);
+		MD5Final(&b_nonce->n.md5_2[0], &ctx);
+	}else{
+		/* no extra checks => only one md5 */
+		len = 4 + 4 + 16;
+#if defined USE_NC || USE_OT_NONCE
+		if (b_nonce->n_small.nid_pf & (NF_VALID_NC_ID | NF_VALID_OT_ID)){
+			/* if extra auth checks are not enabled, nid & pf are after the
+			 *  1st md5 */
+			MD5Update(&ctx, (unsigned char*)&b_nonce->n_small.nid_i,
+							nonce_nid_extra_size);
+			len+=nonce_nid_extra_size;
+		}
+#endif /* USE_NC  || USE_OT_NONCE*/
+		MD5Update(&ctx, secret1->s, secret1->len);
+		MD5Final(&b_nonce->n.md5_1[0], &ctx);
+	}
+	
+	return len;
+}
+
+
+
+/** Calculates the nonce string for RFC2617 digest authentication.
+ * This function creates the nonce string as it will be sent to the
+ * user agent in digest challenge. The format of the nonce string
+ * depends on the value of three module parameters, auth_checks_register,
+ * auth_checks_no_dlg, and auth_checks_in_dlg. These module parameters
+ * control the amount of information from the SIP requst that will be
+ * stored in the nonce string for verification purposes.
+ *
+ * If all three parameters contain zero then the nonce string consists
+ * of time in seconds since 1.1. 1970 and a secret phrase:
+ * <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret)
+ * If any of the parameters is not zero (some optional checks are enabled
+ * then the nonce string will also contain MD5 hash of selected parts
+ * of the SIP request:
+ * <expire_time> <valid_since> MD5(<expire_time>, <valid_since>, secret1) MD5(<extra_checks>, secret2)
+ * @param nonce  Pointer to a buffer of *nonce_len. It must have enough
+ *               space to hold the nonce. MAX_NONCE_LEN should be always 
+ *               safe.
+ * @param nonce_len A value/result parameter. Initially it contains the
+ *                  nonce buffer length. If the length is too small, it 
+ *                  will be set to the needed length and the function will 
+ *                  return error immediately. After a succesfull call it will 
+ *                  contain the size of nonce written into the buffer, 
+ *                  without the terminating 0.
+ * @param cfg This is the value of one of the tree module parameters that
+ *            control which optional checks are enabled/disabled and which
+ *            parts of the message will be included in the nonce string.
+ * @param since Time when nonce was created, i.e. nonce is valid since <valid_since> up to <expires>
+ * @param expires Time in seconds after which the nonce will be considered 
+ *                stale.
+ * @param n_id    Nounce count and/or one-time nonce index value
+ *                (32 bit counter)
+ * @param pf      First 2 bits are flags, the rest is the index pool number
+ *                 used if nonce counts or one-time nonces are enabled.
+ *                The possible flags values are: NF_VALID_NC_ID which means
+ *                the nonce-count support is enabled and NF_VALID_OT_ID 
+ *                which means the one-time nonces support is enabled.
+ *                The pool number can be obtained by and-ing with
+ *                NF_POOL_NO_MASK
+ * @param secret1 A secret used for the nonce expires integrity check:
+ *                MD5(<expire_time>, <valid_since>, secret1).
+ * @param secret2 A secret used for integrity check of the message parts 
+ *                selected by auth_extra_checks (if any):
+ *                MD5(<msg_parts(auth_extra_checks)>, secret2).
+ * @param msg     The message for which the nonce is computed. If 
+ *                auth_extra_checks is set, the MD5 of some fields of the
+ *                message will be included in the  generated nonce.
+ * @return 0 on success and -1 on error
+ */
+int calc_nonce(char* nonce, int *nonce_len, int cfg, int since, int expires,
+#if defined USE_NC || defined USE_OT_NONCE
+					unsigned int n_id, unsigned char pf,
+#endif /* USE_NC || USE_OT_NONCE */
+					str* secret1, str* secret2,
+					struct sip_msg* msg)
+{
+	union bin_nonce b_nonce;
+	int len;
+	if (unlikely(*nonce_len < MAX_NONCE_LEN)) {
+		len=get_nonce_len(cfg, pf & NF_VALID_NC_ID);
+		if (unlikely(*nonce_len<len)){
+			*nonce_len=len;
+			return -1;
+		}
+	}
+
+	BIN_NONCE_PREPARE(&b_nonce, expires, since, n_id, pf, cfg, msg);
+	len=calc_bin_nonce_md5(&b_nonce, cfg, secret1, secret2, msg);
+	*nonce_len=base64_enc(&b_nonce.raw[0], len, 
+							(unsigned char*)nonce, *nonce_len);
+	assert(*nonce_len>=0); /*FIXME*/
+	return 0;
+}
+
+
+
+/** Returns the expire time of the nonce string.
+ * This function returns the absolute expire time
+ * extracted from the nonce string in the parameter.
+ * @param bn is a valid pointer to a union bin_nonce (decoded nonce)
+ * @return Absolute time when the nonce string expires.
+ */
+
+#define get_bin_nonce_expire(bn) ((time_t)ntohl((bn)->n.expire))
+
+/** Returns the valid_since time of the nonce string.
+ * This function returns the absolute time
+ * extracted from the nonce string in the parameter.
+ * @param bn is a valid pointer to a union bin_nonce (decoded nonce)
+ * @return Absolute time when the nonce string was created.
+ */
+#define get_bin_nonce_since(bn) ((time_t)ntohl((bn)->n.since))
+
+
+
+/** Checks if nonce is stale.
+ * This function checks if a nonce given to it in the parameter is stale. 
+ * A nonce is stale if the expire time stored in the nonce is in the past.
+ * @param b_nonce a pointer to a union bin_nonce to be checked.
+ * @return 1 the nonce is stale, 0 the nonce is not stale.
+ */
+#define is_bin_nonce_stale(b_nonce, t) (get_bin_nonce_expire(b_nonce) < (t))
+
+
+
+
+/** Check whether the nonce returned by UA is valid.
+ * This function checks whether the nonce string returned by UA
+ * in digest response is valid. The function checks if the nonce
+ * string hasn't expired, it verifies the secret stored in the nonce
+ * string with the secret configured on the server. If any of the
+ * optional extra integrity checks are enabled then it also verifies
+ * whether the corresponding parts in the new SIP requests are same.
+ * @param nonce A nonce string to be verified.
+ * @param secret1 A secret used for the nonce expires integrity check:
+ *                MD5(<expire_time>,, secret1).
+ * @param secret2 A secret used for integrity check of the message parts 
+ *                selected by auth_extra_checks (if any):
+ *                MD5(<msg_parts(auth_extra_checks)>, secret2).
+ * @param msg The message which contains the nonce being verified. 
+ * @return 0 - success (the nonce was not tampered with and if 
+ *             auth_extra_checks are enabled - the selected message fields
+ *             have not changes from the time the nonce was generated)
+ *         -1 - invalid nonce
+ *          1 - nonce length too small
+ *          2 - no match
+ *          3 - nonce expires ok, but the auth_extra checks failed
+ *          4 - stale
+ *          5 - invalid nc value (not an unsigned int)
+ */
+int check_nonce(auth_body_t* auth, str* secret1, str* secret2,
+					struct sip_msg* msg)
+{
+	str * nonce;
+	int since, b_nonce2_len, b_nonce_len, cfg;
+	union bin_nonce b_nonce;
+	union bin_nonce b_nonce2;
+	time_t t;
+#if defined USE_NC || defined USE_OT_NONCE
+	unsigned int n_id;
+	unsigned char pf;
+#endif /* USE_NC || USE_OT_NONCE */
+#ifdef USE_NC
+	unsigned int nc;
+#endif
+
+	cfg = get_auth_checks(msg);
+	nonce=&auth->digest.nonce;
+
+	if (unlikely(nonce->s == 0)) {
+		return -1;  /* Invalid nonce */
+	}
+	
+	if (unlikely(nonce->len<MIN_NONCE_LEN)){ 
+		return 1; /* length musth be >= then minimum length */
+	}
+	
+#if defined USE_NC || defined USE_OT_NONCE
+	/* clear all possible nonce flags positions prior to decoding,
+	 * to make sure they can be used even if the nonce is shorter */
+	b_nonce.n.nid_pf=0;
+	b_nonce.n_small.nid_pf=0;
+#endif /* USE_NC || USE_OT_NONCE */
+	
+	/* decode nonce */
+	b_nonce_len=base64_dec((unsigned char*)nonce->s, nonce->len,
+							&b_nonce.raw[0], sizeof(b_nonce));
+	if (unlikely(b_nonce_len < MIN_BIN_NONCE_LEN)){
+		DBG("auth: check_nonce: base64_dec failed\n");
+		return -1; /* error decoding the nonce (invalid nonce since we checked
+		              the len of the base64 enc. nonce above)*/
+	}
+	
+	since = get_bin_nonce_since(&b_nonce);
+	if (unlikely(since < up_since)) {
+		/* if valid_since time is time pointing before ser was started 
+		 * then we consider nonce as stalled. 
+		   It may be the nonce generated by previous ser instance having
+		   different length (for example because of different auth.
+		   checks)..  Therefore we force credentials to be rebuilt by UAC
+		   without prompting for password */
+		return 4;
+	}
+	t=ser_time(0);
+	if (unlikely((since > t) && ((since-t) > nonce_auth_max_drift) )){
+		/* the nonce comes from the future, either because of an external
+		 * time adjustment, or because it was generated by another host
+		 * which has the time slightly unsynchronized */
+		return 4; /* consider it stale */
+	}
+	b_nonce2=b_nonce; /*pre-fill it with the values from the received nonce*/
+	b_nonce2.n.expire=b_nonce.n.expire;
+	b_nonce2.n.since=b_nonce.n.since;
+#if defined USE_NC || defined USE_OT_NONCE
+	if (cfg){
+		b_nonce2.n.nid_i=b_nonce.n.nid_i;
+		b_nonce2.n.nid_pf=b_nonce.n.nid_pf;
+		pf=b_nonce.n.nid_pf;
+		n_id=ntohl(b_nonce.n.nid_i);
+	}else{
+		b_nonce2.n_small.nid_i=b_nonce.n_small.nid_i;
+		b_nonce2.n_small.nid_pf=b_nonce.n_small.nid_pf;
+		pf=b_nonce.n_small.nid_pf;
+		n_id=ntohl(b_nonce.n_small.nid_i);
+	}
+#ifdef UE_NC
+	if (unlikely(nc_enabled && !(pf & NF_VALID_NC_ID)) )
+		/* nounce count enabled, but nonce is not marked as nonce count ready
+		 * or is too short => either an old nonce (should
+		 * be caught by the ser start time  check) or truncated nonce  */
+		return 4; /* return stale for now */
+	}
+#endif /* USE_NC */
+#ifdef USE_OT_NONCE
+	if (unlikely(otn_enabled && !(pf & NF_VALID_OT_ID))){
+		/* same as above for one-time-nonce */
+		return 4; /* return stale for now */
+	}
+#endif  /* USE_OT_NONCE */
+	/* don't check if we got the expected length, if the length is smaller 
+	 * then expected then  the md5 check below will fail (since the nid 
+	 * members of the bin_nonce struct will be 0); if the length is bigger
+	 * and it was not caught by the base64_dec above, and the md5 matches,
+	 * we ignore the extra stuff */
+#endif /* USE_NC || USE_OT_NONCE */
+	b_nonce2_len=calc_bin_nonce_md5(&b_nonce2, cfg, secret1, secret2, msg);
+	if (!memcmp(&b_nonce.n.md5_1[0], &b_nonce2.n.md5_1[0], 16)) {
+#ifdef USE_NC
+		/* if nounce-count checks enabled & auth. headers has nc */
+		if (nc_enabled && (pf & NF_VALID_NC_ID) && auth->digest.nc.s &&
+				auth->digest.nc.len){
+			if (str2int(&auth->digest.nc, &nc)!=0){
+				/* error, bad nc */
+				ERR("FIXME:check_nonce: bad nc value %.*s\n",
+						auth->digest.nc.len, auth->digest.nc.s);
+				return 5; /* invalid nc */
+			}
+			switch(nc_check_val(n_id, pf & NF_POOL_NO_MASK, nc)){
+				case NC_OK:
+					/* don't perform extra checks or one-time nonce checks
+					 * anymore, if we have nc */
+					goto check_stale;
+				case NC_ID_OVERFLOW: /* id too old => stale */
+				case NC_TOO_BIG:  /* nc overlfow => force re-auth => stale */
+				case NC_REPLAY:    /* nc seen before => re-auth => stale */
+				case NC_INV_POOL: /* pool-no too big, maybe ser restart?*/
+					return 4; /* stale */
+			}
+		}
+#endif /* USE_NC */
+#ifdef USE_OT_NONCE
+		if (otn_enabled && (pf & NF_VALID_OT_ID)){
+			switch(otn_check_id(n_id, pf & NF_POOL_NO_MASK)){
+				case OTN_OK:
+					/* continue in case auth extra checks are enabled */
+					break;
+				case OTN_ID_OVERFLOW:
+				case OTN_INV_POOL:
+				case OTN_REPLAY:
+					return 4; /* stale */
+			}
+		}
+#endif
+		if (cfg) {
+			if (unlikely(b_nonce_len != b_nonce2_len))
+				return 2; /* someone truncated our nonce? */
+			if (memcmp(&b_nonce.n.md5_2[0], &b_nonce2.n.md5_2[0], 16))
+				return 3; /* auth_extra_checks failed */
+		}
+#ifdef USE_NC
+check_stale:
+#endif /* USE_NC */
+		if (unlikely(is_bin_nonce_stale(&b_nonce, t)))
+			return 4;
+		return 0;
+	}
+	
+	return 2;
+}
+

+ 232 - 0
modules_s/auth/nonce.h

@@ -0,0 +1,232 @@
+/*
+ * $Id$
+ *
+ * Nonce related 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef NONCE_H
+#define NONCE_H
+
+#include "../../parser/msg_parser.h"
+#include "../../parser/digest/digest.h"
+#include "../../str.h"
+#include "../../basex.h"
+#include <time.h>
+
+
+/* auth_extra_checks flags */
+
+#define AUTH_CHECK_FULL_URI (1 << 0)
+#define AUTH_CHECK_CALLID   (1 << 1)
+#define AUTH_CHECK_FROMTAG  (1 << 2)
+#define AUTH_CHECK_SRC_IP   (1 << 3)
+/* nonce format:
+ * base64(bin_nonce)
+ * bin_nonce =  expire_timestamp(4) | since_timestamp(4) | \
+ *   MD5(expire | since | secret1) (16)  \
+ *   [|   MD5(info(auth_extra_checks) | secret2) (16) ]
+ * if nonce-count or one-time nonces are enabled, the format changes to:
+ *  bin_nonce = 
+ * bin_nonce =  expire_timestamp(4) | since_timestamp(4) | 
+ *  MD5(expire | since | nid | pf | secret1) [ | MD5... ] | nid(4) | pf(1) 
+ * where pf is 1 byte, first 2 bits are flags, and the other 6 are 
+ * the pool no:
+ * bit7 : on => nid & pool are valid for nonce-count
+ * bit6 : on => nid & pool are valid for one-time nonce
+ */
+#if defined USE_NC || defined USE_OT_NONCE
+#define NF_VALID_NC_ID 128 
+#define NF_VALID_OT_ID  64 
+
+#define NF_POOL_NO_MASK  63
+#endif
+
+#if defined USE_NC || defined USE_OT_NONCE
+#define nonce_nid_extra_size (sizeof(unsigned int)+sizeof(unsigned char))
+
+#else /* USE_NC || USE_OT_NONCE*/
+
+#define nonce_nid_extra_size 0
+#endif /* USE_NC || USE_OT_NONCE */
+
+/* nonce structure, complete (maximum size) */
+struct bin_nonce_str{
+	int expire;
+	int since;
+	char md5_1[16];
+	char md5_2[16]; /* optional */
+#if defined USE_NC || defined USE_OT_NONCE
+	unsigned int nid_i;
+	unsigned char nid_pf; /* pool no & flags:
+						 	  bits 7, 6 = flags, bits 5..0 pool no*/ 
+#endif /* USE_NC || USE_OT_NONCE */
+};
+
+/* nonce structure, small version  (no auth_extra_checks secondary md5) */
+struct bin_nonce_small_str{
+	int expire;
+	int since;
+	char md5_1[16];
+#if defined USE_NC || defined USE_OT_NONCE
+	unsigned int nid_i;
+	unsigned char nid_pf; /* pool no & flags:
+							  bits 7, 6 = flags, bits 5..0 pool no*/ 
+#endif /* USE_NC || USE_OT_NONCE */
+};
+
+/* nonce union */
+union bin_nonce{
+	struct bin_nonce_str n;
+	struct bin_nonce_small_str n_small;
+	unsigned char raw[sizeof(struct bin_nonce_str)];
+};
+
+
+/* fill an union bin_nonce*, before computing the md5 */
+#define BIN_NONCE_PREPARE_COMMON(bn, expire_val, since_val) \
+	do{\
+		(bn)->n.expire=htonl(expire_val); \
+		(bn)->n.since=htonl(since_val); \
+	}while(0)
+
+#if defined USE_NC || defined USE_OT_NONCE
+#define BIN_NONCE_PREPARE(bn, expire_v, since_v, id_v, pf_v, cfg, msg)  \
+	do{ \
+		BIN_NONCE_PREPARE_COMMON(bn, expire_v, since_v); \
+		if (cfg && msg){ \
+			(bn)->n.nid_i=htonl(id_v); \
+			(bn)->n.nid_pf=(pf_v); \
+		}else{ \
+			(bn)->n_small.nid_i=htonl(id_v); \
+			(bn)->n_small.nid_pf=(pf_v); \
+		} \
+	}while(0)
+#else /* USE_NC || USE_OT_NONCE */
+#define BIN_NONCE_PREPARE(bn, expire, since, id, pf, cfg, msg)  \
+	BIN_NONCE_PREPARE_COMMON(bn, expire, since)
+#endif /* USE_NC || USE_OT_NONCE */
+
+
+
+/* maximum nonce length in binary form (not converted to base64/hex):
+ * expires_t | since_t | MD5(expires_t | since_t | s1) | \
+ *   MD5(info(auth_extra_checks, s2)   => 4  + 4 + 16 + 16 = 40 bytes
+ * or if nc_enabled:
+ * expires_t | since_t | MD5...| MD5... | nonce_id | flag+pool_no(1 byte)
+ * => 4 + 4 + 16 + 16 + 4 + 1 = 45 bytes
+ * (sizeof(struct) cannot be used safely since structs can be padded
+ *  by the compiler if not defined with special attrs)
+ */
+#if defined USE_NC || defined USE_OT_NONCE
+#define MAX_BIN_NONCE_LEN (4 + 4 + 16 + 16 + 4 +1)
+#define MAX_NOCFG_BIN_NONCE_LEN (4 + 4 + 16 + 4 + 1)
+
+#define get_bin_nonce_len(cfg, nid_enabled) \
+	( ( (cfg)?MAX_BIN_NONCE_LEN:MAX_NOCFG_BIN_NONCE_LEN ) - \
+		(!(nid_enabled))*nonce_nid_extra_size )
+
+#else /* USE_NC || USE_OT_NONCE */
+#define MAX_BIN_NONCE_LEN (4 + 4 + 16 + 16)
+#define MAX_NOCFG_BIN_NONCE_LEN (4 + 4 + 16)
+
+#define get_bin_nonce_len(cfg, nid_enabled) \
+		( (cfg)?MAX_BIN_NONCE_LEN:MAX_NOCFG_BIN_NONCE_LEN )
+
+#endif /* USE_NC || USE_OT_NONCE */
+
+/* minimum nonce length in binary form (not converted to base64/hex):
+ * expires_t | since_t | MD5(expires_t | since_t | s1) => 4 + 4 + 16 = 24 
+ * If nc_enabled the nonce will be bigger:
+ * expires_t | since_t | MD5... | nonce_id | flag+pool_no(1 byte) 
+ * => 4 + 4 + 16 + 4 + 1 = 29, but we always return the minimum */
+#define MIN_BIN_NONCE_LEN (4 + 4 + 16)
+
+
+/*
+ * Maximum length of nonce string in bytes
+ * nonce = expires_TIMESTAMP[4 chars] since_TIMESTAMP[4 chars] \
+ * MD5SUM(expires_TIMESTAMP, since_TIMESTAMP, SECRET1)[16 chars] \
+ * MD5SUM(info(auth_extra_checks), SECRET2)[16 chars] \
+ * [nid [4 chars]  pflags[1 char]]
+ */
+#define MAX_NONCE_LEN  base64_enc_len(MAX_BIN_NONCE_LEN)
+/*
+ * Minimum length of the nonce string
+ * nonce = expires_TIMESTAMP[4 chars] since_TIMESTAMP[4 chars] 
+ * MD5SUM(expires_TIMESTAMP, since_TIMESTAMP, SECRET1)[16 chars]
+ */
+#define MIN_NONCE_LEN base64_enc_len(MIN_BIN_NONCE_LEN)
+
+/*
+ * length of nonces when no auth extra checks (cfg==0) are enabled
+ */
+#define MAX_NOCFG_NONCE_LEN base64_enc_len(MAX_NOCFG_BIN_NONCE_LEN)
+
+
+/* Extra authentication checks for REGISTER messages */
+extern int auth_checks_reg;
+/* Extra authentication checks for out-of-dialog requests */
+extern int auth_checks_ood;
+/* Extra authentication checks for in-dialog requests */
+extern int auth_checks_ind;
+
+/* maximum time drift accepted for the nonce creation time
+ * (e.g. nonce generated by another proxy in the same cluster with the
+ * clock slightly in the future)
+ */
+extern unsigned int  nonce_auth_max_drift;
+
+
+int get_auth_checks(struct sip_msg* msg);
+
+
+/*
+ * get the configured nonce len
+ */
+#define get_nonce_len(cfg, nid_enabled) \
+		base64_enc_len(get_bin_nonce_len(cfg, nid_enabled))
+
+
+/*
+ * Calculate nonce value
+ */
+int calc_nonce(char* nonce, int* nonce_len, int cfg, int since, int expires,
+#if defined USE_NC || defined USE_OT_NONCE
+				unsigned int n_id, unsigned char pf,
+#endif /* USE_NC || USE_OT_NONCE */
+				str* secret1, str* secret2, struct sip_msg* msg);
+
+
+/*
+ * Check nonce value received from UA
+ */
+int check_nonce(auth_body_t* auth, str* secret1, str* secret2,
+					struct sip_msg* msg);
+
+
+
+#endif /* NONCE_H */

+ 239 - 0
modules_s/auth/ot_nonce.c

@@ -0,0 +1,239 @@
+/*
+ * $Id$
+ *
+ * one-time nonce support
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-10  initial version (andrei)
+ */
+
+int otn_enabled=0;
+unsigned otn_in_flight_k;  /* max in-flight nonces order (k in 2^k) */
+unsigned otn_in_flight_no; /* 2^k == 1<<oth_in_flight_k */
+
+#ifdef USE_OT_NONCE
+
+#include "ot_nonce.h"
+#include "nid.h"
+#include "../../dprint.h"
+#include "../../bit_scan.h"
+#include "../../atomic_ops.h"
+#include "../../ut.h" /* ROUNDUP...*/
+#include "../../mem/shm_mem.h" /* shm_available() */
+#include <stdlib.h> /* random() */
+#include <string.h> /* memset() */
+#include <assert.h>
+
+static otn_cell_t * otn_array=0;
+
+
+unsigned otn_partition_size; /* partition==otn_in_flight_no/nid_pool_no*/
+unsigned otn_partition_k;    /* k such that 2^k==otn_partition_size */
+unsigned otn_partition_mask; /* mask for computing the real idx. inside
+							   one partition */
+
+
+/* returns -1 on error, 0 on success */
+int init_ot_nonce()
+{
+	unsigned long size;
+	unsigned long max_mem;
+	unsigned orig_array_size;
+
+
+	if (nid_crt==0){
+		BUG("auth: init_ot_nonce: nonce index must be "
+				"initialized first (see init_nonce_id())\n");
+		return -1;
+	}
+	orig_array_size=otn_in_flight_no;
+	if (otn_in_flight_k==0){
+		if (otn_in_flight_no==0){
+			otn_in_flight_no=DEFAULT_OTN_IN_FLIGHT;
+		}
+		otn_in_flight_k=bit_scan_reverse32(otn_in_flight_no);
+	}
+	size=1UL<<otn_in_flight_k; /* ROUNDDOWN to 2^otn_in_flight_k */
+	if (size < MIN_OTN_IN_FLIGHT){
+		WARN("auth: one-time-nonce maximum in-flight nonces is very low (%d),"
+				" consider increasing otn_in_flight_no to at least %d\n",
+				orig_array_size, MIN_OTN_IN_FLIGHT);
+	}
+	if (size > MAX_OTN_IN_FLIGHT){
+		WARN("auth: one-time-nonce maximum in-flight nonces is too high (%d),"
+				" consider decreasing otn_in_flight_no to at least %d\n",
+				orig_array_size, MAX_OTN_IN_FLIGHT);
+	}
+	if (size!=otn_in_flight_no){
+		if (orig_array_size!=0)
+			INFO("auth: otn_in_flight_no rounded down to %ld\n", size);
+		else
+			INFO("auth: otn_in_flight_no set to %ld\n", size);
+	}
+	max_mem=shm_available();
+	if (size/8 >= max_mem){
+		ERR("auth: otn_in_flight_no (%ld) is too big for the configured "
+				"amount of shared memory (%ld bytes)\n", size, max_mem);
+		return -1;
+	}else if (size/8 >= max_mem/2){
+		WARN("auth: the currently configured otn_in_flight_no (%ld)  "
+				"would use more then 50%% of the available shared"
+				" memory(%ld bytes)\n", size, max_mem);
+	}
+	otn_in_flight_no=size;
+	
+	if (nid_pool_no>=otn_in_flight_no/(8*sizeof(otn_cell_t))){
+		ERR("auth: nid_pool_no (%d) too high for the configured "
+				"otn_in_flight_no (%d)\n", nid_pool_no, otn_in_flight_no);
+		return -1;
+	}
+	otn_partition_size=otn_in_flight_no >> nid_pool_k;
+	otn_partition_k=otn_in_flight_k-nid_pool_k;
+	otn_partition_mask=(1<<otn_partition_k)-1;
+	assert(otn_partition_size == otn_in_flight_no/nid_pool_no);
+	assert(1<<(otn_partition_k+nid_pool_k) == otn_in_flight_no);
+	
+	if ((nid_t)otn_partition_size >= ((nid_t)(-1)/NID_INC)){
+		ERR("auth: otn_in_flight_no too big, try decreasing it or increasing"
+				"the number of pools/partitions, such that "
+				"otn_in_flight_no/nid_pool_no < %d\n",
+				(unsigned int)((nid_t)(-1)/NID_INC));
+		return -1;
+	}
+	if (otn_partition_size  < MIN_OTN_PARTITION){
+		WARN("auth: one-time-nonces in-flight nonces very low,"
+				" consider either decreasing nid_pool_no (%d) or "
+				" increasing otn_array_size (%d) such that "
+				"otn_array_size/nid_pool_no >= %d\n",
+				nid_pool_no, orig_array_size, MIN_OTN_PARTITION);
+	}
+	
+	
+	/*  array size should be multiple of sizeof(otn_cell_t) since we
+	 *  access it as an otn_cell_t array */
+	otn_array=shm_malloc(ROUND2TYPE((otn_in_flight_no+7)/8, otn_cell_t));
+	if (otn_array==0){
+		ERR("auth: init_ot_nonce: memory allocation failure, consider"
+				" either decreasing otn_in_flight_no of increasing the"
+				" the shared memory ammount\n");
+		goto error;
+	}
+	/* init the otn_array with 1 for each bit, to avoid replay attacks after
+	 * ser restarts ) */
+	memset(otn_array, 0xff, ROUND2TYPE((otn_in_flight_no+7)/8, otn_cell_t));
+	return 0;
+error:
+	destroy_ot_nonce();
+	return -1;
+}
+
+
+
+void destroy_ot_nonce()
+{
+	if (otn_array){
+		shm_free(otn_array);
+		otn_array=0;
+	}
+}
+
+/* given the nonce id i and pool/partition p, produces a bit index in the
+ * array  partition corresponding to p.
+ * WARNING: the result is the _bit_ index and not the array cell index
+ */
+#define get_otn_array_bit_idx(i,p) \
+	(((i) & otn_partition_mask)+((p)<<otn_partition_k))
+
+/* get the real array cell corresponding to a certain bit index */
+#define get_otn_array_cell_idx(pos) \
+	((pos)/(sizeof(otn_cell_t)*8))
+
+/* get the bit position inside an otn_array cell
+ * (pos can be obtained from a nonce id with get_otn_array_bit_idx(i, p),
+ *  see above) */
+#define get_otn_cell_bit(pos) \
+	((pos)%(sizeof(otn_cell_t)*8))
+
+/* returns true if the crt_idx > idx with at least  otn_partition_size
+ * WARNING: NID_INC * otn_partition_size must fit inside an nidx_t*/
+#define  otn_id_check_overflow(id,  pool) \
+	((nid_t)(nid_get((pool))-(id)) >= \
+	 	((nid_t)NID_INC*otn_partition_size))
+
+/* re-init the stored nc for nonce id in pool p */
+nid_t otn_new(nid_t id, unsigned char p)
+{
+	unsigned int i;
+	unsigned  n, b;
+	
+	n=get_otn_array_bit_idx(id, p); /* n-th bit */
+	i=get_otn_array_cell_idx(n);    /* aray index i, corresponding to n */
+	b=get_otn_cell_bit(n);          /* bit pos corresponding to n */
+	/* new_value = old_value with the corresponding bit zeroed */
+#ifdef OTN_CELL_T_LONG
+	atomic_and_long((long*)&otn_array[i],  ~((otn_cell_t)1<<b));
+#else
+	atomic_and_int((int*)&otn_array[i],  ~((otn_cell_t)1<<b));
+#endif /* OTN_CELL_T_LONG */
+	return id;
+}
+
+
+
+/* check if nonce w/ index i is expected/valid and if so marked it "seen"
+ * returns: 0 - ok, < 0 some error:
+ * OTN_INV_POOL      (pool number is invalid/corrupted)
+ * OTN_ID_OVERFLOW   (crt_id has overflowed with partition size since the
+ *                    id was generated)
+ * OTN_REPLAY        (nonce id seen before => replay )
+ */
+enum otn_check_ret otn_check_id(nid_t id, unsigned pool)
+{
+	unsigned int i;
+	unsigned n, b;
+	otn_cell_t v, b_mask;
+	
+	if (unlikely(pool>=nid_pool_no))
+		return OTN_INV_POOL;
+	if (unlikely(otn_id_check_overflow(id, pool)))
+		return OTN_ID_OVERFLOW;
+	n=get_otn_array_bit_idx(id, pool); /* n-th bit */
+	i=get_otn_array_cell_idx(n);    /* aray index i, corresponding to n */
+	b=get_otn_cell_bit(n);          /* bit pos corresponding to n */
+	b_mask= (otn_cell_t)1<<b;
+	
+#ifdef OTN_CELL_T_LONG
+	v=atomic_get_long(&oth_array[i]);
+	if (unlikely(v & b_mask))
+		return OTN_REPLAY;
+	atomic_or_long((long*)&otn_array[i],  b_mask);
+#else
+	v=atomic_get_int(&otn_array[i]);
+	if (unlikely(v & b_mask))
+		return OTN_REPLAY;
+	atomic_or_int((int*)&otn_array[i],  b_mask);
+#endif /* OTN_CELL_T_LONG */
+	return 0;
+}
+
+#endif /* USE_OT_NONCE */

+ 84 - 0
modules_s/auth/ot_nonce.h

@@ -0,0 +1,84 @@
+/*
+ * $Id$
+ *
+ * one-time nonce support
+ *
+ * Copyright (C) 2008 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
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * Defines: 
+ *  USE_OT_NONCE   - if not defined no one-time nonce specific code will be
+ *                    compiled
+ *  OTN_CELL_T_LONG - uses unsigned long instead os unsigned int for the
+ *                    array cells
+ */
+/*
+ * History:
+ * --------
+ * 2008-07-10  initial version (andrei)
+ */
+
+
+#ifndef _ot_nonce_h
+#define _ot_nonce_h
+
+extern int otn_enabled;
+
+/* instead of storing only the 2^k size we store also k
+ * for faster operations */
+extern unsigned otn_in_flight_k;    /* maximum in-flight nonces (k in 2^k) */
+extern unsigned otn_in_flight_no   ; /* 2^k == 1<<otn_in_flight_no */
+
+#ifdef USE_OT_NONCE
+
+#include "nid.h" /* nid_t */
+#include "../../atomic_ops.h"
+
+
+/* default number of maximum in-flight nonces */
+#define DEFAULT_OTN_IN_FLIGHT (1024*1024U) /*  1M nonces => 128k mem. */
+#define MIN_OTN_IN_FLIGHT      (128*1024U)  /*  warn if < then 128k nonces */
+
+#define MAX_OTN_IN_FLIGHT    (2*1024*1024*1024U) /* warn if size > 250Mb */
+
+#define MIN_OTN_PARTITION   65536U /* warn if < 65k nonces per partition*/
+
+#ifdef OTN_CELL_T_LONG
+typedef unsigned long otn_cell_t;
+#else
+typedef unsigned int otn_cell_t;
+#endif
+
+int init_ot_nonce();
+void destroy_ot_nonce();
+
+
+enum otn_check_ret{ 
+	OTN_OK=0, OTN_INV_POOL=-1, OTN_ID_OVERFLOW=-2, OTN_REPLAY=-3 
+};
+
+/* check if nonce w/ index i is valid & expected and record receiving it */
+enum otn_check_ret otn_check_id(nid_t i, unsigned pool);
+
+/* re-init the stored nonce state for nonce id in pool pool_no */
+nid_t otn_new(nid_t id, unsigned char pool_no);
+
+#endif /* USE_OT_NONCE */
+#endif /* _ot_nonce_h */
+

+ 149 - 0
modules_s/auth/rfc2617.c

@@ -0,0 +1,149 @@
+/*
+ * $Id$
+ *
+ * Digest response calculation as per RFC2617
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "rfc2617.h"
+#include "../../md5global.h"
+#include "../../md5.h"
+#include "../../dprint.h"
+
+
+inline void cvt_hex(HASH _b, HASHHEX _h)
+{
+	unsigned short i;
+	unsigned char j;
+	
+	for (i = 0; i < HASHLEN; i++) {
+		j = (_b[i] >> 4) & 0xf;
+		if (j <= 9) {
+			_h[i * 2] = (j + '0');
+		} else {
+			_h[i * 2] = (j + 'a' - 10);
+		}
+
+		j = _b[i] & 0xf;
+
+		if (j <= 9) {
+			_h[i * 2 + 1] = (j + '0');
+		} else {
+			_h[i * 2 + 1] = (j + 'a' - 10);
+		}
+	};
+
+	_h[HASHHEXLEN] = '\0';
+}
+
+
+/* 
+ * calculate H(A1) as per spec 
+ */
+void calc_HA1(ha_alg_t _alg, str* _username, str* _realm, str* _password,
+	      str* _nonce, str* _cnonce, HASHHEX _sess_key)
+{
+	MD5_CTX Md5Ctx;
+	HASH HA1;
+	
+	MD5Init(&Md5Ctx);
+	MD5Update(&Md5Ctx, _username->s, _username->len);
+	MD5Update(&Md5Ctx, ":", 1);
+	MD5Update(&Md5Ctx, _realm->s, _realm->len);
+	MD5Update(&Md5Ctx, ":", 1);
+	MD5Update(&Md5Ctx, _password->s, _password->len);
+	MD5Final(HA1, &Md5Ctx);
+
+	if (_alg == HA_MD5_SESS) {
+		MD5Init(&Md5Ctx);
+		MD5Update(&Md5Ctx, HA1, HASHLEN);
+		MD5Update(&Md5Ctx, ":", 1);
+		MD5Update(&Md5Ctx, _nonce->s, _nonce->len);
+		MD5Update(&Md5Ctx, ":", 1);
+		MD5Update(&Md5Ctx, _cnonce->s, _cnonce->len);
+		MD5Final(HA1, &Md5Ctx);
+	};
+
+	cvt_hex(HA1, _sess_key);
+}
+
+
+/* 
+ * calculate request-digest/response-digest as per HTTP Digest spec 
+ */
+void calc_response(HASHHEX _ha1,      /* H(A1) */
+		   str* _nonce,       /* nonce from server */
+		   str* _nc,          /* 8 hex digits */
+		   str* _cnonce,      /* client nonce */
+		   str* _qop,         /* qop-value: "", "auth", "auth-int" */
+		   int _auth_int,     /* 1 if auth-int is used */
+		   str* _method,      /* method from the request */
+		   str* _uri,         /* requested URL */
+		   HASHHEX _hentity,  /* H(entity body) if qop="auth-int" */
+		   HASHHEX _response) /* request-digest or response-digest */
+{
+	MD5_CTX Md5Ctx;
+	HASH HA2;
+	HASH RespHash;
+	HASHHEX HA2Hex;
+	
+	     /* calculate H(A2) */
+	MD5Init(&Md5Ctx);
+	MD5Update(&Md5Ctx, _method->s, _method->len);
+	MD5Update(&Md5Ctx, ":", 1);
+	MD5Update(&Md5Ctx, _uri->s, _uri->len);
+
+	if (_auth_int) {
+		MD5Update(&Md5Ctx, ":", 1);
+		MD5Update(&Md5Ctx, _hentity, HASHHEXLEN);
+	};
+
+	MD5Final(HA2, &Md5Ctx);
+	cvt_hex(HA2, HA2Hex);
+
+	     /* calculate response */
+	MD5Init(&Md5Ctx);
+	MD5Update(&Md5Ctx, _ha1, HASHHEXLEN);
+	MD5Update(&Md5Ctx, ":", 1);
+	MD5Update(&Md5Ctx, _nonce->s, _nonce->len);
+	MD5Update(&Md5Ctx, ":", 1);
+
+	if (_qop->len) {
+		MD5Update(&Md5Ctx, _nc->s, _nc->len);
+		MD5Update(&Md5Ctx, ":", 1);
+		MD5Update(&Md5Ctx, _cnonce->s, _cnonce->len);
+		MD5Update(&Md5Ctx, ":", 1);
+		MD5Update(&Md5Ctx, _qop->s, _qop->len);
+		MD5Update(&Md5Ctx, ":", 1);
+	};
+	MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
+	MD5Final(RespHash, &Md5Ctx);
+	cvt_hex(RespHash, _response);
+}

+ 102 - 0
modules_s/auth/rfc2617.h

@@ -0,0 +1,102 @@
+/*
+ * $Id$
+ *
+ * Digest response calculation as per RFC2617
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef RFC2617_H
+#define RFC2617_H
+
+#include "../../str.h"
+
+
+#define HASHLEN 16
+typedef char HASH[HASHLEN];
+
+
+#define HASHHEXLEN 32
+typedef char HASHHEX[HASHHEXLEN+1];
+
+
+/*
+ * Type of algorithm used
+ */
+typedef enum {
+	HA_MD5,      /* Plain MD5 */
+	HA_MD5_SESS  /* MD5-Session */
+} ha_alg_t;
+
+
+/*
+ * Convert to hex form
+ */
+void cvt_hex(HASH Bin, HASHHEX Hex);
+
+
+/* 
+ * calculate H(A1) as per HTTP Digest spec 
+ */
+typedef void (*calc_HA1_t)(ha_alg_t _alg,      /* Type of algorithm */
+	      str* _username,     /* username */
+	      str* _realm,        /* realm */
+	      str* _password,     /* password */
+	      str* _nonce,        /* nonce string */
+	      str* _cnonce,       /* cnonce */
+	      HASHHEX _sess_key); /* Result will be stored here */
+void calc_HA1(ha_alg_t _alg,      /* Type of algorithm */
+	      str* _username,     /* username */
+	      str* _realm,        /* realm */
+	      str* _password,     /* password */
+	      str* _nonce,        /* nonce string */
+	      str* _cnonce,       /* cnonce */
+	      HASHHEX _sess_key); /* Result will be stored here */
+
+
+/* calculate request-digest/response-digest as per HTTP Digest spec */
+typedef void (*calc_response_t)(HASHHEX _ha1,       /* H(A1) */
+		   str* _nonce,        /* nonce from server */
+		   str* _nc,           /* 8 hex digits */
+		   str* _cnonce,       /* client nonce */
+		   str* _qop,          /* qop-value: "", "auth", "auth-int" */
+		   int _auth_int,      /* 1 if auth-int is used */
+		   str* _method,       /* method from the request */
+		   str* _uri,          /* requested URL */
+		   HASHHEX _hentity,   /* H(entity body) if qop="auth-int" */
+		   HASHHEX _response); /* request-digest or response-digest */
+void calc_response(HASHHEX _ha1,       /* H(A1) */
+		   str* _nonce,        /* nonce from server */
+		   str* _nc,           /* 8 hex digits */
+		   str* _cnonce,       /* client nonce */
+		   str* _qop,          /* qop-value: "", "auth", "auth-int" */
+		   int _auth_int,      /* 1 if auth-int is used */
+		   str* _method,       /* method from the request */
+		   str* _uri,          /* requested URL */
+		   HASHHEX _hentity,   /* H(entity body) if qop="auth-int" */
+		   HASHHEX _response); /* request-digest or response-digest */
+
+
+#endif /* RFC2617_H */

+ 9 - 0
modules_s/auth/todo.txt

@@ -0,0 +1,9 @@
+- Describe parser structure, how nonce is looked up
+- Describe that www,proxy_authorize must be called before
+  any other function
+- Create some examples how to use digest parser
+
+- Consider MD5-Sess support(will require to store passwords in clear text)
+- auth-int support
+- Database cache
+- Option to use memory only

+ 1 - 0
modules_s/auth_db/.cvsignore

@@ -0,0 +1 @@
+auth_db.7

+ 17 - 0
modules_s/auth_db/Makefile

@@ -0,0 +1,17 @@
+# $Id$
+#
+# Digest Authentication - Database support
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=auth_db.so
+LIBS=
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 279 - 0
modules_s/auth_db/README

@@ -0,0 +1,279 @@
+
+Auth_db Module
+
+Jan Janak
+
+   FhG Fokus
+
+Jakob Schlyter
+
+Edited by
+
+Jan Janak
+
+   Copyright © 2002, 2003 FhG FOKUS
+     _________________________________________________________
+
+   Table of Contents
+   1. User's Guide
+
+        1.1. Overview
+        1.2. Dependencies
+        1.3. Exported Parameters
+
+              1.3.1. db_url (string)
+              1.3.2. user_column (string)
+              1.3.3. domain_column (string)
+              1.3.4. password_column (string)
+              1.3.5. rpid_column (string)
+              1.3.6. calculate_ha1 (integer)
+              1.3.7. password_column_2 (string)
+              1.3.8. use_rpid (integer)
+
+        1.4. Exported Functions
+
+              1.4.1. www_authorize(realm, table)
+              1.4.2. proxy_authorize(realm, table)
+
+   2. Developer's Guide
+   3. Frequently Asked Questions
+
+   List of Examples
+   1-1. db_url parameter usage
+   1-2. user_column usage
+   1-3. domain_column usage
+   1-4. password_column usage
+   1-5. rpid_column usage
+   1-6. calculate_ha1usage
+   1-7. password_column_2 usage
+   1-8. use_rpidusage
+   1-9. www_authorize usage
+   1-10. proxy_authorize usage
+     _________________________________________________________
+
+Chapter 1. User's Guide
+
+1.1. Overview
+
+   This module contains all authentication related functions that
+   need the access to the database. This module should be used
+   together with auth module, it cannot be used independently
+   because it depends on the module. Select this module if you
+   want to use database to store authentication information like
+   subscriber usernames and passwords. If you want to use radius
+   authentication, then use auth_radius instead.
+     _________________________________________________________
+
+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
+     * database -- Any database module (currently mysql,
+       postgres, dbtext)
+     _________________________________________________________
+
+1.3. Exported Parameters
+
+1.3.1. db_url (string)
+
+   This is URL of the database to be used. Value of the parameter
+   depends on the database module used. For example for mysql and
+   postgres modules this is something like
+   mysql://username:password@host:port/database. For dbtext
+   module (which stores data in plaintext files) it is directory
+   in which the database resides.
+
+   Default value is "mysql://serro:47serro11@localhost/ser".
+
+   Example 1-1. db_url parameter usage
+modparam("auth_db", "db_url", "mysql://foo:[email protected]/ser")
+     _________________________________________________________
+
+1.3.2. user_column (string)
+
+   This is the name of the column holding usernames. Default
+   value is fine for most people. Use the parameter if you really
+   need to change it.
+
+   Default value is "username".
+
+   Example 1-2. user_column usage
+modparam("auth_db", "user_column", "user")
+     _________________________________________________________
+
+1.3.3. domain_column (string)
+
+   This is the name of the column holding domains of users.
+   Default value is fine for most people. Use the parameter if
+   you really need to change it.
+
+   Default value is "domain".
+
+   Example 1-3. domain_column usage
+modparam("auth_db", "domain_column", "domain")
+     _________________________________________________________
+
+1.3.4. password_column (string)
+
+   This is the name of the column holding passwords. Passwords
+   can be either stored as plain text or pre-calculated HA1
+   strings. HA1 strings are MD5 hashes of username, password, and
+   realm. HA1 strings are more safe because the server doesn't
+   need to know plaintext passwords and they cannot be obtained
+   from HA1 strings.
+
+   Default value is "ha1".
+
+   Example 1-4. password_column usage
+modparam("auth_db", "password_column", "password")
+     _________________________________________________________
+
+1.3.5. rpid_column (string)
+
+   This is the name of the column holding information for the
+   Remote-Party-ID header field. Default value is fine for most
+   people. Use the parameter if you really need to change it.
+
+   Default value is "rpid".
+
+   Example 1-5. rpid_column usage
+modparam("auth_db", "rpid_column", "remote_party_id")
+     _________________________________________________________
+
+1.3.6. calculate_ha1 (integer)
+
+   This parameter tells server whether it should expect plaintext
+   passwords in the database or HA1 string. If the parameter is
+   set to 1 then the server will assume that the column pointed
+   to by password_column contains plaintext passwords and it will
+   calculate HA1 strings on the fly.
+
+   If the parameter is set to 0 then the server assumes that the
+   database contains HA1 strings directly and will not calculate
+   them. If username parameter of credentials contains also
+   @domain (some user agents put domain in username parameter),
+   then column pointed to by password_column_2 parameter will be
+   used instead. This column should also contain HA1 strings but
+   they should be calculated including the domain in the username
+   parameter (as opposed to password_column which (when
+   containing HA1 strings) should always contains HA1 strings
+   calculated without domain in username.
+
+   This ensures that the authentication will always work when
+   using pre-calculated HA1 string, not depending on the presence
+   of the domain in username.
+
+   Default value of this parameter is 0.
+
+   Example 1-6. calculate_ha1usage
+modparam("auth_db", "calculate_ha1", 1)
+     _________________________________________________________
+
+1.3.7. password_column_2 (string)
+
+   As described in the previous section this parameter contains
+   name of column holding pre-calculated HA1 string that were
+   calculated including the domain in the username. This
+   parameter is used only when calculate_ha1 is set to 0 and user
+   agent send a credentials containing the domain in the
+   username.
+
+   Default value of the parameter is ha1b.
+
+   Example 1-7. password_column_2 usage
+modparam("auth_db", "password_column_2", "ha1_2")
+     _________________________________________________________
+
+1.3.8. use_rpid (integer)
+
+   This parameter specifies whether the server should fetch a
+   value for the Remote-Party-ID header field from the database.
+
+   If the parameter is set to 1 the server expects to find a
+   value for this header in the column specified by the
+   rpid_column parameter.
+
+   Default value of this parameter is 0.
+
+   Example 1-8. use_rpidusage
+modparam("auth_db", "use_rpid", 1)
+     _________________________________________________________
+
+1.4. Exported Functions
+
+1.4.1. www_authorize(realm, table)
+
+   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.
+
+   Meaning of the parameters 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.
+     * table - Table to be used to lookup usernames and passwords
+       (usually subscribers table).
+
+   Example 1-9. www_authorize usage
+...
+if (www_authorize("iptel.org", "subscriber")) {
+    www_challenge("iptel.org", "1");
+};
+...
+     _________________________________________________________
+
+1.4.2. proxy_authorize(realm, table)
+
+   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.
+
+   Meaning of the parameters 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.
+     * table - Table to be used to lookup usernames and passwords
+       (usually subscribers table).
+
+   Example 1-10. proxy_authorize usage
+...
+if (!proxy_authorize("", "subscriber)) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+     _________________________________________________________
+
+Chapter 2. Developer's Guide
+
+   To be done.
+     _________________________________________________________
+
+Chapter 3. Frequently Asked Questions
+
+   3.1. What is the meaning of life ?
+
+   3.1. What is the meaning of life ?
+
+   42

+ 121 - 0
modules_s/auth_db/aaa_avps.h

@@ -0,0 +1,121 @@
+/*
+ * $Id$
+ *
+ * Common functions for Digest Authentication and Accounting Modules
+ *
+ * Copyright (C) 2001-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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+
+#ifndef _SER_AAA_AVPS_H_
+#define _SER_AAA_AVPS_H_
+
+#include "../../mem/mem.h"
+#include "../../parser/parser_f.h"
+#include "../../dprint.h"
+
+#include <string.h>
+
+/*
+ * Parse list of tokens separated by some char and put each tocken
+ * into result array. Caller frees result array!
+ */
+static inline int
+parse_token_list(char *p, char *pend, char separator, str **result)
+{
+	int i;
+
+	i = 0;
+	*result = NULL;
+	while ((pend - p) > 0) {
+		*result = pkg_realloc(*result, sizeof(**result) * (i + 1));
+		if (*result == NULL)
+			return -1;
+		(*result)[i].s = p;
+		p = eat_token2_end(p, pend, separator) + 1;
+		(*result)[i].len = p - (*result)[i].s - 1;
+		i++;
+	}
+	return i;
+}
+
+static inline int
+aaa_avps_init(str *avps_column_int, str *avps_column_str,
+    str **avps_int, str **avps_str, int *avps_int_n, int *avps_str_n)
+{
+	int errcode, i;
+	char *cp;
+
+	avps_column_int->len = strlen(avps_column_int->s);
+	avps_column_str->len = strlen(avps_column_str->s);
+
+	cp = pkg_malloc(avps_column_int->len + 1);
+	if (cp == NULL) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't allocate memory\n");
+		errcode = -1;
+		goto bad;
+	}
+	memcpy(cp, avps_column_int->s, avps_column_int->len);
+	*avps_int_n = parse_token_list(cp, cp + avps_column_int->len, '|', avps_int);
+	if (*avps_int_n == -1) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't parse avps_column_int "
+		    "parameter\n");
+		errcode = -2;
+		pkg_free(cp);
+		goto bad;
+	}
+	cp = pkg_malloc(avps_column_str->len + 1);
+	if (cp == NULL) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't allocate memory\n");
+		errcode = -3;
+		goto bad;
+	}
+	memcpy(cp, avps_column_str->s, avps_column_str->len);
+	*avps_str_n = parse_token_list(cp, cp + avps_column_str->len, '|', avps_str);
+	if (*avps_str_n == -1) {
+		LOG(L_ERR, "aaa_avps::aaa_avps_init(): can't parse avps_column_str "
+		    "parameter\n");
+		errcode = -4;
+		pkg_free(cp);
+		goto bad;
+	}
+	for (i = 0; i < *avps_int_n; i++)
+		(*avps_int)[i].s[(*avps_int)[i].len] = '\0';
+	for (i = 0; i < *avps_str_n; i++)
+		(*avps_str)[i].s[(*avps_str)[i].len] = '\0';
+
+	return 0;
+bad:
+	if (*avps_int != NULL) {
+		pkg_free((*avps_int)[0].s);
+		pkg_free(*avps_int);
+	}
+	if (*avps_str != NULL) {
+		pkg_free((*avps_str)[0].s);
+		pkg_free(*avps_str);
+	}
+	return errcode;
+}
+
+#endif

+ 541 - 0
modules_s/auth_db/auth_db.xml

@@ -0,0 +1,541 @@
+<?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.auth_db"
+          xmlns:xi="http://www.w3.org/2001/XInclude"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>auth_db</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>auth_db</refname>
+    <refpurpose>Digest Authentication Using a Database</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>auth_db</command> SER module provides digest
+      authentication using credentials stored in a database.
+    </para>
+    <para>
+      The <command>auth_db</command> module needs the
+      <serdoc:module>auth</serdoc:module> module and a database module such
+      as the <serdoc:module>mysql</serdoc:module> module. 
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.proxy_authenticate">
+      <title>
+        <function>proxy_authenticate</function>
+        (<symbol>realm</symbol>, <symbol>table</symbol>)
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>proxy_authenticate()</function> function verifies
+        credentials provided by the client for the realm given by the
+        <symbol>realm</symbol>. The username and digest response
+        provided by the client are checked against the credentials
+        stored in the database table <symbol>table</symbol>.
+      </para>
+      <para>
+        The <function>proxy_authenticate()</function> function takes
+        the credentials from the Proxy-Authorize header field. If the
+        credentials their match an entry in the
+        credentials database table, <function>proxy_authenticate()</function>
+        stores the user ID matching the credentials in the attribute
+        <varname>$fu.uid</varname> and returns <literal>true</literal>.
+      </para>
+      <para>
+        Otherwise, it creates a digest challenge which should be included
+        in an error response sent back to the client. The challenge will
+        be stored in the attribute defined by the
+        <serdoc:modparam module="auth">challenge_attr</serdoc:modparam>
+        of the <serdoc:module>auth</serdoc:module> module,
+        usually <varname>$digest_challenge</varname>. You should add
+        the content of this attribute to the response using the
+        <serdoc:func>append_to_reply</serdoc:func> function from
+        the <serdoc:module>textops</serdoc:module> module.
+      </para>
+      <para>
+        The reason for the failure can be determined from the interger
+        return value stored in the <varname>$?</varname> attribute.
+        A value of <literal>-3</literal> indicates that it was impossible
+        to retrieve credentials from the request. A 400 (Bad Request)
+        response is the usual way to handle this. The value of
+        <literal>-2</literal> indicates that something went wrong
+        internally and the client is not to be blamed.
+      </para>
+      <para>
+        The typical usage of <function>proxy_authenticate()</function> looks
+        like this:
+      </para>
+      <informalexample>
+        <programlisting>
+	if (!proxy_authenticate("$fd.digest_realm", "credentials")) {
+		if ($? == -2) {
+			sl_reply("500", "Internal Server Error");
+		}
+		else if ($? == -3) {
+			sl_reply("400", "Bad Request");
+		}
+		else {
+			if ($digest_challenge) {
+				append_to_reply("%$digest_challenge");
+			}
+			sl_reply("401", "Unauthorized");
+		}
+		drop;
+	}
+        </programlisting>
+      </informalexample>
+    </refsect2>
+
+    <refsect2 xml:id="function.proxy_authorize">
+      <title>
+        <function>proxy_authorize</function>
+        (<symbol>realm</symbol>, <symbol>table</symbol>)
+      </title>
+      <para>
+        The <function>proxy_authorize()</function> function is an
+        alternative name for the
+        <serdoc:func>proxy_authenticate</serdoc:func> function. It is
+        deprecated and is likely to be removed in future version.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.www_authenticate">
+      <title>
+        <function>www_authenticate</function>
+        (<symbol>realm</symbol>, <symbol>table</symbol>)
+      </title>
+      <para>
+        The <function>www_authenticate()</function> function behaves
+        similarly to the <serdoc:func>proxy_authenticate()</serdoc:func>
+        function. The only difference is that it takes the credentials
+        from the Authorization header field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.www_authorize">
+      <title>
+        <function>www_authorize</function>
+        (<symbol>realm</symbol>, <symbol>table</symbol>)
+      </title>
+      <para>
+        The <function>www_authorize()</function> function is an
+        alternative name for the
+        <serdoc:func>www_authenticate</serdoc:func> function. It is
+        deprecated and is likely to be removed in future version.
+      </para>
+    </refsect2>
+  </refsect1>
+
+  <refsect1 xml:id="module.auth_db.parameters">
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.auth_db.calculate_ha1">
+      <title><parameter>calculate_ha1</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>calculate_ha1</parameter> parameter determines
+        whether the <command>auth_db</command> module uses the hash
+        values stored in the database or the plaintext password. If left at
+        default value <literal>no</literal>, the hashes from the columns
+        specified by the parameters
+        <serdoc:modparam module="auth_db">password_column</serdoc:modparam>
+        and
+        <serdoc:modparam module="auth_db">password_column2</serdoc:modparam>
+        are used. If it is set to <literal>yes</literal>, the hashes are
+        calculated from the various fields, including the plaintext password
+        stored in the column specified by
+        <serdoc:modparam module="auth_db">
+          plain_password_column</serdoc:modparam>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.db_url">
+      <title><parameter>db_url</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>
+          mysql://serro:47serro11@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.auth_db.did_column">
+      <title><parameter>did_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>did</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>did_column</parameter> parameter specifies the
+        name of the <varname>did</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_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 specifies the
+        name of the <varname>flags</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.load_credentials">
+      <title><parameter>load_credentials</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>uid</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>load_credentials</parameter> parameter contains a
+        list of attributes, that should be loaded upon successful
+        authentication with a certain credentials.
+      </para>
+      <para>
+        The list contains the name names of both the attribute without the
+        leading dollar sign and any prefix which doubles as the name of
+        the database field which contains the value for the attribute.
+        The names are separated by vertical bar <literal>|</literal>.
+        The attributes will be loaded into the <varname>$fu</varname>
+        namespace.
+      </para>
+      <para>
+        In the default setup, the only attribute loaded this way is the
+        user ID of the user associated with the credentials. It is stored
+        in the database field <varname>uid</varname> and loaded into the
+        attribute <varname>$fu.uid</varname> upon successful
+        authentication.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.password_column">
+      <title><parameter>password_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>ha1</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>password_column</parameter> parameter specifies
+        the name of the <varname>ha1</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.password_column_2">
+      <title><parameter>password_column_2</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>ha1b</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>password_column_2</parameter> parameter specifies
+        the name of the <varname>ha1b</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.plain_password_column">
+      <title><parameter>password_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>password</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>plain_password_column</parameter> parameter
+        specifies the name of the <varname>password</varname> database
+        field. See <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+       </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.realm_column">
+      <title><parameter>realm_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>realm</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>realm_column</parameter> parameter specifies
+        the name of the <varname>realm</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.username_column">
+      <title><parameter>username_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>auth_username</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>username_column</parameter> parameter specifies
+        the name of the <varname>auth_username</varname> database field. See
+        <serdoc:link linkend="module.auth_db.database">Database
+        Scheme</serdoc:link> below.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_db.use_did">
+      <title><parameter>use_did</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>use_did</parameter> specifies whether the
+        domain ID stored in the database should be considered when
+        loading credentials from the database. If it is changed to
+        <literal>yes</literal>, only those rows are loaded where the
+        domain ID in the column specified by the 
+        <serdoc:modparam module="auth_db">did_column</serdoc:modparam>
+        parameter matches that determined from the domain in the
+        To header field.
+      </para>
+    </refsect2>
+
+    <!--
+    <refsect2 xml:id="module.auth_db.">
+      <title><parameter></parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype></serdoc:paramtype>
+        <serdoc:paramdefault></serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+      </para>
+    </refsect2>
+    -->
+
+  </refsect1>
+
+  <refsect1 xml:id="module.auth_db.database">
+    <title>Database Scheme</title>
+
+
+    <refsect2 xml:id="table.credentials.auth_username">
+      <title><varname>auth_username</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>auth_username</varname> field contains the username
+        used for authentication. It is passed by the client in the
+        <varname>username</varname> parameter of the
+        <varname>Authorize</varname> or <varname>Proxy-Authorize</varname>
+        header field. Most commonly, it is equal to the username part of
+        the URI for which the client is registering or which it uses as
+        the From URI.
+      </para>
+      <para>
+        The field is used as part of the key for finding the correct
+        credentials.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.credentials.did">
+      <title><varname>did</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>
+          VARCHAR(64) NOT NULL DEFAULT '_default'
+        </serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>did</varname> field contains the domain ID of the
+        domain to which the credentials apply. The field is used as part
+        of the key for finding the correct credentials if the
+        <serdoc:modparam module="auth_db">use_did</serdoc:modparam>
+        parameter is set to <literal>yes</literal>. In this case, the
+        domain ID is taken from the <varname>$t.did</varname> attribute
+        if the method of the request is <varname>REGISTER</varname> or
+        <varname>$f.did</varname> otherwise. 
+      </para>
+      <para>
+        If <serdoc:modparam module="auth_db">use_did</serdoc:modparam>
+        is kept at its default <literal>no</literal>, the field is ignored.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.credentials.realm">
+      <title><varname>realm</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>realm</varname> field contains the realm of the
+        credentials.
+        The realm is a string used to distinguish the various credentials
+        that a request may contains. It is also presented to the user so
+        that they will be able to figure out which username and password
+        are requested. It is recommended to put the name of the domain
+        name associated with the credentials into the realm.
+      </para>
+      <para>
+        The field is used as part of the key for finding the correct
+        credentials.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.credentials.password">
+      <title><varname>password</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(28) NOT NULL DEFAULT ''</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>password</varname> field contains the plaintext
+        password. The field is only used if the
+        <serdoc:modparam module="auth">calculate_ha1</serdoc:modparam>
+        parameter is set to <literal>yes</literal>. Otherwise, the
+        hashes in the <varname>ha1</varname> and <varname>ha1b</varname>
+        fields are used and this field is ignored.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="table.credentials.flags">
+      <title><varname>flags</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>INT NOT NULL DEFAULT '0'</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>flags</varname> field contains the database flags,
+        a bitfield of various values. The <command>auth_db</command> module
+        uses the following bit values:
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><literal>1</literal></term>
+          <listitem>
+            (name: <literal>LOAD_SER</literal>,
+            serctl letter: <literal>s</literal>)
+            the entry will only be considered if this flag is set;
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>2</literal></term>
+          <listitem>
+            (name: <literal>DISABLED</literal>,
+            serctl letter: <literal>d</literal>)
+            the entry will be ignored if this flag is set.
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 id="table.credentials.ha1">
+      <title><varname>ha1</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(32) NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>ha1</varname> contains an MD5 hash used in calculating
+        the <varname>response</varname> parameter of the digest response.
+      </para>
+      <para>
+        In order to not transmit the password in plain text over a
+        potentially tapped wire, the digest authentication scheme only
+        transmits a hash generated over certain values. Among other things,
+        this includes an MD5 hash called H(A1) generated over the username,
+        realm, and
+        plaintext password concatenated with colons in between. Since
+        these three values never change for given credentials, the hash
+        can be stored in the database. This will save some work.
+      </para>
+      <para>
+        The tool <serdoc:bin>ser_cred</serdoc:bin> automatically generates
+        these hash values when creating or updating credentials. If you
+        are filling the database yourself, you can use the tool
+        <serdoc:sbin>gen_ha1</serdoc:sbin> to generate the hash.
+      </para>
+      <para>
+        Alteratively, you can also let the <command>auth_db</command>
+        module calculate the hash by setting the
+        <serdoc:modparam module="auth">calculate_ha1</serdoc:modparam>
+        parameter to <literal>yes</literal>. In this case, the field
+        <varname>ha1</varname> is ignored and the plain text password
+        in the <varname>password</varname> field is used instead.
+      </para>
+    </refsect2>
+
+    <refsect2 id="table.credentials.ha1b">
+      <title><varname>ha1b</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(32) NOT NULL DEFAULT ''</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>ha1b</varname> field contains an MD5 hash used in
+        calculating the <varname>response</varname> parameter of the
+        digest response.
+      </para>
+      <para>
+        The hash stored in this field is the same as in the
+        <varname>ha1</varname>. However, some clients put the combination
+        of username and domain into the <varname>username</varname>
+        parameter of their digest response. The hash then needs to be
+        calculated over a concatenation of username and domain separated
+        by an at sign, followed by the realm and password separated by a
+        colon.
+      </para>
+    </refsect2>
+
+    <refsect2 id="table.credentials.uid">
+      <title><varname>uid</varname></title>
+      <serdoc:fieldinfo>
+        <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+      </serdoc:fieldinfo>
+      <para>
+        The <varname>uid</varname> field contains the user ID of the user
+        associated with the credentials. If a request using the credentials
+        is successfully authenticated, the user ID is stored in the
+        attribute <varname>$fu.uid</varname>.
+      </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>auth</serdoc:module></member>
+      <member><serdoc:module>auth_diameter</serdoc:module></member>
+      <member><serdoc:module>auth_identity</serdoc:module></member>
+      <member><serdoc:module>auth_radius</serdoc:module></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+
+<!-- vim:sw=2 sta et sts=2 ai
+  -->

+ 374 - 0
modules_s/auth_db/authdb_mod.c

@@ -0,0 +1,374 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication Module
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * --------
+ * 2003-02-26: checks and group moved to separate modules (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)
+ * 2003-04-05: default_uri #define used (jiri)
+ * 2004-06-06  cleanup: static & auth_db_{init,bind,close.ver} used (andrei)
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "../../sr_module.h"
+#include "../../lib/srdb2/db.h"
+#include "../../dprint.h"
+#include "../../error.h"
+#include "../../mem/mem.h"
+#include "authorize.h"
+#include "../auth/aaa_avps.h"
+#include "../auth/api.h"
+#include "authdb_mod.h"
+
+MODULE_VERSION
+
+#define TABLE_VERSION 7
+
+/*
+ * Module destroy function prototype
+ */
+static void destroy(void);
+
+
+/*
+ * Module child-init function prototype
+ */
+static int child_init(int rank);
+
+
+/*
+ * Module initialization function prototype
+ */
+static int mod_init(void);
+
+
+static int authdb_fixup(void** param, int param_no);
+
+
+/*
+ * Pointer to reply function in stateless module
+ */
+sl_api_t sl;
+
+
+#define USERNAME_COL "auth_username"
+#define DID_COL "did"
+#define REALM_COL "realm"
+#define PASS_COL "ha1"
+#define PASS_COL_2 "ha1b"
+#define PLAIN_PASS_COL "password"
+#define DEFAULT_CRED_LIST "uid"
+#define FLAGS_COL "flags"
+
+/*
+ * Module parameter variables
+ */
+static char* db_url         = DEFAULT_RODB_URL;
+
+str username_column         = STR_STATIC_INIT(USERNAME_COL);
+str did_column              = STR_STATIC_INIT(DID_COL);
+str realm_column            = STR_STATIC_INIT(REALM_COL);
+str pass_column             = STR_STATIC_INIT(PASS_COL);
+str pass_column_2           = STR_STATIC_INIT(PASS_COL_2);
+str flags_column            = STR_STATIC_INIT(FLAGS_COL);
+str plain_password_column   = STR_STATIC_INIT(PLAIN_PASS_COL);
+
+int calc_ha1                = 0;
+int use_did                 = 0;
+int check_all               = 0;
+
+db_ctx_t* auth_db_handle = 0;      /* database connection handle */
+auth_api_t auth_api;
+
+str credentials_list        = STR_STATIC_INIT(DEFAULT_CRED_LIST);
+
+str* credentials;          /* Parsed list of credentials to load */
+int credentials_n;         /* Number of credentials in the list */
+
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+    {"www_authenticate",   www_authenticate,    2, authdb_fixup, REQUEST_ROUTE},
+    {"www_authorize",      www_authenticate,    2, authdb_fixup, REQUEST_ROUTE},
+    {"proxy_authenticate", proxy_authenticate,  2, authdb_fixup, REQUEST_ROUTE},
+    {"proxy_authorize",    proxy_authenticate,  2, authdb_fixup, REQUEST_ROUTE},
+    {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+    {"db_url",            PARAM_STRING, &db_url          },
+    {"username_column",   PARAM_STR,    &username_column },
+    {"did_column",        PARAM_STR,    &did_column      },
+    {"realm_column",      PARAM_STR,    &realm_column    },
+    {"password_column",   PARAM_STR,    &pass_column     },
+    {"password_column_2", PARAM_STR,    &pass_column_2   },
+    {"plain_password_column",   PARAM_STR,    &plain_password_column },
+    {"flags_column",      PARAM_STR,    &flags_column    },
+    {"calculate_ha1",     PARAM_INT,    &calc_ha1        },
+    {"load_credentials",  PARAM_STR,    &credentials_list},
+    {"use_did",           PARAM_INT,    &use_did         },
+    {"check_all_ha1",     PARAM_INT,    &check_all       },
+    {0, 0, 0}
+};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+    "auth_db",
+    cmds,       /* Exported functions */
+    0,          /* RPC methods */
+    params,     /* Exported parameters */
+    mod_init,   /* module initialization function */
+    0,          /* response function */
+    destroy,    /* destroy function */
+    0,          /* oncancel function */
+    child_init  /* child initialization function */
+};
+
+static authdb_table_info_t *registered_tables = NULL;
+
+static int generate_queries(authdb_table_info_t *info)
+{
+	db_fld_t match_with_did[] = {
+		{ .name = username_column.s, .type = DB_STR }, 
+		{ .name = realm_column.s, .type = DB_STR }, 
+		{ .name = did_column.s, .type = DB_STR }, 
+		{ .name = NULL }
+	};
+	db_fld_t match_without_did[] = {
+		{ .name = username_column.s, .type = DB_STR }, 
+		{ .name = realm_column.s, .type = DB_STR }, 
+		{ .name = NULL }
+	};
+	db_fld_t *result_cols = NULL;
+	int len, i;
+
+	len = sizeof(*result_cols) * (credentials_n + 3);
+	result_cols = pkg_malloc(len);
+	if (!result_cols) {
+		ERR("can't allocate pkg mem\n");
+		return -1;
+	}
+	memset(result_cols, 0, len);
+
+	result_cols[0].name = pass_column.s;
+	result_cols[0].type = DB_CSTR;
+	
+	result_cols[1].name = flags_column.s;
+	result_cols[1].type = DB_INT;
+	for (i = 0; i < credentials_n; i++) {
+		result_cols[2 + i].name = credentials[i].s;
+		result_cols[2 + i].type = DB_STR;
+	}
+	result_cols[2 + i].name = NULL;
+
+	if (use_did) {
+		info->query_pass = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_with_did, NULL);
+		result_cols[0].name = pass_column_2.s;
+		info->query_pass2 = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_with_did, NULL);
+		result_cols[0].name = plain_password_column.s;
+		info->query_password = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_with_did, NULL);
+	}
+	else {
+		info->query_pass = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_without_did, NULL);
+		result_cols[0].name = pass_column_2.s;
+		info->query_pass2 = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_without_did, NULL);
+		result_cols[0].name = plain_password_column.s;
+		info->query_password = db_cmd(DB_GET, auth_db_handle, info->table.s, 
+				result_cols, match_without_did, NULL);
+	}
+
+	pkg_free(result_cols);
+	if (info->query_pass && info->query_pass2 && info->query_password) return 0;
+	else return -1;
+}
+
+static int child_init(int rank)
+{
+	authdb_table_info_t *i;
+
+	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0; /* do nothing for the main process */
+
+	auth_db_handle = db_ctx("auth_db");
+	if (!auth_db_handle) goto err;
+	if (db_add_db(auth_db_handle, db_url) < 0) goto err;
+	if (db_connect(auth_db_handle) < 0) goto err;
+
+	/* initializing queries */
+	i = registered_tables;
+	while (i) {
+		if (generate_queries(i) < 0) {
+			ERR("can't prepare queries\n");
+			return -1;
+		}
+		i = i->next;
+	}
+    
+	return 0;
+
+err:
+
+	if (auth_db_handle) {
+		auth_db_handle = NULL;
+		db_ctx_free(auth_db_handle);
+	}
+
+	ERR("Error while initializing database layer\n");
+	return -1;
+}
+
+
+static int mod_init(void)
+{
+    bind_auth_t bind_auth;
+    
+    DBG("auth_db module - initializing\n");
+    
+    bind_auth = (bind_auth_t)find_export("bind_auth", 0, 0);
+    if (!bind_auth) {
+	LOG(L_ERR, "auth_db:mod_init: Unable to find bind_auth function\n");
+	return -1;
+    }
+    if (bind_auth(&auth_api) < 0) {
+	LOG(L_ERR, "auth_db:child_init: Unable to bind auth module\n");
+	return -3;
+    }
+    
+    if (aaa_avps_init(&credentials_list, &credentials, &credentials_n)) {
+	return -1;
+    }
+    
+    return 0;
+}
+
+
+static void destroy(void)
+{
+    if (auth_db_handle) {
+		db_ctx_free(auth_db_handle);
+		auth_db_handle = NULL;
+    }
+}
+
+static int str_case_equals(const str *a, const str *b)
+{
+	/* ugly hack: taken from libcds */
+	int i;
+	
+	if (!a) {
+		if (!b) return 0;
+		else return (b->len == 0) ? 0 : 1;
+	}
+	if (!b) return (a->len == 0) ? 0 : 1;
+	if (a->len != b->len) return 1;
+	
+	for (i = 0; i < a->len; i++) 
+		if (a->s[i] != b->s[i]) return 1;
+	return 0;
+}
+
+static authdb_table_info_t *find_table_info(str *table)
+{
+	authdb_table_info_t *i = registered_tables;
+	
+	/* sequential search is OK because it is called only in child init */
+	while (i) { 
+		if (str_case_equals(&i->table, table) == 0) return i;
+		i = i->next;
+	}
+	return NULL;
+}
+
+static authdb_table_info_t *register_table(str *table)
+{
+	authdb_table_info_t *info;
+
+	info = find_table_info(table);
+	if (info) return info; /* queries for this table already exist */
+
+	info = (authdb_table_info_t*)pkg_malloc(sizeof(authdb_table_info_t) + table->len + 1);
+	if (!info) {
+		ERR("can't allocate pkg mem\n");
+		return NULL;
+	}
+
+	info->table.s = info->buf;
+	info->table.len = table->len;
+	memcpy(info->table.s, table->s, table->len);
+	info->table.s[table->len] = 0;
+
+	/* append to the begining (we don't care about order) */
+	info->next = registered_tables;
+	registered_tables = info;
+
+	return info;
+}
+/*
+ * Convert char* parameter to str* parameter
+ */
+static int authdb_fixup(void** param, int param_no)
+{
+	fparam_t* p;
+
+	if (param_no == 1) {
+		return fixup_var_str_12(param, param_no);
+	} else if (param_no == 2) {
+		if (fixup_var_str_12(param, param_no) < 0) return -1;
+		p = (fparam_t*)(*param);
+		if (p->type == FPARAM_STR) {
+			*param = register_table(&p->v.str);
+			if (!*param) {
+				ERR("can't register table %.*s\n", p->v.str.len, p->v.str.s);
+				return -1;
+			}
+		} else {
+			ERR("Non-string value of table with credentials is not allowed.\n");
+			/* TODO: allow this too */
+			return -1;
+		}
+	}
+
+    return 0;
+}

+ 82 - 0
modules_s/auth_db/authdb_mod.h

@@ -0,0 +1,82 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Database 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef AUTHDB_MOD_H
+#define AUTHDB_MOD_H
+
+#include "../../str.h"
+#include "../../lib/srdb2/db.h"
+#include "../auth/api.h"
+#include "../sl/sl.h"
+#include "../../parser/msg_parser.h"
+
+
+/*
+ * Module parameters variables
+ */
+extern str username_column; /* 'username' column name */
+extern str did_column;      /* 'did' column name */
+extern str realm_column;    /* 'realm' column name */
+extern str pass_column;     /* 'password' column name */
+extern str pass_column_2;   /* Column containing HA1 string constructed
+			     * of user@domain username
+			     */
+extern str flags_column;    /* Flags column in credentials table */
+
+extern int calc_ha1;          /* if set to 1, ha1 is calculated by the server */
+extern int use_did;           /* Whether query should also use did in query */
+extern int check_all;         /* if set to 1, multiple db entries are checked */
+
+extern db_ctx_t* auth_db_handle; /* database connection handle */
+
+extern auth_api_t auth_api;
+
+extern str* credentials;
+extern int credentials_n;
+
+
+/*
+ * Pointer to reply function in stateless module
+ */
+extern sl_api_t sl;
+
+/* structure holding information for a table (holds
+ * only pregenerated DB queries now) */
+typedef struct _authdb_table_info_t {
+	str table; /* s is zero terminated */
+	db_cmd_t *query_pass; /* queries HA1 */
+	db_cmd_t *query_pass2; /* queries HA1B */
+	db_cmd_t *query_password; /* queries plain password */
+
+	struct _authdb_table_info_t *next;
+	char buf[1]; /* used to hold 'table' data */
+} authdb_table_info_t;
+
+#endif /* AUTHDB_MOD_H */

+ 470 - 0
modules_s/auth_db/authorize.c

@@ -0,0 +1,470 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Database 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * history:
+ * ---------
+ * 2003-02-28 scratchpad compatibility abandoned
+ * 2003-01-27 next baby-step to removing ZT - PRESERVE_ZT (jiri)
+ * 2004-06-06 updated to the new DB api, added auth_db_{init,bind,close,ver}
+ *             (andrei)
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "../../ut.h"
+#include "../../str.h"
+#include "../../lib/srdb2/db.h"
+#include "../../dprint.h"
+#include "../../parser/digest/digest.h"
+#include "../../parser/hf.h"
+#include "../../parser/parser_f.h"
+#include "../../usr_avp.h"
+#include "../../mem/mem.h"
+#include "../../config.h"
+#include "../../id.h"
+#include "../auth/api.h"
+#include "authdb_mod.h"
+
+
+#define IS_NULL(f)	((f).flags & DB_NULL)
+
+static inline int get_ha1(struct username* username, str* did, str* realm,
+			  authdb_table_info_t *table_info, char* ha1, db_res_t** res, db_rec_t** row)
+{
+	str result;
+	db_cmd_t *q = NULL;
+   
+	if (calc_ha1) {
+		q = table_info->query_password;
+		DBG("querying plain password\n");
+	} else {
+		if (username->domain.len) {
+			q = table_info->query_pass2;
+			DBG("querying ha1b\n");
+		} else {
+			q = table_info->query_pass;
+			DBG("querying ha1\n");
+		}
+	}
+    
+	q->match[0].v.lstr = username->user;
+	q->match[1].v.lstr = *realm;
+
+	if (use_did) q->match[2].v.lstr = *did;
+
+	if (db_exec(res, q) < 0 ) {
+		ERR("Error while querying database\n");
+		return -1;
+	}
+
+	if (*res) *row = db_first(*res);
+	else *row = NULL;
+	while (*row) {
+		if (IS_NULL((*row)->fld[0]) || IS_NULL((*row)->fld[1])) {
+			LOG(L_ERR, "auth_db:get_ha1: Credentials for '%.*s'@'%.*s' contain NULL value, skipping\n",
+				username->user.len, ZSW(username->user.s), realm->len, ZSW(realm->s));
+		} else {
+			if ((*row)->fld[1].v.int4 & SRDB_DISABLED) {
+				/* disabled rows ignored */
+			} else {
+				if ((*row)->fld[1].v.int4 & SRDB_LOAD_SER) {
+					/* *row = i; */
+					break;
+				}
+			}
+		}
+		*row = db_next(*res);
+	}
+
+	if (!*row) {
+		DBG("auth_db:get_ha1: Credentials for '%.*s'@'%.*s' not found\n",
+			username->user.len, ZSW(username->user.s), realm->len, ZSW(realm->s));
+		return 1;
+	}		
+
+	result.s = (*row)->fld[0].v.cstr;
+	result.len = strlen(result.s);
+
+	if (calc_ha1) {
+		/* Only plaintext passwords are stored in database,
+		 * we have to calculate HA1 */
+		auth_api.calc_HA1(HA_MD5, &username->whole, realm, &result, 0, 0, ha1);
+		DBG("auth_db:get_ha1: HA1 string calculated: %s\n", ha1);
+	} else {
+		memcpy(ha1, result.s, result.len);
+		ha1[result.len] = '\0';
+	}
+
+	return 0;
+}
+
+/*
+ * Calculate the response and compare with the given response string
+ * Authorization is successful if this two strings are same
+ */
+static inline int check_response(dig_cred_t* cred, str* method, char* ha1)
+{
+	HASHHEX resp, hent;
+    
+	/*
+	 * First, we have to verify that the response received has
+	 * the same length as responses created by us
+	 */
+	if (cred->response.len != 32) {
+		DBG("auth_db:check_response: Receive response len != 32\n");
+		return 1;
+	}
+    
+	/*
+	 * Now, calculate our response from parameters received
+	 * from the user agent
+	 */
+	auth_api.calc_response(ha1, &(cred->nonce), 
+				  &(cred->nc), &(cred->cnonce), 
+				  &(cred->qop.qop_str), cred->qop.qop_parsed == QOP_AUTHINT,
+				  method, &(cred->uri), hent, resp);
+    
+	DBG("auth_db:check_response: Our result = \'%s\'\n", resp);
+    
+	/*
+	 * And simply compare the strings, the user is
+	 * authorized if they match
+	 */
+	if (!memcmp(resp, cred->response.s, 32)) {
+		DBG("auth_db:check_response: Authorization is OK\n");
+		return 0;
+	} else {
+		DBG("auth_db:check_response: Authorization failed\n");
+		return 2;
+	}
+}
+
+
+/*
+ * Generate AVPs from the database result
+ */
+static int generate_avps(db_res_t* result, db_rec_t *row)
+{
+	int i;
+	int_str iname, ivalue;
+	str value;
+	char buf[32];
+    
+	for (i = 2; i < credentials_n + 2; i++) {
+		value = row->fld[i].v.lstr;
+
+		if (IS_NULL(row->fld[i]))
+			continue;
+
+		switch (row->fld[i].type) {
+		case DB_STR:
+			value = row->fld[i].v.lstr;
+			break;
+
+		case DB_INT:
+			value.len = sprintf(buf, "%d", row->fld[i].v.int4);
+			value.s = buf;
+			break;
+
+		default:
+			abort();
+			break;
+		}
+
+		if (value.s == NULL)
+			continue;
+
+		iname.s = credentials[i - 2];
+		ivalue.s = value;
+
+		if (add_avp(AVP_NAME_STR | AVP_VAL_STR | AVP_CLASS_USER, iname, ivalue) < 0) {
+			LOG(L_ERR, "auth_db:generate_avps: Error while creating AVPs\n");
+			return -1;
+		}
+
+		DBG("auth_db:generate_avps: set string AVP \'%.*s = %.*s\'\n",
+			iname.s.len, ZSW(iname.s.s), value.len, ZSW(value.s));
+	}
+    
+	return 0;
+}
+
+/* this is a dirty work around to check the credentials of all users,
+ * if the database query returned more then one result
+ *
+ * Fills res (which must be db_free'd afterwards if the call was succesfull)
+ * returns  0 on success, 1 on no match (?)
+ *          and -1 on error (memory, db a.s.o).
+ * WARNING: if -1 is returned res _must_ _not_ be freed (it's empty)
+ *
+ */
+static inline int check_all_ha1(struct sip_msg* msg, struct hdr_field* hdr, 
+		dig_cred_t* dig, str* method, str* did, str* realm, 
+		authdb_table_info_t *table_info, db_res_t** res) 
+{
+	char ha1[256];
+	db_rec_t *row;
+	str result;
+	db_cmd_t *q;
+   
+	if (calc_ha1) {
+		q = table_info->query_password;
+		DBG("querying plain password\n");
+	}
+	else {
+	    if (dig->username.domain.len) {
+			q = table_info->query_pass2;
+			DBG("querying ha1b\n");
+		}
+		else {
+			q = table_info->query_pass;
+			DBG("querying ha1\n");
+		}
+	}
+    
+	q->match[0].v.lstr = dig->username.user;
+	if (dig->username.domain.len) 
+		q->match[1].v.lstr = dig->username.domain;
+	else
+		q->match[1].v.lstr = *realm;
+
+	if (use_did) q->match[2].v.lstr = *did;
+
+	if (db_exec(res, q) < 0 ) {
+		ERR("Error while querying database\n");
+	}
+
+	if (*res) row = db_first(*res);
+	else row = NULL;
+	while (row) {
+		if (IS_NULL(row->fld[0]) || IS_NULL(row->fld[1])) {
+			LOG(L_ERR, "auth_db:check_all_ha1: Credentials for '%.*s'@'%.*s' contain NULL value, skipping\n",
+			    dig->username.user.len, ZSW(dig->username.user.s), realm->len, ZSW(realm->s));
+		}
+		else {
+			if (row->fld[1].v.int4 & SRDB_DISABLED) {
+				/* disabled rows ignored */
+			}
+			else {
+				if (row->fld[1].v.int4 & SRDB_LOAD_SER) {
+					result.s = row->fld[0].v.cstr;
+					result.len = strlen(result.s);
+					if (calc_ha1) {
+						 /* Only plaintext passwords are stored in database,
+						  * we have to calculate HA1 */
+						auth_api.calc_HA1(HA_MD5, &(dig->username.whole), realm, &result, 0, 0, ha1);
+						DBG("auth_db:check_all_ha1: HA1 string calculated: %s\n", ha1);
+					} else {
+						memcpy(ha1, result.s, result.len);
+						ha1[result.len] = '\0';
+					}
+
+					if (!check_response(dig, method, ha1)) {
+						if (auth_api.post_auth(msg, hdr) == AUTHENTICATED) {
+							generate_avps(*res, row);
+							return 0;
+						}
+					}
+				}
+			}
+		}
+		row = db_next(*res);
+	}
+
+	if (!row) {
+		DBG("auth_db:check_all_ha1: Credentials for '%.*s'@'%.*s' not found",
+		    dig->username.user.len, ZSW(dig->username.user.s), realm->len, ZSW(realm->s));
+	}		
+	return 1;
+
+
+}
+
+
+/*
+ * Authenticate digest credentials
+ * Returns:
+ *      -3 -- Bad Request
+ *      -2 -- Error while checking credentials (such as malformed message or database problem)
+ *      -1 -- Authentication failed
+ *       1 -- Authentication successful
+ */
+static inline int authenticate(struct sip_msg* msg, str* realm, authdb_table_info_t *table, hdr_types_t hftype)
+{
+	char ha1[256];
+	int res, ret;
+	db_rec_t *row;
+	struct hdr_field* h;
+	auth_body_t* cred;
+	db_res_t* result;
+	str did;
+    
+	cred = 0;
+	result = 0;
+	ret = -1;
+    
+	switch(auth_api.pre_auth(msg, realm, hftype, &h, NULL)) {
+	case ERROR:
+	case BAD_CREDENTIALS:
+		ret = -3;
+		goto end;
+	case CREATE_CHALLENGE:
+		ERR("auth_db:authenticate: CREATE_CHALLENGE is not a valid state\n");
+		ret = -2;
+		goto end;
+	case DO_RESYNCHRONIZATION:
+		ERR("auth_db:authenticate: DO_RESYNCHRONIZATION is not a valid state\n");
+		ret = -2;
+		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 (check_all) {
+		res = check_all_ha1(msg, h, &(cred->digest), &msg->first_line.u.request.method, &did, realm, table, &result);
+		if (res < 0) {
+			ret = -2;
+			goto end;
+		}
+		else if (res > 0) {
+			ret = -1;
+			goto end;
+		}
+		else {
+			ret = 1;
+			goto end;
+		}
+    	} else {
+		res = get_ha1(&cred->digest.username, &did, realm, table, ha1, &result, &row);
+		if (res < 0) {
+			ret = -2;
+			goto end;
+		}
+		if (res > 0) {
+			/* Username not found in the database */
+			ret = -1;
+			goto end;
+        	}
+    	}
+    
+	/* Recalculate response, it must be same to authorize successfully */
+	if (!check_response(&(cred->digest), &msg->first_line.u.request.method, ha1)) {
+		switch(auth_api.post_auth(msg, h)) {
+		case ERROR:
+		case BAD_CREDENTIALS:
+			ret = -2; 
+			break;
+			
+		case NOT_AUTHENTICATED: 
+			ret = -1; 
+			break;
+			
+		case AUTHENTICATED:
+			generate_avps(result, row);
+			ret = 1;
+			break;
+			
+		default:
+			ret = -1;
+			break;
+		}
+	} else {
+		ret = -1;
+	}
+
+ end:
+	if (result) db_res_free(result);
+	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;
+}
+
+
+/*
+ * Authenticate using Proxy-Authorize header field
+ */
+int proxy_authenticate(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, (authdb_table_info_t*)p2, HDR_PROXYAUTH_T);
+}
+
+
+/*
+ * Authorize using WWW-Authorize header field
+ */
+int www_authenticate(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, (authdb_table_info_t*)p2, HDR_AUTHORIZATION_T);
+}

+ 52 - 0
modules_s/auth_db/authorize.h

@@ -0,0 +1,52 @@
+/*
+ * $Id$
+ *
+ * Digest Authentication - Database 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef AUTHORIZE_H
+#define AUTHORIZE_H
+
+
+#include "../../parser/msg_parser.h"
+
+int auth_db_init(char* db_url);
+int auth_db_bind(char* db_url);
+void auth_db_close();
+
+/*
+ * Authorize using Proxy-Authorization header field
+ */
+int proxy_authenticate(struct sip_msg* msg, char* realm, char* table);
+
+
+/*
+ * Authorize using WWW-Authorization header field
+ */
+int www_authenticate(struct sip_msg* msg, char* realm, char* table);
+
+#endif /* AUTHORIZE_H */

+ 29 - 0
modules_s/auth_db/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = auth_db
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 79 - 0
modules_s/auth_db/doc/auth_db.xml

@@ -0,0 +1,79 @@
+<?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_db" 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>Jakob</firstname>
+		<surname>Schlyter</surname>
+		<email>[email protected]</email>
+	    </author>
+	</authorgroup>
+
+	<copyright>
+	    <year>2002</year>
+	    <year>2003</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+    <title>Auth_db Module</title>
+    
+    <section id="auth_db.overview">
+	<title>Overview</title>
+	<para>
+	    This module contains all authentication related functions that need
+	    the access to the database. This module should be used together
+	    with auth module, it cannot be used independently because it
+	    depends on the module. Select this module if you want to use
+	    database to store authentication information like subscriber
+	    usernames and passwords. If you want to use radius authentication,
+	    then use auth_radius instead.
+	</para>
+    </section>
+
+    <section id="auth_db.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>
+		<listitem>
+		    <formalpara>
+			<title>database</title>
+			<para>
+			    Any database module (currently mysql, postgres, dbtext)
+			</para>
+		    </formalpara>
+		</listitem>
+	    </itemizedlist>
+	</para>
+    </section>
+    
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+
+</section>

+ 112 - 0
modules_s/auth_db/doc/functions.xml

@@ -0,0 +1,112 @@
+<?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_db.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Functions</title>
+
+    <section id="www_authorize">
+	<title><function>www_authorize(realm, table)</function></title>
+	<para>
+	    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 <function>www_challenge</function> which
+	    will challenge the user again.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<formalpara>
+		    <title>realm</title>
+		    <para>
+			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.
+		    </para>
+		</formalpara>
+	    </listitem>
+	    <listitem>
+		<formalpara>
+		    <title>table</title>
+		    <para>
+			Table to be used to lookup usernames and passwords
+			(usually subscribers table).
+		    </para>
+		</formalpara>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function moreinfo="none">www_authorize</function> usage</title>
+	    <programlisting>
+...
+if (www_authorize("iptel.org", "subscriber")) {
+    www_challenge("iptel.org", "1");
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="proxy_authorize">
+	<title><function>proxy_authorize(realm, table)</function></title>
+	<para>
+	    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 <function>proxy_challenge</function>
+	    which will challenge the user again.
+	</para>
+	<para>Meaning of the parameters 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>
+	    <listitem>
+		<para>
+		    <emphasis>table</emphasis> - Table to be used to lookup
+		    usernames and passwords (usually subscribers table).
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title>proxy_authorize usage</title>
+	    <programlisting>
+...
+if (!proxy_authorize("", "subscriber)) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 210 - 0
modules_s/auth_db/doc/params.xml

@@ -0,0 +1,210 @@
+<?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_db.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="auth_db.db_url">
+	<title><varname>db_url</varname> (string)</title>
+	<para>
+	    This is URL of the database to be used. Value of the parameter
+	    depends on the database module used. For example for mysql and
+	    postgres modules this is something like
+	    mysql://username:password@host:port/database. For dbtext module
+	    (which stores data in plaintext files) it is directory in which the
+	    database resides.
+	</para>
+	<para>
+	    Default value is "mysql://serro:47serro11@localhost/ser".
+	</para>
+	<example>
+	    <title><varname>db_url</varname> parameter usage</title>
+	    <programlisting>
+modparam("auth_db", "db_url", "mysql://foo:[email protected]/ser")
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="user_column">
+	<title><varname>user_column</varname> (string)</title>
+	<para>
+	    This is the name of the column holding usernames. Default value is
+	    fine for most people. Use the parameter if you really need to
+	    change it.
+	</para>
+	<para>
+	    Default value is "username".
+	</para>
+
+	<example>
+	    <title><varname>user_column</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "user_column", "user")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="domain_column">
+	<title><varname>domain_column</varname> (string)</title>
+	<para>
+	    This is the name of the column holding domains of users. Default
+	    value is fine for most people. Use the parameter if you really need
+	    to change it.
+	</para>
+	<para>
+	    Default value is "domain".
+	</para>
+	<example>
+	    <title><varname>domain_column</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "domain_column", "domain")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="password_column">
+	<title><varname>password_column</varname> (string)</title>
+	<para>
+	    This is the name of the column holding passwords. Passwords can be
+	    either stored as plain text or pre-calculated HA1 strings. HA1
+	    strings are MD5 hashes of username, password, and realm. HA1
+	    strings are more safe because the server doesn't need to know
+	    plaintext passwords and they cannot be obtained from HA1 strings.
+	</para>
+	<para>
+	    Default value is "ha1".
+	</para>
+	<example>
+	    <title><varname>password_column</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "password_column", "password")
+	    </programlisting>
+	    </example>
+    </section>
+
+    <section id="rpid_column">
+	<title><varname>rpid_column</varname> (string)</title>
+	<para>
+	    This is the name of the column holding information for the
+	    Remote-Party-ID header field. Default value is fine for most
+	    people. Use the parameter if you really need to change it.
+	</para>
+	<para>
+	    Default value is "rpid".
+	</para>
+	<example>
+	    <title><varname>rpid_column</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "rpid_column", "remote_party_id")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="calculate_ha1">
+	<title><varname>calculate_ha1</varname> (integer)</title>
+	<para>
+	    This parameter tells server whether it should read plaintext
+	    password from the database or HA1 string. If the parameter is set to
+	    1 then the server will assume that the column pointed to by
+	    <varname>plain_password_column</varname> contains plaintext passwords and
+	    it will calculate HA1 strings on the fly.
+	</para>
+	<para>
+	    If the parameter is set to 0 then the server assumes that the
+	    database contains HA1 strings directly and will not calculate
+	    them. In this case it will use value of <varname>password_column</varname>
+		as name of column with HA1 password.
+		If username parameter of credentials contains also @domain
+	    (some user agents put domain in username parameter), then column
+	    pointed to by <varname>password_column_2</varname> parameter will
+	    be used instead. This column should also contain HA1 strings but
+	    they should be calculated including the domain in the username
+	    parameter (as opposed to <varname>password_column</varname> which
+	    (when containing HA1 strings) should always contains HA1 strings
+	    calculated without domain in username.
+	</para>
+	<para>
+	    This ensures that the authentication will always work when using
+	    pre-calculated HA1 string, not depending on the presence of the
+	    domain in username.
+	</para>
+	<para>
+	    Default value of this parameter is 0.
+	</para>
+	<example>
+	    <title><varname>calculate_ha1</varname>usage</title>
+	    <programlisting>
+modparam("auth_db", "calculate_ha1", 1)
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="plain_password_column">
+	<title><varname>plain_password_column</varname> (string)</title>
+	<para>
+		This parameter holds the name of column holding plain text password.
+		This column is used when calculate_ha1 is set.
+	</para>
+	<para>
+	    Default value is "password".
+	</para>
+	<example>
+	    <title><varname>plain_password_column</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "plain_password_column", "password")
+	    </programlisting>
+	    </example>
+    </section>
+
+    <section id="password_column_2">
+	<title><varname>password_column_2</varname> (string)</title>
+	<para>
+	    As described in the previous section this parameter contains name
+	    of column holding pre-calculated HA1 string that were calculated
+	    including the domain in the username. This parameter is used only
+	    when <varname>calculate_ha1</varname> is set to 0 and user agent
+	    send a credentials containing the domain in the username.
+	</para>
+	<para>
+	    Default value of the parameter is ha1b.
+	</para>
+	<example>
+	    <title><varname>password_column_2</varname> usage</title>
+	    <programlisting>
+modparam("auth_db", "password_column_2", "ha1_2")
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="use_rpid">
+	<title><varname>use_rpid</varname> (integer)</title>
+	<para>
+	    This parameter specifies whether the server should fetch a value
+	    for the Remote-Party-ID header field from the database.
+	</para>
+	<para>
+	    If the parameter is set to 1 the server expects to find a value for
+	    this header in the column specified by the
+	    <varname>rpid_column</varname> parameter.
+	</para>
+	<para>
+	    Default value of this parameter is 0.
+	</para>
+	<example>
+	    <title><varname>use_rpid</varname>usage</title>
+	    <programlisting>
+modparam("auth_db", "use_rpid", 1)
+	    </programlisting>
+	</example>
+    </section>
+</section>

+ 20 - 0
modules_s/auth_identity/Makefile

@@ -0,0 +1,20 @@
+
+include ../../Makefile.defs
+
+auto_gen=
+NAME=auth_identity.so
+
+DEFS+= -Wall -I$(LOCALBASE)/ssl/include
+#
+# Dynamic linking
+#
+LIBS+= -L$(LOCALBASE)/lib -L$(LOCALBASE)/ssl/lib -lssl -lcrypto -lcurl
+
+#
+# Static linking, if you'd like to use TLS and AUTH_IDENTITY at the same time
+#
+#LIBS+= /usr/lib/libcurl.a /usr/lib/libssl.a /usr/lib/libcrypto.a -lkrb5 -lidn -lz -lgssapi_krb5
+
+DEFS+=-DSER_MOD_INTERFACE
+
+include ../../Makefile.modules

+ 409 - 0
modules_s/auth_identity/auth_crypt.c

@@ -0,0 +1,409 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "../../mem/mem.h"
+#include "../../parser/parse_uri.h"
+
+#include "auth_identity.h"
+
+
+
+int retrieve_x509(X509 **pcert, str *scert, int bacceptpem)
+{
+	BIO *bcer=NULL;
+	char serr[160];
+	int iRet=0;
+
+
+	if (!(bcer=BIO_new(BIO_s_mem()))) {
+		LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to create BIO\n");
+
+		return -1;
+	}
+
+	do {
+		if (BIO_write(bcer, scert->s, scert->len)!=scert->len) {
+			LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: Unable to write BIO\n");
+			iRet=-2;
+			break;
+		}
+
+		/* RFC 4474 only accepts certs in the DER form but it can not harm
+		 * to be a little bit more flexible and accept PEM as well. */
+		if (bacceptpem
+		  	&& scert->len > BEGIN_PEM_CERT_LEN
+			&& memmem(scert->s,
+					  scert->len,
+					  BEGIN_PEM_CERT,
+					  BEGIN_PEM_CERT_LEN)) {
+			if (!(*pcert = PEM_read_bio_X509(bcer, NULL, NULL, NULL))) {
+				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
+				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: PEM Certificate %s\n", serr);
+				iRet=-4;
+			}
+		} else {
+			if (!(*pcert = d2i_X509_bio(bcer, NULL))) {
+				ERR_error_string_n(ERR_get_error(), serr, sizeof(serr));
+				LOG(L_ERR, "AUTH_IDENTITY:retrieve_x509: DER Certificate %s\n", serr);
+				iRet=-3;
+			}
+		}
+	} while (0);
+
+	BIO_free(bcer);
+
+	return iRet;
+}
+
+int check_x509_subj(X509 *pcert, str* sdom)
+{
+	STACK_OF(GENERAL_NAME) *altnames;
+	int ialts, i1, ilen, altlen;
+	const GENERAL_NAME *actname;
+	char scname[AUTH_DOMAIN_LENGTH];
+	char *altptr;
+	struct sip_uri suri;
+	int ret = 0;
+
+
+	/* we're looking for subjectAltName for the first time */
+	altnames = X509_get_ext_d2i(pcert, NID_subject_alt_name, NULL, NULL);
+
+	if (altnames) {
+		ialts = sk_GENERAL_NAME_num(altnames);
+
+		for (i1=0; i1 < ialts; i1++) {
+			actname = sk_GENERAL_NAME_value(altnames, i1);
+
+			if (actname->type == GEN_DNS || actname->type == GEN_URI) {
+				/* we've found one */
+				altptr = (char *)ASN1_STRING_data(actname->d.ia5);
+				if (actname->type == GEN_URI) {
+					if (parse_uri(altptr, strlen(altptr), &suri) != 0) {
+						continue;
+					}
+					if (!(suri.type == SIP_URI_T || suri.type == SIPS_URI_T)) {
+						continue;
+					}
+					if (suri.user.len != 0 || suri.passwd.len != 0) {
+						continue;
+					}
+					altptr = suri.host.s;
+					altlen = suri.host.len;
+				} else {
+					altlen = strlen(altptr);
+				}
+				if (sdom->len != altlen 
+					|| strncasecmp(altptr, sdom->s, sdom->len)) {
+					LOG(L_INFO, "AUTH_IDENTITY VERIFIER: subAltName of certificate doesn't match host name\n");
+					ret = -1;
+				} else {
+					ret = 1;
+					break;
+				}
+			}
+		}
+		GENERAL_NAMES_free(altnames);
+	}
+
+	if (ret != 0) {
+		return ret == 1 ? 0 : ret;
+ 	}
+
+	/* certificate supplier host and certificate subject match check */
+	ilen=X509_NAME_get_text_by_NID (X509_get_subject_name (pcert),
+									NID_commonName,
+									scname,
+									sizeof (scname));
+	if (sdom->len != ilen || strncasecmp(scname, sdom->s, sdom->len)) {
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: common name of certificate doesn't match host name\n");
+		return -2;
+	}
+
+	return 0;
+}
+
+int verify_x509(X509 *pcert, X509_STORE *pcacerts)
+{
+	X509_STORE_CTX ca_ctx;
+	char *strerr;
+
+
+	if (X509_STORE_CTX_init(&ca_ctx, pcacerts, pcert, NULL) != 1) {
+		LOG(L_ERR, "AUTH_IDENTITY:verify_x509: Unable to init X509 store ctx\n");
+		return -1;
+	}
+
+	if (X509_verify_cert(&ca_ctx) != 1) {
+		strerr = (char *) X509_verify_cert_error_string(ca_ctx.error);
+		LOG(L_ERR, "AUTH_IDENTITY VERIFIER: Certificate verification error: %s\n", strerr);
+		X509_STORE_CTX_cleanup(&ca_ctx);
+		return -2;
+	}
+	X509_STORE_CTX_cleanup(&ca_ctx);
+
+	LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Certificate is valid\n");
+
+	return 0;
+}
+
+int rsa_sha1_enc (dynstr *sdigeststr,
+				  dynstr *senc,
+				  dynstr *sencb64,
+				  RSA *hmyprivkey)
+{
+	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
+	int ires;
+	char serr[160];
+
+
+	SHA1((unsigned char*)getstr_dynstr(sdigeststr).s,
+		 getstr_dynstr(sdigeststr).len,
+		 sstrcrypted);
+
+#ifdef NEW_RSA_PROC
+	ires = senc->size;
+	if (RSA_sign(NID_sha1,
+			 	 sstrcrypted,
+			 	 sizeof sstrcrypted,
+				 (unsigned char*)getstr_dynstr(senc).s,
+				 (unsigned int*)&ires,
+			 	 hmyprivkey) != 1) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
+		return -2;
+	}
+#else
+	ires=RSA_private_encrypt(sizeof sstrcrypted, sstrcrypted,
+							 (unsigned char*)getstr_dynstr(senc).s, hmyprivkey,
+							 RSA_PKCS1_PADDING );
+	if (ires<0)
+	{
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:rsa_sha1_enc: '%s'\n", serr);
+		return -1;
+	}
+#endif
+
+	base64encode(getstr_dynstr(senc).s, senc->size, getstr_dynstr(sencb64).s, &getstr_dynstr(sencb64).len );
+
+	return 0;
+}
+
+int rsa_sha1_dec (char *sencedsha, int iencedshalen,
+				  char *ssha, int sshasize, int *ishalen,
+				  X509 *pcertx509)
+{
+	EVP_PKEY *pkey;
+	RSA* hpubkey;
+	unsigned long lerr;
+	char serr[160];
+
+
+	pkey=X509_get_pubkey(pcertx509);
+	if (pkey == NULL) {
+		lerr=ERR_get_error(); ERR_error_string_n(lerr, serr, sizeof(serr));
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Pubkey %s\n", serr);
+		return -1;
+	}
+
+	X509_free(pcertx509);
+
+	hpubkey = EVP_PKEY_get1_RSA(pkey);
+	EVP_PKEY_free(pkey);
+	if (hpubkey == NULL) {
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Error getting RSA key\n");
+		return -2;
+	}
+
+#ifdef NEW_RSA_PROC
+	if (RSA_verify(NID_sha1,
+		 			(unsigned char*)ssha, sshasize,
+					(unsigned char*)sencedsha, iencedshalen,
+					hpubkey) != 1) {
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify returned: '%s'\n", ERR_error_string(ERR_get_error(), NULL));
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: RSA verify failed -> Invalid Identity Header\n");
+		RSA_free(hpubkey);
+		return -5;
+	}
+#else
+	/* it is bigger than the output buffer */
+	if (RSA_size(hpubkey) > sshasize) {
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: Unexpected Identity hash length (%d > %d)\n", RSA_size(hpubkey), sshasize);
+		RSA_free(hpubkey);
+		return -3;
+	}
+	*ishalen=RSA_public_decrypt(iencedshalen,
+								(unsigned char*)sencedsha,
+								(unsigned char*)ssha,
+								hpubkey,
+								RSA_PKCS1_PADDING);
+	if (*ishalen<=0) {
+		lerr=ERR_get_error(); ERR_error_string_n(lerr, serr, sizeof(serr));
+		LOG(L_ERR, "AUTH_IDENTITY:decrypt_identity: RSA operation error %s\n", serr);
+		RSA_free(hpubkey);
+		return -4;
+	}
+#endif
+
+	RSA_free(hpubkey);
+
+	return 0;
+}
+
+/* copypasted from ser/modules/rr/avp_cookie.c + this adds '=' sign! ) */
+void base64encode(char* src_buf, int src_len, char* tgt_buf, int* tgt_len) {
+	static char code64[64+1] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+	int pos;
+	for (pos=0, *tgt_len=0; pos < src_len; pos+=3,*tgt_len+=4) {
+		tgt_buf[*tgt_len+0] = code64[(unsigned char)src_buf[pos+0] >> 2];
+		tgt_buf[*tgt_len+1] = code64[(((unsigned char)src_buf[pos+0] & 0x03) << 4) | ((pos+1 < src_len)?((unsigned char)src_buf[pos+1] >> 4):0)];
+		if (pos+1 < src_len)
+			tgt_buf[*tgt_len+2] = code64[(((unsigned char)src_buf[pos+1] & 0x0F) << 2) | ((pos+2 < src_len)?((unsigned char)src_buf[pos+2] >> 6):0)];
+		else
+			tgt_buf[*tgt_len+2] = '=';
+		if (pos+2 < src_len)
+			tgt_buf[*tgt_len+3] = code64[(unsigned char)src_buf[pos+2] & 0x3F];
+		else
+			tgt_buf[*tgt_len+3] = '=';
+	}
+}
+
+
+/* copypasted from ser/modules/rr/avp_cookie.c */
+void base64decode(char* src_buf, int src_len, char* tgt_buf, int* tgt_len) {
+	int pos, i, n;
+	unsigned char c[4];
+	for (pos=0, i=0, *tgt_len=0; pos < src_len; pos++) {
+		if (src_buf[pos] >= 'A' && src_buf[pos] <= 'Z')
+			c[i] = src_buf[pos] - 65;   /* <65..90>  --> <0..25> */
+		else if (src_buf[pos] >= 'a' && src_buf[pos] <= 'z')
+			c[i] = src_buf[pos] - 71;   /* <97..122>  --> <26..51> */
+		else if (src_buf[pos] >= '0' && src_buf[pos] <= '9')
+			c[i] = src_buf[pos] + 4;    /* <48..56>  --> <52..61> */
+		else if (src_buf[pos] == '+')
+			c[i] = 62;
+		else if (src_buf[pos] == '/')
+			c[i] = 63;
+		else  /* '=' */
+			c[i] = 64;
+		i++;
+		if (pos == src_len-1) {
+			while (i < 4) {
+				c[i] = 64;
+				i++;
+			}
+		}
+		if (i==4) {
+			if (c[0] == 64)
+				n = 0;
+			else if (c[2] == 64)
+				n = 1;
+			else if (c[3] == 64)
+				n = 2;
+			else
+				n = 3;
+			switch (n) {
+				case 3:
+					tgt_buf[*tgt_len+2] = (char) (((c[2] & 0x03) << 6) | c[3]);
+					/* no break */
+				case 2:
+					tgt_buf[*tgt_len+1] = (char) (((c[1] & 0x0F) << 4) | (c[2] >> 2));
+					/* no break */
+				case 1:
+					tgt_buf[*tgt_len+0] = (char) ((c[0] << 2) | (c[1] >> 4));
+					break;
+			}
+			i=0;
+			*tgt_len+= n;
+		}
+	}
+}
+
+int x509_get_validitytime(time_t *tout, ASN1_UTCTIME *tin)
+{
+	char *sasn1;
+	int i1;
+	struct tm tmptm;
+
+
+	memset(&tmptm, 0, sizeof(tmptm));
+	i1=tin->length;
+	sasn1=(char *)tin->data;
+
+	if (i1 < 10)
+		return -1;
+/*	if (sasn1[i1-1]!='Z')
+		return -1;*/
+	for (i1=0; i1<10; i1++)
+		if((sasn1[i1] > '9') || (sasn1[i1] < '0'))
+			return -2;
+
+	tmptm.tm_year=(sasn1[0]-'0')*10+(sasn1[1]-'0');
+	if(tmptm.tm_year < 50)
+		tmptm.tm_year+=100;
+
+	tmptm.tm_mon=(sasn1[2]-'0')*10+(sasn1[3]-'0')-1;
+	if((tmptm.tm_mon > 11) || (tmptm.tm_mon < 0))
+		return -3;
+
+	tmptm.tm_mday=(sasn1[4]-'0')*10+(sasn1[5]-'0');
+	tmptm.tm_hour= (sasn1[6]-'0')*10+(sasn1[7]-'0');
+	tmptm.tm_min=(sasn1[8]-'0')*10+(sasn1[9]-'0');
+
+	if ((sasn1[10] >= '0') && (sasn1[10] <= '9') &&
+		   (sasn1[11] >= '0') && (sasn1[11] <= '9'))
+		tmptm.tm_sec=(sasn1[10]-'0')*10+(sasn1[11]-'0');
+
+#ifdef HAVE_TIMEGM
+	*tout=timegm(&tmptm);
+#else
+	*tout=_timegm(&tmptm);
+#endif
+
+	return 0;
+}
+
+int x509_get_notbefore(time_t *tout, X509 *pcert)
+{
+	return (x509_get_validitytime(tout, X509_get_notBefore(pcert)));
+}
+
+int x509_get_notafter(time_t *tout, X509 *pcert)
+{
+	return (x509_get_validitytime(tout, X509_get_notAfter(pcert)));
+}

+ 119 - 0
modules_s/auth_identity/auth_dynstr.c

@@ -0,0 +1,119 @@
+/*
+ * $Id$ 
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <errno.h>
+
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_cseq.h"
+#include "../../parser/parse_content.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/contact/parse_contact.h"
+
+#include "../../data_lump.h"
+#include "../../msg_translator.h"
+#include "auth_identity.h"
+
+/*
+ * Dynamic string functions
+ */
+
+int initdynstr(dynstr *sout, int isize)
+{
+	memset(sout,0,sizeof(*sout));
+	getstr_dynstr(sout).s=pkg_malloc(isize);
+	if (!getstr_dynstr(sout).s) {
+		LOG(L_WARN,
+			"AUTH_IDENTITY:initdynstr: Not enough memory error\n");
+		return -1;
+	}
+	sout->size=isize;
+
+	return 0;
+}
+
+int cpy2dynstr(dynstr *sout, str *s2app)
+{
+	char *stmp;
+	int isize = s2app->len;
+
+	if (isize > sout->size) {
+		stmp=pkg_realloc(sout->sd.s, isize);
+		if (!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:cpy2dynstr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s=stmp;
+		sout->size=isize;
+	}
+
+	memcpy(sout->sd.s,s2app->s,s2app->len);
+	sout->sd.len = isize;
+
+	return 0;
+}
+
+int app2dynchr(dynstr *sout, char capp)
+{
+	char *stmp;
+	int isize = sout->sd.len + 1;
+
+	if (isize > sout->size) {
+		stmp=pkg_realloc(sout->sd.s, isize);
+		if (!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:app2dynchr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s=stmp;
+		sout->size++;
+	}
+
+	sout->sd.s[sout->sd.len]=capp;
+	sout->sd.len++;
+
+	return 0;
+}
+
+int app2dynstr(dynstr *sout, str *s2app)
+{
+	char *stmp;
+	int isize = sout->sd.len + s2app->len;
+
+	if (isize > sout->size) {
+		stmp=pkg_realloc(sout->sd.s, isize);
+		if (!stmp) {
+			LOG(L_ERR, "AUTH_IDENTITY:app2dynstr: Not enough memory error\n");
+			return -1;
+		}
+		sout->sd.s=stmp;
+		sout->size=isize;
+	}
+
+	memcpy(&sout->sd.s[sout->sd.len],s2app->s,s2app->len);
+	sout->sd.len = isize;
+
+	return 0;
+}

+ 739 - 0
modules_s/auth_identity/auth_hdrs.c

@@ -0,0 +1,739 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <errno.h>
+
+#include "../../parser/parser_f.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_cseq.h"
+#include "../../parser/parse_content.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/keys.h"
+#include "../../parser/contact/parse_contact.h"
+
+#include "../../modules/tm/ut.h"
+#include "../../data_lump.h"
+#include "../../msg_translator.h"
+#include "auth_identity.h"
+
+
+struct hdr_field glb_contact;
+char *glb_siphdr=NULL;
+char *glb_msgbody=NULL;
+
+static int tohdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int in_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int out_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int in_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static int out_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg);
+static void free_out_contacthdr(void);
+static void free_out_msgbody(void);
+
+
+/* macros from the core parser */
+#define LOWER_BYTE(b) ((b) | 0x20)
+#define LOWER_DWORD(d) ((d) | 0x20202020)
+
+#define READ(val) \
+(*(val + 0) + (*(val + 1) << 8) + (*(val + 2) << 16) + (*(val + 3) << 24))
+
+static char *auth_next_line(char *buf, char *buf_end);
+static inline char* skip_ws(char* p, unsigned int size);
+static char *auth_get_hf_name(char *begin, char *end, enum _hdr_types_t *type);
+static int get_contact_body(char *buf, unsigned int len, str *sout);
+
+
+/*
+ *	Header parsing functions
+ */
+
+/* From */
+int fromhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if ((!msg->from) && (parse_headers(msg, HDR_FROM_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:fromhdr_proc: Error while parsing FROM header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->from) {
+		LOG(L_ERR, "AUTH_IDENTITY:fromhdr_proc: FROM header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_from_header explicitly */
+	if ((!(msg->from)->parsed) && (parse_from_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:fromhdr_proc: Error while parsing FROM body\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=get_from(msg)->uri;
+
+	if (soutopt)
+		*soutopt=get_from(msg)->tag_value;
+
+	return AUTH_OK;
+}
+
+/* To */
+static int tohdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->to && (parse_headers(msg, HDR_TO_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: Error while parsing TO header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->to) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: TO header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	if (!msg->to->parsed) {
+		LOG(L_ERR, "AUTH_IDENTITY:tohdr_proc: TO is not parsed\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=((struct to_body*)msg->to->parsed)->uri;
+
+	return AUTH_OK;
+}
+
+/* Call-ID */
+int callidhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->callid && (parse_headers(msg, HDR_CALLID_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:callidhdr_proc: error while parsing CALLID header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->callid) {
+		LOG(L_ERR, "AUTH_IDENTITY:callidhdr_proc: CALLID header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+
+	if (sout)
+		*sout=msg->callid->body;
+
+	return AUTH_OK;
+}
+
+/* CSeq */
+int cseqhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->cseq && (parse_headers(msg, HDR_CSEQ_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:cseqhdr_proc: Error while parsing CSEQ header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->cseq) {
+		LOG(L_ERR, "AUTH_IDENTITY:cseqhdr_proc: CSEQ header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	if (!msg->cseq->parsed) {
+		LOG(L_ERR, "AUTH_IDENTITY:cseqhdr_proc: CSEQ is not parsed\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=get_cseq(msg)->number;
+	if (soutopt)
+		*soutopt=get_cseq(msg)->method;
+
+	return AUTH_OK;
+}
+
+/* Date */
+int datehdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if ((!msg->date) && (parse_headers(msg, HDR_DATE_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:datehdr_proc: Error while parsing DATE header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->date) {
+		LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY:datehdr_proc: DATE header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_date_header explicitly */
+	if ((!(msg->date)->parsed) && (parse_date_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:datehdr_proc: Error while parsing DATE body\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=msg->date->body;
+
+	return AUTH_OK;
+}
+
+/* Contact header of the incoming SIP message */
+static int in_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->contact && (parse_headers(msg, HDR_CONTACT_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:in_contacthdr_proc: Error while parsing CONTACT header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->contact) {
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_contact explicitly */
+	if (!msg->contact->parsed && (parse_contact(msg->contact) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:in_contacthdr_proc: Error while parsing CONTACT body\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=((contact_body_t*)msg->contact->parsed)->contacts->uri;
+
+	return AUTH_OK;
+}
+
+/* Contact header of the outgoing SIP message */
+static int out_contacthdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	unsigned int ulen;
+	int ierror;
+	struct dest_info dst;
+	int ires;
+
+
+#ifdef USE_DNS_FAILOVER
+	/* get info about outbound socket */
+	if ((uri2dst(NULL, &dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#else
+	if ((uri2dst(&dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#endif
+		|| (dst.send_sock == 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:out_contacthdr_proc: Can't determinate destination socket\n");
+		return -1;
+	}
+
+	/* we save it to global variable because we'll process it later */
+	glb_siphdr=build_only_headers(msg, 1, &ulen, &ierror, &dst);
+
+	if (ierror)
+		return -2;
+
+	memset(&glb_contact, 0, sizeof(glb_contact));
+
+	/* parse_contact() needs only the body element of "struct hdr_field" */
+	ires=get_contact_body(glb_siphdr, ulen, &glb_contact.body);
+	if (ires==AUTH_NOTFOUND) {
+		pkg_free(glb_siphdr); glb_siphdr=NULL;
+		return AUTH_NOTFOUND;
+	}
+	if (ires!=AUTH_OK) {
+		pkg_free(glb_siphdr); glb_siphdr=NULL;
+		return AUTH_ERROR;
+	}
+
+	if (parse_contact(&glb_contact) < 0) {
+		pkg_free(glb_siphdr); glb_siphdr=NULL;
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=((contact_body_t*)glb_contact.parsed)->contacts->uri;
+
+	return AUTH_OK;
+}
+
+/* Identity */
+int identityhdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->identity && (parse_headers(msg, HDR_IDENTITY_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityhdr_proc: Error while parsing IDENTITY header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->identity) {
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_identityinfo_header explicitly */
+	if ((!(msg->identity)->parsed) && (parse_identity_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityhdr_proc: Error while parsing IDENTITY body\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=get_identity(msg)->hash;
+
+	return AUTH_OK;
+}
+
+/* Identity-info */
+int identityinfohdr_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!msg->identity_info && (parse_headers(msg, HDR_IDENTITY_INFO_F, 0) == -1)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: Error while parsing IDENTITY-INFO header\n");
+		return AUTH_ERROR;
+	}
+	if (!msg->identity_info) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: IDENTITY-INFO header field is not found\n");
+		return AUTH_NOTFOUND;
+	}
+	/* we must call parse_identityinfo_header explicitly */
+	if ((!(msg->identity_info)->parsed) && (parse_identityinfo_header(msg) < 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:identityinfohdr_proc: Error while parsing IDENTITY-INFO body\n");
+		return AUTH_ERROR;
+	}
+
+	if (sout)
+		*sout=get_identityinfo(msg)->uri;
+	if (soutopt)
+		*soutopt=get_identityinfo(msg)->domain;
+
+	return AUTH_OK;
+}
+
+/* body of the incoming SIP message */
+static int in_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+	if (!sout)
+		return AUTH_OK;
+
+	sout->s = get_body(msg);
+	if (!sout->s || sout->s[0] == 0) {
+		sout->len = 0;
+	} else {
+		if (!msg->content_length) {
+			LOG(L_ERR, "AUTH_IDENTITY:route_msgbody_proc: no Content-Length header found!\n");
+			return AUTH_ERROR;
+		}
+		sout->len = get_content_length(msg);
+	}
+
+	return AUTH_OK;
+}
+
+/* body of the outgoing SIP message */
+static int out_msgbody_proc(str *sout, str *soutopt, struct sip_msg *msg)
+{
+
+	unsigned int len;
+	int    err;
+	struct dest_info dst;
+	char scontentlen[AUTH_CONTENTLENGTH_LENGTH];
+
+
+	if (!sout)
+		return AUTH_OK;
+
+#ifdef USE_DNS_FAILOVER
+	/* get info about outbound socket */
+	if ((uri2dst(NULL, &dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#else
+	if ((uri2dst(&dst, msg, GET_NEXT_HOP(msg), PROTO_NONE) == 0)
+#endif
+		|| (dst.send_sock == 0)) {
+		LOG(L_ERR, "AUTH_IDENTITY:rtend_msgbody_proc: Can't determinate destination socket\n");
+		return -1;
+	}
+
+	/* we save it to global variable too to be able to free it later */
+	sout->s = glb_msgbody = build_body(msg, &len, &err, &dst);
+	if (err) {
+		LOG(L_ERR, "AUTH_IDENTITY:rtend_msgbody_proc: Can't build body (%d)\n", err);
+		return -2;
+	}
+
+	sout->len = (int)len;
+
+	/* authentication services MUST add a Content-Length header field to
+	 * SIP requests if one is not already present
+	 *
+	 * content-length (if present) must be already parsed and if destination
+	 * protocol is not UDP then core will append Content-Length
+	 */
+	if (!msg->content_length && dst.proto==PROTO_UDP) {
+		snprintf(scontentlen, sizeof(scontentlen), "Content-Length: %d\r\n", len);
+		scontentlen[sizeof(scontentlen)-1]=0;
+		/* if HDR_CONTENTLENGTH_T's specified then the header won't be added! */
+		if (append_hf(msg, scontentlen, HDR_OTHER_T)) {
+			pkg_free(glb_msgbody);
+			glb_msgbody=NULL;
+			return -3;
+		}
+	}
+
+	return AUTH_OK;
+}
+
+/* Contact header deinitializer of outgoing message */
+static void free_out_contacthdr(void)
+{
+	void** h_parsed;
+
+	h_parsed=&glb_contact.parsed; /*strict aliasing warnings workarround */
+	if (glb_siphdr) {
+		pkg_free(glb_siphdr);
+		glb_siphdr=NULL;
+	}
+
+	if (glb_contact.parsed)
+		free_contact((contact_body_t**)h_parsed);
+}
+
+/* body deinitializer of the outgoing message */
+static void free_out_msgbody(void)
+{
+	if (glb_msgbody) {
+		pkg_free(glb_msgbody);
+		glb_msgbody=NULL;
+	}
+}
+
+/* Digest-string assebmler function (RFC 4474 [9] */
+int digeststr_asm(dynstr *sout, struct sip_msg *msg, str *sdate, int iflags)
+{
+	/* incoming SIP message parser describer */
+	dgst_part incoming_sip_digest_desc[] = {
+		{ DS_FROM, fromhdr_proc, NULL, DS_REQUIRED },
+		{ DS_TO, tohdr_proc, NULL, DS_REQUIRED },
+		{ DS_CALLID, callidhdr_proc, NULL, DS_REQUIRED },
+		{ DS_CSEQ, cseqhdr_proc, NULL, DS_REQUIRED },
+		{ DS_DATE, datehdr_proc, NULL, DS_NOTREQUIRED },
+		{ DS_CONTACT, in_contacthdr_proc, NULL, DS_NOTREQUIRED },
+		{ DS_BODY, in_msgbody_proc, NULL, DS_NOTREQUIRED },
+		{ 0, NULL, NULL, 0 }
+	};
+	/* outgoing SIP message parser describer */
+	dgst_part outgoing_sip_digest_desc[] = {
+		{ DS_FROM, fromhdr_proc, NULL, DS_REQUIRED },
+		{ DS_TO, tohdr_proc, NULL, DS_REQUIRED },
+		{ DS_CALLID, callidhdr_proc, NULL, DS_REQUIRED },
+		{ DS_CSEQ, cseqhdr_proc, NULL, DS_REQUIRED },
+		{ DS_DATE, datehdr_proc, NULL, DS_NOTREQUIRED },
+		{ DS_CONTACT, out_contacthdr_proc, free_out_contacthdr, DS_NOTREQUIRED },
+		{ DS_BODY, out_msgbody_proc, free_out_msgbody, DS_NOTREQUIRED },
+		{ 0, NULL, NULL, 0 }
+	};
+	dgst_part *pactpart;
+	dgst_part *sip_digest_desc;
+	str sact, sactopt;
+	int i1;
+	int iRes;
+
+
+	if ((iflags & AUTH_INCOMING_BODY) ^ (iflags & AUTH_OUTGOING_BODY)) {
+		(iflags & AUTH_INCOMING_BODY) ?
+			(sip_digest_desc = incoming_sip_digest_desc) :
+			(sip_digest_desc = outgoing_sip_digest_desc);
+	} else
+		/* AUTH_INCOMING_BODY or AUTH_OUTGOING_BODY flag must set */
+		return -1;
+
+	resetstr_dynstr(sout);
+
+	for (pactpart=&sip_digest_desc[0],i1=0; pactpart[i1].itype; i1++) {
+		iRes=pactpart[i1].pfunc(&sact, &sactopt, msg);
+
+		/* there was an error or the required header is missing */
+		if (iRes==AUTH_ERROR
+		    || (iRes==AUTH_NOTFOUND && (pactpart[i1].iflag & DS_REQUIRED)))
+			return -1;
+
+		switch (pactpart[i1].itype) {
+			/* Cseq handle (we need SP instead of LWS (RFC4474 [9])) */
+			case DS_CSEQ:
+				if (app2dynstr(sout,&sact))
+					return -1;
+				if (app2dynchr(sout,' '))
+					return -2;
+				if (app2dynstr(sout,&sactopt))
+					return -3;
+				break;
+			case DS_DATE:
+				if (iRes==AUTH_NOTFOUND) {
+					if (iflags & AUTH_ADD_DATE) {
+						if (app2dynstr(sout,sdate))
+							return -8;
+					} else {
+						/* Date header must exist */
+						LOG(L_ERR, "AUTH_IDENTITY:digeststr_asm: DATE header is not found\n");
+						return -9;
+					}
+				}
+			default:
+				if (iRes==AUTH_NOTFOUND)
+					break;
+				if (app2dynstr(sout,&sact))
+					return -10;
+		}
+
+		/* if there is desctructor function available then we call it */
+		if (pactpart[i1].pfreefunc)
+			pactpart[i1].pfreefunc();
+
+		/* we don't add separator after message body */
+		if (pactpart[i1+1].itype) {
+			/* we append the separator */
+			if (app2dynchr(sout,'|'))
+				return -11;
+		}
+	}
+
+	return 0;
+}
+
+/* copypasted and ripped from ser/modules/textops/textops.c) */
+int append_hf(struct sip_msg* msg, char *str1, enum _hdr_types_t type)
+{
+	struct lump* anchor;
+	char* s;
+	int len;
+
+	if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Error while parsing message\n");
+		return -1;
+	}
+
+	anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, type);
+	if (anchor == 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Can't get anchor\n");
+		return -1;
+	}
+
+	len=strlen(str1);
+
+	s = (char*)pkg_malloc(len);
+	if (!s) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: No memory left\n");
+		return -1;
+	}
+
+	memcpy(s, str1, len);
+
+	if (insert_new_lump_before(anchor, s, len, type) == 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_hf: Can't insert lump\n");
+		pkg_free(s);
+		return -1;
+	}
+	return 0;
+}
+
+/* get the current system date and appends it to the message */
+int append_date(str *sdate, int idatesize, time_t *tout, struct sip_msg *msg)
+{
+	char date_hf[AUTH_TIME_LENGTH];
+	char date_str[AUTH_TIME_LENGTH];
+	time_t tdate_now;
+	struct tm *bd_time;
+	size_t ilen;
+	int istrlen;
+
+
+	if ((tdate_now=time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: time error %s\n", strerror(errno));
+		return -1;
+	}
+	if (!(bd_time=gmtime(&tdate_now))) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: gmtime error\n");
+		return -2;
+	}
+
+	ilen=strftime(date_str, sizeof(date_str), AUTH_TIME_FORMAT, bd_time);
+	if (ilen > sizeof(date_hf) - strlen("Date: \r\n") || ilen==0) {
+		LOG(L_ERR, "AUTH_IDENTITY:append_date: unexpected time length\n");
+		return -3;
+	}
+
+	/* we append the date header to the message too */
+	istrlen=strlen("Date: ");
+	memcpy(date_hf,"Date: ",istrlen);
+	memcpy(date_hf+istrlen,date_str,ilen);
+	istrlen+=ilen;
+	date_hf[istrlen]='\r'; date_hf[istrlen+1]='\n'; date_hf[istrlen+2]=0;
+	if (append_hf(msg, date_hf, HDR_DATE_T))
+		return -4;
+
+	if (sdate && idatesize >= ilen) {
+		memcpy(sdate->s, date_str, ilen);
+		sdate->len=ilen;
+	} else
+		return -5;
+		if (tout)
+			*tout=tdate_now;
+
+	return 0;
+}
+
+/*
+ *
+ *	"Contact" header parser part
+ *
+ */
+
+
+/* returns a pointer to the next line */
+static char *auth_next_line(char *buf, char *buf_end)
+{
+	char	*c;
+
+	c = buf;
+	do {
+		while ((c < buf_end) && (*c != '\n')) c++;
+		if (c < buf_end) c++;
+		if ((c < buf_end) && (*c == '\r')) c++;
+
+	} while ((c < buf_end) && ((*c == ' ') || (*c == '\t')));	/* next line begins with whitespace line folding */
+
+	return c;
+}
+
+/*
+ * Skip all white-chars and return position of the first
+ * non-white char
+ */
+static inline char* skip_ws(char* p, unsigned int size)
+{
+	char* end;
+
+	end = p + size;
+	for(; p < end; p++) {
+		if ((*p != ' ') && (*p != '\t')) return p;
+	}
+	return p;
+}
+
+/* looks for "Contact" header */
+static char *auth_get_hf_name(char *begin, char *end, enum _hdr_types_t *type)
+{
+	char *p;
+	unsigned int val;
+
+
+	if (end - begin < 4) {
+		*type = HDR_ERROR_T;
+		return begin;
+	}
+
+	p = begin;
+	val = LOWER_DWORD(READ(p));
+
+	switch(val) {
+		case _cont_:	/* Content-Length */
+			p+=4;
+			switch (LOWER_DWORD(READ(p))) {
+			case _act1_:
+				*type = HDR_CONTACT_T;
+				return (p + 4);
+			case _act2_:
+				*type = HDR_CONTACT_T;
+				p += 4;
+				goto dc_end;
+			}
+		default:
+			/* compact headers */
+			switch(LOWER_BYTE(*p)) {
+			case 'm':
+				switch(*(p + 1)) {
+				case ' ':
+					*type = HDR_CONTACT_T;
+					p += 2;
+					goto dc_end;
+				case ':':
+					*type = HDR_CONTACT_T;
+					return (p + 2);
+				}
+			default:
+				*type = HDR_OTHER_T;
+				break;
+			}
+	}
+
+dc_end:
+	p = skip_ws(p, end - p);
+	if (*p != ':') {
+		goto other;
+	} else {
+		return (p + 1);
+ 	}
+
+	/* Unknown header type */
+other:
+	p = q_memchr(p, ':', end - p);
+ 	if (!p) {        /* No double colon found, error.. */
+		*type = HDR_ERROR_T;
+		return 0;
+ 	} else {
+		*type = HDR_OTHER_T;
+		return (p + 1);
+ 	}
+
+	return p;
+}
+
+/* parses buffer that contains a SIP message header, looks for "Contact"
+   header field and returns the value of that */
+static int get_contact_body(char *buf, unsigned int len, str *sout)
+{
+	char *end, *s, *tmp, *match;
+	enum _hdr_types_t hf_type;
+
+
+	end = buf + len;
+	s = buf;
+
+	memset(sout, 0, sizeof(*sout));
+
+	while (s < end) {
+		if ((*s == '\n') || (*s == '\r')) {
+			/* end of SIP msg */
+			hf_type = HDR_EOH_T;
+		} else {
+			/* parse HF name */
+			if (!(s = auth_get_hf_name(s, end, &hf_type)))
+				return AUTH_ERROR;
+		}
+
+		switch(hf_type) {
+			case HDR_CONTACT_T:
+				tmp=eat_lws_end(s, end);
+				if (tmp>=end) {
+					LOG(L_ERR, "AUTH_IDENTITY:get_contact_body: get_hdr_field: HF empty\n");
+					return AUTH_ERROR;
+				}
+				sout->s=tmp;
+				/* find lf */
+				do{
+					match=q_memchr(tmp, '\n', end-tmp);
+					if (match){
+						match++;
+					}else {
+						LOG(L_ERR, "AUTH_IDENTITY:get_contact_body: bad msg body\n");
+						return AUTH_ERROR;
+					}
+					tmp=match;
+				} while( match<end &&( (*match==' ')||(*match=='\t') ) );
+				tmp=match;
+				sout->len=match-sout->s;
+				trim_r(*sout);
+				return AUTH_OK;
+				break;
+			case HDR_ERROR_T:
+				return AUTH_ERROR;
+			default:
+				s = auth_next_line(s, end);
+		}
+	}
+
+	return AUTH_NOTFOUND;
+}

+ 109 - 0
modules_s/auth_identity/auth_http.c

@@ -0,0 +1,109 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <openssl/bio.h>
+
+
+#include "../../mem/mem.h"
+#include "../../data_lump.h"
+
+#include "auth_identity.h"
+
+size_t curlmem_cb(void *ptr, size_t size, size_t nmemb, void *data)
+{
+	size_t irealsize = size * nmemb;
+
+	/* too big certificate */
+	if (((str*)data)->len + irealsize >= CERTIFICATE_LENGTH)
+		return 0;
+
+	memcpy(&(((str*)data)->s[((str*)data)->len]), ptr, irealsize);
+	((str*)data)->len+=irealsize;
+
+	return irealsize;
+}
+
+int download_cer(str *suri, CURL *hcurl)
+{
+	CURLcode iRes;
+	long lerr=200;
+	char snulled[CERTIFICATE_URL_LENGTH], *snulledptr=NULL;
+	int iRet=0;
+
+	if (suri->len < sizeof(snulled)) {
+		memcpy(snulled, suri->s, suri->len);
+		snulled[suri->len]=0;
+	} else {
+		/* +1 for the terminating \0 byte */
+		if (!(snulledptr=pkg_malloc(suri->len + 1))) {
+			LOG(L_ERR, "AUTH_IDENTITY:download_cer: Not enough memory error\n");
+			return -1;
+		}
+		memcpy(snulledptr, suri->s, suri->len);
+		snulledptr[suri->len]=0;
+	}
+
+	do {
+		if ((iRes=curl_easy_setopt(hcurl,
+								   CURLOPT_URL,
+								   snulledptr ? snulledptr : snulled))!=0) {
+			LOG(L_ERR,
+				"AUTH_IDENTITY:download_cer: Unable to set the url of certificate: %s\n",
+				curl_easy_strerror(iRes));
+			iRet=-2;
+			break;
+		}
+
+		if ((iRes=curl_easy_perform(hcurl))!=0) {
+			LOG(L_ERR,
+				"AUTH_IDENTITY:download_cer: Error while downloading certificate '%s'\n",
+				curl_easy_strerror(iRes));
+			iRet=-3;
+			break;
+		}
+
+		curl_easy_getinfo(hcurl,CURLINFO_RESPONSE_CODE,&lerr);
+		if (lerr/100 != 2) {
+			LOG(L_ERR, "AUTH_IDENTITY:download_cer: Bad HTTP response: %ld\n", lerr );
+			iRet=-4;
+		}
+	} while (0);
+
+	if (snulledptr)
+		pkg_free(snulledptr);
+
+	return iRet;
+}

+ 831 - 0
modules_s/auth_identity/auth_identity.c

@@ -0,0 +1,831 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+
+#include <curl/curl.h>
+#include <curl/types.h>
+#include <curl/easy.h>
+
+#include "../../dprint.h"
+#include "../../ut.h"
+#include "../../sr_module.h"
+#include "../../mem/mem.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_cseq.h"
+#include "../../parser/parse_content.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/contact/parse_contact.h"
+#include "../../timer.h"
+
+#include "auth_identity.h"
+
+MODULE_VERSION;
+
+static int mod_init(void); /* Module initialization function */
+static void mod_deinit(void);
+static int add_identity(struct sip_msg* msg, char* srt1, char* str2);
+static int get_certificate(struct sip_msg* msg, char* srt1, char* str2);
+static int check_validity(struct sip_msg* msg, char* srt1, char* str2);
+static int check_date(struct sip_msg* msg, char* srt1, char* str2);
+static int check_callid(struct sip_msg* msg, char* srt1, char* str2);
+static int date_proc(struct sip_msg* msg, char* srt1, char* str2);
+static int check_certificate(struct sip_msg* msg, char* srt1, char* str2);
+void callid_gc(unsigned int tick, void *param);
+
+/*
+ * Module parameter variables
+ */
+char	*glb_sprivkeypath="";	/* private key of the authentication service */
+char 	*glb_sservercerturl="";	/* URL of the certificate of the authentication service */
+char 	*glb_sservercertpath=""; /* Path of the certificate of the authentication service */
+int		glb_icertlimit=CERTIFICATE_TABLE_ITEM_LIMIT;
+char	*glb_scainfo="";
+int		glb_iauthval=AUTH_MSG_VALIDITY_TIME;	/* Message validity time in seconds (verification service)*/
+int		glb_imsgtime=AUTH_MSG_TO_AUTH_VALIDITY_TIME;	/* Message validity time in seconds (authentication service)*/
+int 	glb_icallidlimit=CALLID_TABLE_ITEM_LIMIT;
+
+CURL 	*glb_hcurl;		/* global cURL handle */
+X509 	*glb_pcertx509=NULL;
+X509_STORE *glb_cacerts=NULL;
+
+RSA 	*glb_hmyprivkey=NULL;	/* private key of the authentication service */
+time_t	glb_imycertnotafter=0;
+
+int 	glb_authservice_disabled=0;
+int 	glb_acceptpem=0;
+
+dynstr	glb_sdgst={{0,0},0}; /* Digest string */
+dynstr	glb_sidentity={{0,0},0}; /* Identity message header */
+dynstr	glb_sidentityinfo={{0,0},0}; /* Identity-info message header */
+dynstr	glb_sdate={{0,0},0}; /* Date  message header */
+
+dynstr	glb_encedmsg={{0,0},0}; /* buffer for rsa encrypted string */
+dynstr	glb_b64encedmsg={{0,0},0}; /* buffer for base64, rsa encrypted string */
+
+ttable *glb_tcert_table=0;				/* Certificate Table */
+char glb_certisdownloaded=0;
+tcert_item glb_tcert={{0,0},{0,0},0};	/* Actually Used Certificate */
+
+ttable *glb_tcallid_table=0;			/* Certificate Table */
+typedef struct timeparams { /* sturct of the callid garbage collector */
+	int ibnow;	/* the actual bucket we've not checked yet */
+	int ibnum;  /* number of the buckets we've to check */
+	int ibcir;  /* timer function's called this times during the whole table check */
+} ttimeparams;
+ttimeparams glb_ttimeparams={0,0,0};
+
+/*
+ * Exported functions
+ */
+static cmd_export_t glb_cmds[] = {
+	{"auth_date_proc", date_proc, 0, 0, REQUEST_ROUTE},
+	{"auth_add_identity", add_identity, 0, 0, REQUEST_ROUTE},
+	{"vrfy_get_certificate", get_certificate, 0, 0, REQUEST_ROUTE},
+	{"vrfy_check_msgvalidity", check_validity, 0, 0, REQUEST_ROUTE},
+	{"vrfy_check_certificate", check_certificate, 0, 0, REQUEST_ROUTE},
+	{"vrfy_check_date", check_date, 0, 0, REQUEST_ROUTE},
+	{"vrfy_check_callid", check_callid, 0, 0, REQUEST_ROUTE},
+	{0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t glb_params[] = {
+	{"privatekey_path", 		PARAM_STRING,	&glb_sprivkeypath},
+	{"certificate_url", 		PARAM_STRING,	&glb_sservercerturl},
+	{"certificate_cache_limit", PARAM_INT, 		&glb_icertlimit},
+	{"callid_cache_limit",		PARAM_INT, 		&glb_icallidlimit},
+	{"certificate_path", 		PARAM_STRING,	&glb_sservercertpath},
+	{"auth_validity_time",		PARAM_INT,    	&glb_iauthval},
+	{"msg_timeout", 			PARAM_INT, 		&glb_imsgtime},
+	{"cainfo_path", 			PARAM_STRING, 	&glb_scainfo},
+	{"accept_pem_certs", 		PARAM_INT,		&glb_acceptpem},
+	{0, 0, 0}
+};
+
+
+/*
+ * Module interface
+ */
+struct module_exports exports = {
+	"auth_identity",
+	glb_cmds,   /* Exported functions */
+	0,          /* RPC methods */
+	glb_params, /* Exported parameters */
+	mod_init,   /* module initialization function */
+	0,          /* response function */
+	mod_deinit, /* destroy function */
+	0,          /* oncancel function */
+	0			/* child initialization function */
+};
+
+
+static int mod_init(void)
+{
+	CURLcode iRet;
+	str sstr;
+	FILE *hpemfile;
+	char serr[160];
+	X509 *pmycert=NULL;		/* certificate of the authentication service */
+	time_t tnow, ttmp;
+
+	/*
+	 *
+	 * Parameter check
+	 *
+	 */
+	if (glb_sprivkeypath[0]==0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Private key path is missing! Authorization service is disabled\n");
+		glb_authservice_disabled=1;
+	}
+	if (!glb_authservice_disabled && glb_sservercerturl[0]==0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: URL of certificate of the server is missing! Authorization service is disabled\n");
+		glb_authservice_disabled=1;
+	}
+	if (!glb_authservice_disabled && glb_sservercertpath[0]==0) {
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: Path of certificate of the server is missing! Authorization service is disabled\n");
+		glb_authservice_disabled=1;
+	}
+
+	/*
+	 *
+	 * Init the curl session and download buffer
+	 *
+	 */
+	curl_global_init(CURL_GLOBAL_ALL);
+	if ((glb_hcurl=curl_easy_init())==NULL) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Unable to init cURL library!\n");
+		return -1;
+	}
+	/* send all data to this function  */
+	if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_WRITEFUNCTION, curlmem_cb))!=0) {
+		LOG(L_ERR,
+			"AUTH_IDENTITY:mod_init: Unable to set cURL write function option: %s\n",
+			curl_easy_strerror(iRet));
+		return -2;
+	}
+	/* we pass our 'glb_tcert' struct to the callback function */
+	if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_WRITEDATA, (void *)&glb_tcert.scertpem))!=0) {
+		LOG(L_ERR,
+			"AUTH_IDENTITY:mod_init: Unable to set cURL writedata option: %s\n",
+			curl_easy_strerror(iRet));
+		return -4;
+	}
+	if (!(glb_tcert.scertpem.s=pkg_malloc(CERTIFICATE_LENGTH))) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Not enough memory error\n");
+		return -3;
+	}
+  	/* some servers don't like requests that are made without a user-agent
+	   field, so we provide one */
+	if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_USERAGENT, "ser-agent/1.0"))!=0) {
+		LOG(L_WARN,
+			"AUTH_IDENTITY:mod_init: Unable to set cURL useragent option: %s\n",
+			curl_easy_strerror(iRet));
+	}
+	if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYPEER, 1))!=0) {
+		LOG(L_WARN,
+			"AUTH_IDENTITY:mod_init: Unable to set cURL verifypeer option: %s\n",
+			curl_easy_strerror(iRet));
+	}
+	if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_SSL_VERIFYHOST, 2))!=0) {
+		LOG(L_WARN,
+			"AUTH_IDENTITY:mod_init: Unable to set cURL verifyhost option: %s\n",
+			curl_easy_strerror(iRet));
+	}
+
+	/* cainfo_path module parameter's been set */
+	if (glb_scainfo[0]) {
+		if ((iRet=curl_easy_setopt(glb_hcurl, CURLOPT_CAINFO, glb_scainfo))!=0) {
+			LOG(L_WARN,
+				"AUTH_IDENTITY:mod_init: Unable to set cURL cainfo option: %s\n",
+				curl_easy_strerror(iRet));
+		}
+	}
+
+
+	/*
+	 *
+	 * OpenSSL certificate verification initialization
+	 *
+	 */
+	OpenSSL_add_all_algorithms();
+	if (!(glb_cacerts=X509_STORE_new())) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to initialize X509 store\n");
+		return -16;
+	}
+	if (X509_STORE_set_default_paths(glb_cacerts)!=1) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to set X509 store default path\n");
+		return -17;
+	}
+	if (glb_scainfo[0]
+		   && X509_STORE_load_locations(glb_cacerts, glb_scainfo, NULL) != 1)
+		LOG(L_WARN, "AUTH_IDENTITY:mod_init: unable to load X509 store location\n");
+
+
+	/*
+	 *
+	 * Init the Date, Digest-String, Identity and Identity-Info
+	 *
+	 */
+	if (initdynstr(&glb_sdgst, DGST_STR_INIT_SIZE))
+		return -5;
+
+	/*
+	 * Init certificate table
+	 */
+	if (init_table(&glb_tcert_table,
+				   CERTIFICATE_TABLE_ENTRIES,
+				   glb_icertlimit,
+				   cert_item_cmp,
+				   cert_item_init,
+				   cert_item_least,
+				   cert_item_free,
+				   NULL))
+ 		return -5;
+
+	/*
+	 * Init call-id table
+	 */
+	if (init_table(&glb_tcallid_table,
+				   CALLID_TABLE_ITEM_LIMIT,
+				   glb_icallidlimit,
+				   cid_item_cmp,
+				   cid_item_init,
+				   cid_item_least,
+				   cid_item_free,
+				   cid_item_gc))
+		return -5;
+
+	glb_ttimeparams.ibnow=0;
+	/* we've to check the whole table in glb_imsgtime, so the number of
+	   buckets we've to check in every timer call is
+	   CALLID_TABLE_ENTRIES/glb_imsgtime/CALLID_GARBAGE_COLLECTOR_INTERVAL */
+	glb_ttimeparams.ibcir=glb_iauthval/CALLID_GARBAGE_COLLECTOR_INTERVAL;
+	if (!glb_ttimeparams.ibcir)
+		glb_ttimeparams.ibcir=1;
+	glb_ttimeparams.ibnum=CALLID_TABLE_ENTRIES/glb_ttimeparams.ibcir;
+
+	if (register_timer(callid_gc, (void*)&glb_ttimeparams /* param*/, CALLID_GARBAGE_COLLECTOR_INTERVAL  /* period */) < 0 ) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Can not register timer\n");
+		return -5;
+	}
+
+	/*
+	 * If there were not enough parameter set then we could not initialize
+	 * the authorizer part
+	 */
+	if (glb_authservice_disabled)
+		return 0;
+
+
+	if (initdynstr(&glb_sidentity, DGST_STR_INIT_SIZE))
+		return -6;
+
+	if (initdynstr(&glb_sdate, AUTH_TIME_LENGTH))
+		return -7;
+
+	if (initdynstr(&glb_sidentityinfo, AUTH_URL_LENGTH))
+		return -8;
+
+	/* we initialize indentity info header */
+	sstr.s=IDENTITY_INFO_FIRST_PART; sstr.len=strlen(IDENTITY_INFO_FIRST_PART);
+	if (cpy2dynstr(&glb_sidentityinfo, &sstr))
+		return -9;
+	sstr.s=glb_sservercerturl; sstr.len=strlen(glb_sservercerturl);
+	if (app2dynstr(&glb_sidentityinfo, &sstr))
+		return -10;
+	sstr.s=IDENTITY_INFO_LAST_PART;
+	/* we copy the trailing \0 because append_hf expects strings */
+	sstr.len=strlen(IDENTITY_INFO_LAST_PART) + 1;
+	if (app2dynstr(&glb_sidentityinfo, &sstr))
+		return -11;
+
+	/*
+  	 * Get my certificate
+	 */
+	if (!(hpemfile=fopen(glb_sservercertpath, "r"))) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open certificate '%s'\n", strerror(errno));
+		return -12;
+	}
+	if (!(pmycert=PEM_read_X509(hpemfile, NULL, NULL, NULL))) {
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
+		fclose(hpemfile);
+		return -13;
+	}
+	if (x509_get_notafter(&glb_imycertnotafter, pmycert)) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate expiration date\n");
+		return -13;
+	}
+	if (x509_get_notbefore(&ttmp, pmycert)) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Error getting certificate validity date\n");
+		return -13;
+	}
+	if ((tnow=time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: time error %s\n", strerror(errno));
+		return -13;
+	}
+	if (tnow < ttmp || tnow > glb_imycertnotafter) {
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: Date of certificate is invalid (%s)\n", glb_sservercertpath);
+		return -14;
+	}
+
+	if (fclose(hpemfile))
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");
+	X509_free(pmycert);
+
+	/*
+	 *
+ 	 * Init RSA-SHA1 encoder
+	 *
+	 */
+	hpemfile=fopen(glb_sprivkeypath, "r");
+	if (!hpemfile)
+	{
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to open private key '%s'\n", strerror(errno));
+		return -12;
+	}
+	glb_hmyprivkey=PEM_read_RSAPrivateKey(hpemfile, NULL, NULL, NULL);
+	if (!glb_hmyprivkey)
+	{
+		ERR_error_string_n(ERR_get_error(), serr, sizeof serr);
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: '%s'\n", serr);
+		fclose(hpemfile);
+		return -13;
+	}
+	if (fclose(hpemfile))
+		LOG(L_ERR, "AUTH_IDENTITY:mod_init: unable to close file\n");
+
+	/* we encrypt the digest string hash to this buffer */
+	if (initdynstr(&glb_encedmsg, RSA_size(glb_hmyprivkey)))
+		return -14;
+
+	/* we base64 encode the encrypted digest string hash to this buffer */
+	if (initdynstr(&glb_b64encedmsg, (RSA_size(glb_hmyprivkey)/3+1)*4))
+		return -15;
+
+	return 0;
+}
+
+
+static void mod_deinit(void)
+{
+	curl_easy_cleanup(glb_hcurl);
+	if (glb_tcert.scertpem.s)
+		pkg_free(glb_tcert.scertpem.s);
+	free_dynstr(&glb_sdgst);
+	free_dynstr(&glb_sidentity);
+	free_dynstr(&glb_sdate);
+ 	free_table(glb_tcert_table);
+	free_table(glb_tcallid_table);
+
+	if (glb_cacerts)
+		X509_STORE_free(glb_cacerts);
+}
+
+
+/*
+ *
+ *	VERIFIER FUNCTIONS
+ *
+ */
+
+
+static int get_certificate(struct sip_msg* msg, char* srt1, char* str2)
+{
+	if (identityinfohdr_proc(&glb_tcert.surl, NULL, msg))
+		return -3;
+
+	/* we support rsa-sha1 only (alg.len==0 then we use rsa-sha1) */
+	if (get_identityinfo(msg)->alg.len
+		&& (get_identityinfo(msg)->alg.len != strlen("rsa-sha1")
+		    || strncasecmp("rsa-sha1",
+							get_identityinfo(msg)->alg.s,
+							get_identityinfo(msg)->alg.len ))) {
+		LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Unsupported Identity-Info algorithm\n");
+		return -5;
+	}
+
+	/* this case ivalidbefore==0 singns that this certificate was downloaded */
+	glb_tcert.ivalidbefore=0;
+
+	/* chech whether this certificate is our certificate table */
+	if (get_cert_from_table(glb_tcert_table, &glb_tcert.surl, &glb_tcert)) {
+		/* we did not found it in the table, so we've to download it */
+		/* we reset the PEM buffer */
+		glb_tcert.scertpem.len=0;
+		if (download_cer(&glb_tcert.surl, glb_hcurl))
+			return -6;
+		glb_certisdownloaded=1;
+	} else
+		glb_certisdownloaded=0;
+
+	if (retrieve_x509(&glb_pcertx509, &glb_tcert.scertpem, glb_acceptpem))
+		return -7;
+
+
+	return 1;
+}
+
+/*
+ * If the digest-string, assembled from the message, corresponds to the string
+ * decoded from the Identity header by the acquired public key then the message
+ * is valid. RFC 4474 [6] Step 3
+ */
+static int check_validity(struct sip_msg* msg, char* srt1, char* str2)
+{
+	str sidentity;
+	char sencedsha[HASH_STR_SIZE];
+	int iencedshalen;
+#ifndef NEW_RSA_PROC
+	char ssha[HASH_STR_SIZE];
+#endif
+	int ishalen;
+	unsigned char sstrcrypted[SHA_DIGEST_LENGTH];
+	int iRet=1;
+
+
+	if (!glb_pcertx509) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_validity: Certificate uninitialized! (has vrfy_get_certificate been called?)\n");
+		return -1;
+	}
+
+	do {
+		/* get the value of identity header parsed */
+		if (identityhdr_proc(&sidentity, NULL, msg)) {
+			iRet=-1;
+			break;
+		}
+
+		/* the length of identity value should be 172 octets long */
+		if (sidentity.len > sizeof(sencedsha)) {
+			LOG(L_ERR, "AUTH_IDENTITY:check_validity: Unexpected Identity length (%d)\n", sidentity.len);
+			iRet=-2;
+			break;
+		}
+
+		/* base64 decode the value of Identity header */
+		base64decode(sidentity.s, sidentity.len, sencedsha, &iencedshalen);
+
+		/* assemble the digest string to be able to compare it with decrypted one */
+		if (digeststr_asm(&glb_sdgst, msg, NULL, AUTH_INCOMING_BODY)) {
+			iRet=-5;
+			break;
+		}
+		/* calculate hash */
+		SHA1((unsigned char*)getstr_dynstr(&glb_sdgst).s,
+			  getstr_dynstr(&glb_sdgst).len,
+			  sstrcrypted);
+
+#ifdef NEW_RSA_PROC
+		/* decrypt with public key retrieved from the downloaded certificate
+		   and compare it with the calculated digest hash */
+		if (rsa_sha1_dec(sencedsha, iencedshalen,
+						 (char *)sstrcrypted, sizeof(sstrcrypted), &ishalen,
+						 glb_pcertx509)) {
+			iRet=-3;
+			break;
+		} else
+			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
+#else
+		/* decrypt with public key retrieved from the downloaded certificate */
+		if (rsa_sha1_dec(sencedsha, iencedshalen,
+						 ssha, sizeof(ssha), &ishalen,
+						 glb_pcertx509)) {
+			iRet=-3;
+			break;
+		}
+
+		/* check size */
+		if (ishalen != sizeof(sstrcrypted)) {
+			LOG(L_ERR, "AUTH_IDENTITY:check_validity: Unexpected decrypted hash length (%d != %d)\n", ishalen, SHA_DIGEST_LENGTH);
+			iRet=-4;
+			break;
+		}
+		/* compare */
+		if (memcmp(sstrcrypted, ssha, ishalen)) {
+			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: comparing hashes failed -> Invalid Identity Header\n");
+			iRet=-6;
+			break;
+		} else
+			LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Identity OK\n");
+#endif
+	} while (0);
+
+	glb_pcertx509=NULL;
+
+	return iRet;
+}
+
+/*
+ * The Date header must indicate a time within 3600 seconds of the receipt of a
+ * message. RFC 4474 [6] Step 4
+ */
+static int check_date(struct sip_msg* msg, char* srt1, char* str2)
+{
+	time_t tnow, tmsg;
+	int ires;
+
+	ires=datehdr_proc(NULL, NULL, msg);
+	if (ires)
+		return -1;
+
+
+#ifdef HAVE_TIMEGM
+	tmsg=timegm(&get_date(msg)->date);
+#else
+	tmsg=_timegm(&get_date(msg)->date);
+#endif
+	if (tmsg < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_date: timegm error\n");
+		return -2;
+	}
+
+	if ((tnow=time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_date: time error %s\n", strerror(errno));
+		return -3;
+	}
+
+	if (tnow > tmsg + glb_iauthval) {
+		LOG(L_INFO, "AUTH_IDENTITY VERIFIER: Outdated date header value (%ld sec)\n", tnow - tmsg + glb_iauthval);
+		return -4;
+	} else
+		LOG(AUTH_DBG_LEVEL, "AUTH_IDENTITY VERIFIER: Date header value OK\n");
+
+	return 1;
+}
+
+
+int check_certificate(struct sip_msg* msg, char* srt1, char* str2) {
+	struct sip_uri tfrom_uri;
+	str suri;
+
+	if (!glb_pcertx509) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_certificate: Certificate uninitialized! (has vrfy_get_certificate been called?)\n");
+		return -1;
+	}
+	/* this certificate was downloaded so we've to verify and add it to table */
+	if (glb_certisdownloaded) {
+		if (fromhdr_proc(&suri, NULL, msg))
+			return -1;
+
+		if (parse_uri(suri.s, suri.len, &tfrom_uri)) {
+			LOG(L_ERR, "AUTH_IDENTITY:get_certificate: Error while parsing FROM URI\n");
+			return -2;
+		}
+
+		if (verify_x509(glb_pcertx509, glb_cacerts))
+			return -3;
+
+		if (check_x509_subj(glb_pcertx509, &tfrom_uri.host))
+			return -4;
+
+		/* we retrieve expiration date from the certificate (it needs for
+		   certificate table garbage collector) */
+		if (x509_get_notafter(&glb_tcert.ivalidbefore, glb_pcertx509))
+			return -5;
+
+		if (addcert2table(glb_tcert_table, &glb_tcert))
+			return -6;
+	}
+	return 1;
+}
+
+static int check_callid(struct sip_msg* msg, char* srt1, char* str2)
+{
+	str scid, sftag, scseqnum;
+	unsigned int ucseq;
+	int ires;
+	time_t ivalidbefore;
+
+
+	if (callidhdr_proc(&scid, NULL, msg))
+		return -1;
+
+	if (cseqhdr_proc(&scseqnum, NULL, msg))
+		return -2;
+	if (str2int(&scseqnum, &ucseq))
+		return -3;
+
+	if (fromhdr_proc(NULL, &sftag, msg))
+		return -4;
+
+	if ((ivalidbefore=time(0)) < 0) {
+		LOG(L_ERR, "AUTH_IDENTITY:check_callid: time error %s\n", strerror(errno));
+		return -5;
+	}
+
+	ires=proc_cid(glb_tcallid_table,
+				  &scid,
+				  &sftag,
+				  ucseq,
+				  ivalidbefore + glb_iauthval);
+	if (ires) {
+		if (ires==AUTH_FOUND)
+			LOG(L_INFO, "AUTH_IDENTITY VERIFIER: Call is replayed!\n");
+		return -6;
+	}
+
+	return 1;
+}
+
+
+void callid_gc(unsigned int tick, void *param)
+{
+	/* check the last slice */
+	if (((ttimeparams*)param)->ibnow + 1 == ((ttimeparams*)param)->ibcir) {
+		garbage_collect(glb_tcallid_table,
+						 (((ttimeparams*)param)->ibnow)*((ttimeparams*)param)->ibnum,
+						 CALLID_TABLE_ENTRIES-1);
+		/* we step to the first slice */
+		((ttimeparams*)param)->ibnow=0;
+	} else {
+		garbage_collect(glb_tcallid_table,
+						 (((ttimeparams*)param)->ibnow)*((ttimeparams*)param)->ibnum,
+						 ((((ttimeparams*)param)->ibnow+1)*((ttimeparams*)param)->ibnum)-1);
+		/* we step to the next slice */
+		((ttimeparams*)param)->ibnow++;
+	}
+}
+
+/*
+ *
+ *	AUTHORIZER FUNCTIONS
+ *
+ */
+
+/* Checks the Date header of the message. RFC4474 [5] Step 3 */
+static int date_proc(struct sip_msg* msg, char* srt1, char* str2)
+{
+	str sdate;
+	int iRes;
+	time_t tmsg, tnow;
+
+	if (glb_authservice_disabled) {
+		LOG(L_WARN, "AUTH_IDENTITY:date_proc: Authentication Service is disabled\n");
+		return -1;
+	}
+
+	getstr_dynstr(&glb_sdate).len=0;
+
+	/* we'd like to get the DATE header of the massage */
+	iRes=datehdr_proc(&sdate, NULL, msg);
+	switch (iRes) {
+		case AUTH_ERROR:
+			return -1;
+		case AUTH_NOTFOUND:
+			if (append_date(&getstr_dynstr(&glb_sdate), glb_sdate.size, &tmsg, msg))
+				return -2;
+			break;
+		/* Message has Date header so we check that */
+		case AUTH_OK:
+#ifdef HAVE_TIMEGM
+			tmsg=timegm(&get_date(msg)->date);
+#else
+			tmsg=_timegm(&get_date(msg)->date);
+#endif
+			if (tmsg < 0) {
+				LOG(L_ERR, "AUTH_IDENTITY:date_proc: timegm error\n");
+				return -3;
+			}
+			if ((tnow=time(NULL))<0) {
+				LOG(L_ERR, "AUTH_IDENTITY:date_proc: time error\n");
+				return -4;
+			}
+			/*
+			 * If the value of this field contains a time different by more than
+			 * ten minutes from the current time noted by the authentication
+			 * service then it should reject the message.
+			 */
+			if (tmsg + glb_imsgtime < tnow || tnow + glb_imsgtime < tmsg) {
+				LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: Date header overdue\n");
+				return -6;
+			}
+			break;
+		default:
+			/* unknown result */
+			return -7;
+	}
+
+	/*
+	 * The authentication service MUST verify that the Date header
+	 * falls within the validity period of its certificate
+	 * RFC 4474 [6] Step 3
+	 */
+	if (glb_imycertnotafter < tmsg) {
+		LOG(L_INFO, "AUTH_IDENTITY AUTHORIZER: My certificate has been expired\n");
+		return -8;
+	}
+
+	return 1;
+}
+
+/*
+ * Concates the message From, To, Call-ID, Cseq, Date,  Contact header fields
+ * and the message body to digest-string, signs with the domain private-key,
+ * BASE64 encodes that, and finally adds it to the message as the 'Identity'
+ * header value. RFC4474 [5] Step 4
+ *
+ * Adds Identity-Info header to the message which contains an URI from which
+ * its certificate can be acquired. RFC4474 [5] Step 4
+ */
+static int add_identity(struct sip_msg* msg, char* srt1, char* str2)
+{
+	int iRes;
+	str sstr;
+
+
+	if (glb_authservice_disabled) {
+		LOG(L_WARN, "AUTH_IDENTITY:add_identity: Authentication Service is disabled\n");
+		return -1;
+	}
+
+	/* check Date */
+	iRes=datehdr_proc(NULL, NULL, msg);
+	switch (iRes) {
+ 		case AUTH_ERROR:
+			return -1;
+		case AUTH_NOTFOUND:
+			if (!getstr_dynstr(&glb_sdate).len) {
+				/*
+				 * date_proc() must be called before add_identity() because
+				 * that function initializes the Date if that not exists
+				 * in the SIP message
+				 */
+				LOG(L_ERR, "AUTH_IDENTITY:add_identity: Date header is not found (has auth_date_proc been called?)\n");
+				return -2;
+			}
+			/*  assemble the digest string and the DATE header is missing in the orignal message */
+			if (digeststr_asm(&glb_sdgst,
+							  msg,
+							  &getstr_dynstr(&glb_sdate),
+							  AUTH_OUTGOING_BODY | AUTH_ADD_DATE))
+				return -3;
+			break;
+		default:
+			/*  assemble the digest string and the DATE header is available in the message */
+			if (digeststr_asm(&glb_sdgst, msg, NULL, AUTH_OUTGOING_BODY))
+				return -4;
+			break;
+	}
+
+	/* calculate the SHA1 hash and encrypt with our provate key */
+	if (rsa_sha1_enc(&glb_sdgst, &glb_encedmsg, &glb_b64encedmsg, glb_hmyprivkey))
+		return -5;
+
+	/* we assemble the value of the Identity haader */
+	sstr.s=IDENTITY_FIRST_PART; sstr.len=strlen(IDENTITY_FIRST_PART);
+	if (cpy2dynstr(&glb_sidentity, &sstr))
+		return -6;
+
+	if (app2dynstr(&glb_sidentity, &getstr_dynstr(&glb_b64encedmsg)))
+		return -7;
+
+	sstr.s=IDENTITY_LAST_PART;
+	/* +1 : we need the trailing \0 character too */
+	sstr.len=strlen(IDENTITY_LAST_PART) + 1;
+	if (app2dynstr(&glb_sidentity, &sstr))
+		return -8;
+
+	if (append_hf(msg, getstr_dynstr(&glb_sidentity).s, HDR_IDENTITY_T))
+		return -9;
+
+	if (append_hf(msg, getstr_dynstr(&glb_sidentityinfo).s, HDR_IDENTITY_INFO_T))
+		return -10;
+
+	return 1;
+}

+ 259 - 0
modules_s/auth_identity/auth_identity.h

@@ -0,0 +1,259 @@
+/*
+ * $Id$
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+#ifndef AUTH_IDENT_H
+#define AUTH_IDENT_H
+
+#include <openssl/x509.h>
+#include <curl/curl.h>
+
+#include "../../locking.h"
+#include "../../mem/mem.h"
+#include "../../parser/msg_parser.h"	/* struct sip_msg */
+#include "../../str.h"					/* struct str */
+#include "../../parser/parse_identity.h"
+#include "../../parser/parse_identityinfo.h"
+#include "../../parser/parse_date.h"
+
+#define NEW_RSA_PROC
+
+#define AUTH_DBG_LEVEL L_DBG
+
+#define AUTH_URL_LENGTH 512
+#define CERTIFICATE_URL_LENGTH AUTH_URL_LENGTH
+#define CERTIFICATE_LENGTH 8*1024
+#define DGST_STR_INIT_SIZE 8*1024
+#define HASH_STR_SIZE 1024
+#define AUTH_TIME_FORMAT "%a, %d %b %Y %H:%M:%S GMT"
+#define AUTH_TIME_LENGTH 64
+#define AUTH_CONTENTLENGTH_LENGTH AUTH_TIME_LENGTH
+#define AUTH_DOMAIN_LENGTH 256
+#define IDENTITY_INFO_FIRST_PART "Identity-Info: <"
+#define IDENTITY_INFO_LAST_PART ">;alg=rsa-sha1\r\n"
+
+#define IDENTITY_FIRST_PART "Identity: \""
+#define IDENTITY_LAST_PART "\"\r\n"
+
+#define ITEM_IN_BUCKET_LIMIT 8
+
+#define CERTIFICATE_TABLE_ENTRIES (2<<10)
+#define CERTIFICATE_TABLE_ITEM_LIMIT CERTIFICATE_TABLE_ENTRIES*ITEM_IN_BUCKET_LIMIT*2
+
+/* callid table garbage collector defines */
+#define CALLID_GARBAGE_COLLECTOR_INTERVAL 10
+
+#define CALLID_TABLE_ENTRIES (2<<13)
+#define CALLID_TABLE_ITEM_LIMIT	CALLID_TABLE_ENTRIES*ITEM_IN_BUCKET_LIMIT*2
+
+#define AUTH_MSG_VALIDITY_TIME 3600
+#define AUTH_MSG_TO_AUTH_VALIDITY_TIME 600
+
+#define BEGIN_PEM_CERT "-----BEGIN CERTIFICATE-----"
+#define BEGIN_PEM_CERT_LEN (sizeof(BEGIN_PEM_CERT) - 1)
+
+enum msg_part {
+	DS_FROM = 1,
+	DS_TO,
+	DS_CALLID,
+	DS_CSEQ,
+	DS_DATE,
+	DS_CONTACT,
+	DS_BODY
+};
+
+enum msg_part_flag {
+	DS_REQUIRED = 0,
+	DS_NOTREQUIRED = 1
+};
+
+typedef int (msg_part_proc)(str *, str *, struct sip_msg *);
+typedef void (msg_part_free_proc)(void);
+
+typedef struct _dgst_part {
+	int itype;
+	msg_part_proc *pfunc;
+	msg_part_free_proc *pfreefunc;
+	int iflag;
+} dgst_part;
+
+enum dgststr_asm_flags {
+	AUTH_ADD_DATE = 1,
+	AUTH_INCOMING_BODY = 1<<1,
+	AUTH_OUTGOING_BODY = 1<<2
+};
+
+enum proc_ret_val {
+	AUTH_OK,
+	AUTH_NOTFOUND,
+	AUTH_FOUND,
+	AUTH_ERROR
+};
+
+
+typedef struct _dstr {
+	str	sd;
+	int size;
+} dynstr;
+
+int app2dynstr(dynstr *sout, str *s2app);
+int app2dynchr(dynstr *sout, char capp);
+int cpy2dynstr(dynstr *sout, str *s2app);
+int initdynstr(dynstr *sout, int isize);
+#define free_dynstr(sdyn) if ((sdyn)->sd.s) { pkg_free((sdyn)->sd.s); (sdyn)->size=0; }
+#define resetstr_dynstr(sdyn) (sdyn)->sd.len=0
+#define getstr_dynstr(sdyn) (sdyn)->sd
+
+
+/* Table declarations */
+/*
+fleast(s1, s2) return values:
+ 1	s2 is less than s1
+ 0	s1 and s2 are equal
+-1  s1 is less than s2
+-2	s1 is the least
+-3  s2 is the least
+
+fcmp(s1, s2) return values:
+ 0  s1 and s2 are the same
+ any other	s1 and s2 are not the same
+
+fgc(s1) return values:
+ 1 s1 is garbage
+ 0 s1 is not garbage
+*/
+typedef int (table_item_cmp)(const void *, const void *);
+typedef void (table_item_free)(const void *);
+typedef void (table_item_searchinit)();
+typedef int (table_item_gc)(const void *); /* garbage collector function */
+typedef struct item {
+	void *pdata;
+	unsigned int uhash;
+	struct item *pnext;
+	struct item *pprev;
+} titem;
+typedef struct bucket {
+	titem	*pfirst;
+	titem	*plast;
+	gen_lock_t lock;
+} tbucket;
+typedef struct table {
+	unsigned int unum;	/* number of items */
+	unsigned int ubuckets;	/* number of buckets */
+	unsigned int uitemlim;	/* maximum of items */
+	gen_lock_t lock;	/* lock for unum modifiing */
+	table_item_cmp *fcmp; /* compare function (used by search) */
+	table_item_searchinit *fsearchinit; /* init function (used by least item search, garbage collect) */
+	table_item_cmp *fleast; /* init function (used by least item search) */
+	table_item_free *ffree; /* free function */
+	table_item_gc *fgc; /* garbage signer function */
+	tbucket *entries;
+} ttable;
+
+
+int init_table(ttable **ptable,
+			   unsigned int ubucknum,
+			   unsigned int uitemlim,
+			   table_item_cmp *fcmp,
+			   table_item_searchinit *searchinit,
+			   table_item_cmp *fleast,
+			   table_item_free *ffree,
+			   table_item_gc *fgc);
+void free_table(ttable *ptable);
+void garbage_collect(ttable *ptable, int ihashstart, int ihashend);
+
+/* Certificate table declarations */
+typedef struct cert_item {
+	str		surl;
+	str 	scertpem;
+	time_t	ivalidbefore;	/* expiration time */
+	unsigned int uaccessed;
+} tcert_item;
+int cert_item_cmp(const void *s1, const void *s2);
+void cert_item_init();
+int cert_item_least(const void *s1, const void *s2);
+void cert_item_free(const void *sitem);
+int get_cert_from_table(ttable *ptable, str *skey, tcert_item *ptarget);
+int addcert2table(ttable *ptable, tcert_item *pcert);
+
+/* Call-ID table declarations */
+typedef struct dlg_item {
+	str	sftag;	/* tag of the From header */
+	unsigned int ucseq; /* number part of the cseq */
+	struct dlg_item *pnext; /* next dialog concerned the same call-id */
+} tdlg_item;
+
+typedef struct cid_item {
+	str	scid; /* call-id of the message */
+	time_t ivalidbefore; /* the later expiration time among dialogs concerned this call-id*/
+	tdlg_item *pdlgs; /* Cseqs and From tags */
+} tcid_item;
+int proc_cid(ttable *ptable,
+			 str *scid,
+			 str *sftag,
+			 unsigned int ucseq,
+			 time_t ivalidbefore);
+int cid_item_cmp(const void *s1, const void *s2);
+int cid_item_least(const void *s1, const void *s2);
+void cid_item_free(const void *sitem);
+void cid_item_init();
+int cid_item_gc();
+
+/* cURL functions */
+size_t curlmem_cb(void *ptr, size_t size, size_t nmemb, void *data);
+int download_cer(str *suri, CURL *hcurl);
+
+/* OpenSSL, Base64 functions */
+int retrieve_x509(X509 **pcert, str *scert, int bacceptpem);
+int check_x509_subj(X509 *pcert, str* sdom);
+int verify_x509(X509 *pcert, X509_STORE *pcacerts);
+int rsa_sha1_dec (char *sencedsha, int iencedshalen,
+				  char *ssha, int sshasize, int *ishalen,
+				  X509 *pcertx509);
+int rsa_sha1_enc (dynstr *sdigeststr,
+				  dynstr *senc,
+				  dynstr *sencb64,
+				  RSA *hmyprivkey);
+void base64decode(char* src_buf, int src_len, char* tgt_buf, int* tgt_len);
+void base64encode(char* src_buf, int src_len, char* tgt_buf, int* tgt_len);
+int x509_get_notafter(time_t *tout, X509 *pcert);
+int x509_get_notbefore(time_t *tout, X509 *pcert);
+
+/* Common functions */
+int digeststr_asm(dynstr *sout, struct sip_msg *msg, str *sdate, int iflags);
+
+int fromhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int cseqhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int callidhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int datehdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int identityhdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+int identityinfohdr_proc(str *sout, str *soutopt, struct sip_msg *msg);
+
+int append_date(str *sdate, int idatesize, time_t *tout, struct sip_msg *msg);
+int append_hf(struct sip_msg* msg, char *str1, enum _hdr_types_t type);
+
+#endif

+ 629 - 0
modules_s/auth_identity/auth_identity.xml

@@ -0,0 +1,629 @@
+<?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.auth_identity"
+          xmlns:xi="http://www.w3.org/2001/XInclude"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>auth_identity</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>auth_identity</refname>
+    <refpurpose>Securely identify originator of SIP messages.</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>auth_identity</command> SER module provides
+      functionalities for securely identifying originators of SIP
+      messages by implementing the mechanism described in RFC 4474
+      and known as SIP Identity framework.
+    </para>
+    <para>
+      The module provides two services:
+      The <emphasis>authorizer</emphasis> authorizes a message and adds
+      Identity and Identity-Info headers to forwarded requests.
+      The <emphasis>verifier</emphasis> verifies the identity of a
+      received message.
+    </para>
+    <para>
+      In order to use the <command>auth_identity</command> module you
+      have to provide a certificate. More information can be found in
+      the section
+      <serdoc:link linkend="module.auth_identity.certificates">Certificates
+      </serdoc:link> below.
+    </para>
+  </refsect1>
+
+  <refsect1 xml:id="module.auth_identity.functions">
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.auth_add_identity">
+      <title>
+        <function>auth_add_identity</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>auth_add_identity()</function> function authorizes
+        a request by adding identity information to it.
+      </para>
+      <para>
+        It calculates a SHA1 hash over certain components of the message
+        and encrypts it using the private key given through the
+        <serdoc:modparam
+          module="auth_identity">privatekey_path</serdoc:modparam>
+        parameter. The resulting cyphertext is appended to the message
+        as the content of the Identity header field.
+      </para>
+      <para>
+        To allow the receiver to decypher the identity information, it adds
+        the URL where the receiver may find the certificate in the
+        Indentity-Info header field. This URL is set through the
+        <serdoc:modparam
+          module="auth_identity">certificate_url</serdoc:modparam>
+        parameter.
+      </para>
+      <para>
+        <emphasis>Note:</emphasis> After the function has been called,
+        the following header fields must not be modified:
+        From, To, Call-ID, CSeq, Date, Contact. Additionally, the
+        message body must remain unmodified, too.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.auth_date_proc">
+      <title>
+        <function>auth_date_proc</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>auth_date_proc()</function> checks the age of a
+        request to be authroized. If the time given in the Date header
+        field of the request differs by more than the amount of seconds
+        given by the
+        <serdoc:modparam module="auth_identity">msg_timeout</serdoc:modparam>
+        parameter, the function returns <literal>false</literal>. It also
+        returns <literal>false</literal> if the certificate used by the
+        authorizer has expired.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.vrfy_check_callid">
+      <title>
+        <function>vrfy_check_callid</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>vrfy_check_callid()</function> checks whether a
+        request for this call has been processed within the
+        time given by the
+        <serdoc:modparam
+          module="auth_identity">auth_validity_time</serdoc:modparam>
+        parameter.
+      </para>
+      <para>
+        The function generates a call identifier from the Call-ID and CSqe
+        header fields and the Tag parameter of the From header field and
+        compares it with the values stored in the Call-ID cache.
+      </para>
+      <para>
+        It returns <literal>false</literal> if the value is found and thus
+        the request is replayed or <literal>true</literal> otherwise.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.vrfy_check_certificate">
+      <title>
+        <function>vrfy_check_certificate</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>vrfy_check_certificate()</function> function
+        checks the downloaded certificate and returns <literal>true</literal>
+        if it is valid or <literal>false</literal> otherwise.
+      </para>
+      <para>
+        A certificate is considered valid if it has not yet expired and
+        if its subject is identical to the domain part of the URL.
+      </para>
+      <!-- XXX Er, which URL? -->
+      <para>
+        Before <function>vrfy_check_certificate()</function> can be called,
+        the function <function>vrfy_check_date()</function> must have been
+        called and returned <literal>true</literal>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.vrfy_check_date">
+      <title>
+        <function>vrfy_check_date</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>vrfy_check_date()</function> function checks
+        the date of the request currently being processed. It returns
+        <literal>false</literal> if the time in the Date header field
+        of the request differs from the current system time by more than
+        the number of seconds given by the
+        <serdoc:modparam
+          module="auth_identity">auth_validity_time</serdoc:modparam>
+        parameter.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.vrfy_check_msgvalidity">
+      <title>
+        <function>vrfy_check_msgvalidity</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>vrfy_check_msgvalidity()</function> function
+        checks the integrity of the request currently being processed.
+      </para>
+      <para>
+        It does so by calculating a SHA1 hash over certain parts of the
+        message and comparing it with the hash value that results from
+        decrypting the content of the Identity header field using the
+        certificate previously retrieved using the
+        <function>vrfy_get_certificate()</function> function.
+      </para>
+      <para>
+        The function returns <literal>true</literal> if the two hashes
+        match.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.vrfy_get_certificate">
+      <title>
+        <function>vrfy_get_certificate</function>
+        ()
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>vrfy_get_certificate()</function> tries to get
+        the certificate from the URL given in the Identity-Info header
+        field of the currently processed request.
+      </para>
+      <para>
+        It returns <literal>true</literal> if the certifcate was
+        successfully loaded in a format that the module can process or
+        <literal>false</literal> otherwise.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+
+  <refsect1 xml:id="module.auth_identity.parameters">
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.auth_identity.accept_pem_certs">
+      <title><parameter>accept_pem_certs</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>boolean</serdoc:paramtype>
+        <serdoc:paramdefault>no</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>accept_pem_certs</parameter> parameter defines,
+        whether the verifier should process certificates in PEM format.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.auth_validity_time">
+      <title><parameter>auth_validity_time</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>3600</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>auth_validity_time</parameter> parameter
+        sets the maximum age of a verified message.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.cainfo_path">
+      <title><parameter>cainfo_path</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>cainfo_path</parameter> paramter contains the
+        path and name of a file that contains trusted certificates.
+      </para>
+      <para>
+        The file should contain one or more certificates in PEM format
+        concatenated together. It is necessary if the verifier should
+        accept self-signed certifcates.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.callid_cache_limit">
+      <title><parameter>callid_cache_limit</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>262144</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>callid_cache_limit</parameter> parameter
+        limits the number of entries in the Call-ID cache.
+      </para>
+      <para>
+        The Call-IDs of previously verified messages are stored in the
+        Call-ID cache for some time in order to recognize call
+        replay attacks. The cache uses shared memory. Each entry is
+        about 100 bytes in size.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.certificate_cache_limit">
+      <title><parameter>certificate_cache_limit</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>32768</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>certificate_cache_limit</parameter> parameter
+        limits the number of entries stored in the certificate cache.
+      </para>
+      <para>
+        Each retrieved certificate is placed in the certificate cache
+        to speed up processing should it be needed again.
+      </para>
+      <para>
+        The cahce uses shared memory. Each entry is approximately 600
+        bytes in size.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.certificate_path">
+      <title><parameter>certificate_path</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>certificate_path</parameter> parameter must be
+        set to path and name of the file that contains the certificate
+        of the authentication service in PEM format.
+      </para>
+      <para>
+        The parameter is required for the authentication service to work.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.certificate_url">
+      <title><parameter>certificate_url</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>certificate_url</parameter> parameter must be
+        set to the URL where a verifier can retrieve the certificate.
+        The URL will be stored in the Identity-Info header of any
+        authorized request.
+      </para>
+      <para>
+        The certificate must be available at the given URL in DER
+        format.
+      </para>
+      <para>
+        The parameter is required for the authentication service to work.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.msg_timeout">
+      <title><parameter>msg_timeout</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>int</serdoc:paramtype>
+        <serdoc:paramdefault>600</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>msg_timeout</parameter> parameter contains the
+        maximum age of a request to be authorized in seconds.
+      </para>
+      <para>
+        The authorizer will compare the time given in the Date header field
+        of the request with its own current time.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.auth_identity.privatekey_path">
+      <title><parameter>privatekey_path</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>""</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>privatekey_path</parameter> must be set to the paht
+        and name of the file that contains the private key of the
+        authentication service in PEM format.
+      </para>
+      <para>
+        The parameter is required for the authentication service to work.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 id="modules.auth_identity.examples">
+    <title>Examples</title>
+    <para>
+      The <emphasis>authentifier</emphasis> is typically included in the
+      part of the routing
+      logic that sends a request to a foreign domain. In the
+      <filename>ser-oob.cfg</filename> configuration, this is the
+      <varname>route[OUTBOUND]</varname>. With an authentifier included,
+      it looks like this:
+    </para>
+    <programlisting><![CDATA[
+route[OUTBOUND]
+{
+	if ($f.did && !$t.did) {
+		# Authentication service
+		if (method=="INVITE" || method=="OPTION") {
+			# Identity and Identity-info headers must not exist
+			if (@identity) {
+				sl_reply("403", "Invalid Identity header");
+				drop;
+			}
+			if (@identity_info) {
+				sl_reply("403", "Invalid Identity-info header");
+				drop;
+			}
+
+			if (!auth_date_proc()) {
+				sl_reply("403", "Invalid Date value");
+				drop;
+			}
+
+			if (!auth_add_identity()) {
+				sl_reply("480", "Authentication error");
+				drop;
+			}
+		}
+		route(FORWARD);
+	}
+}
+]]></programlisting>
+    <para>
+      The <emphasis>verifier</emphasis> should be run after you discovered
+      that a request originates from a foreign domain (for your own domains,
+      you should be doing digest authentication instead).
+    </para>
+    <para>
+      While you should reject any request that does contain an Identity
+      header but does not verify, it is up to your local policy, what to
+      do with messages that do not contain an Identity header at all.
+      You could reject them or divert them to voice mail or any of a number
+      of other options.
+    </para>
+    <para>
+      The following config snippet rejects any message that does not have
+      an Identity header.
+    </para>
+    <programlisting><![CDATA[
+	if (method=="INVITE" || method=="OPTIONS") {
+		if (!@identity) {
+			sl_reply("428", "Use Identity Header");
+			drop;
+		}
+		if (!@identity_info) {
+			sl_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_date()) {
+			sl_reply("403", "Outdated Date header value");
+			drop;
+		}
+
+		if (!vrfy_get_certificate()) {
+			sl_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_certificate()) {
+			sl_reply("437", "Unsupported Certificate");
+			drop;
+		}
+
+		if (!vrfy_check_msgvalidity()) {
+			sl_reply("438", "Invalid Identity Header");
+			drop;
+		}
+
+		if (!vrfy_check_callid()) {
+			sl_reply("403", "Message is replayed");
+			drop;
+		}
+	}
+]]></programlisting>
+  </refsect1>
+
+  <refsect1 id="mdoule.auth_identity.how-it-works">
+    <title>The SIP Identity Mechanism</title>
+    <para>
+      The SIP Identity Mechanism is defined in RFC 4474 "Enhancements for
+      Authenticated Identity Management in the Session Initiation Protocol
+      (SIP)". Its main purpose is to affirm the identity of the originator
+      of a request as stated in the From header field of the request.
+      While basic SIP provides a mechanism for a user agent to proof its
+      authenticity to the proxy serving the domain of the address used
+      by the user agent, no such mechanism exists to proof the authenticity
+      to other proxies or end points. The SIP Identity Mechanism mends this
+      oversight.
+    </para>
+    <para>
+      The mechanism allows a proxy to cryptographically sign a message as
+      authentic before sending it to another proxy. It does so after
+      making sure the sender actually is who he claims and is allowed to
+      use the address in the context of the message.
+    </para>
+    <para>
+      If the proxy is satisfied, it creates two header fields: Identity and
+      Identity-Info. The Identity header field contains a signed hash over
+      the following components of the message:
+    </para>
+    <itemizedlist>
+      <listitem>
+        the address given in the From header field,
+      </listitem>
+      <listitem>
+        the address given in the To header field,
+      </listitem>
+      <listitem>
+        the Call-ID,
+      </listitem>
+      <listitem>
+        the content of the CSeq header field,
+      </listitem>
+      <listitem>
+        the content of the Date header field,
+      </listitem>
+      <listitem>
+        the address given in the Contact header field if present, and
+      </listitem>
+      <listitem>
+        the body of the message.
+      </listitem>
+    </itemizedlist>
+    <para>
+      The hash is signed with a certificate for the domain. A URL pointing
+      to a place where an interested party can download a copy of the
+      certificate is placed into the Identity-Info header field.
+    </para>
+    <para>
+      If the message arrives at a proxy or user agent that wishes to verify
+      the authenticity, this device downloads the certificate and itself
+      forms the hash over the above components. If both hashes match, the
+      message has been authenticated by the proxy of the domain of the
+      certificate and has not been tempered with on the way.
+    </para>
+  </refsect1>
+
+  <refsect1 id="modules.auth_identity.credentials">
+    <title>Certificates</title>
+    <para>
+      In order to use the <command>auth_identity</command> module, you need a
+      certificate for your domain. There is two options, how to get one: you
+      buy one from a certification authority or you create your own
+      certification authority and use a self-signed certificate.  If you plan
+      to provide real  inter-domain calls for your domain, you should pick the
+      first option. Although it is more expensive, it guarantees proper
+      interaction between domains.
+    </para>
+    <para>
+      Either way, you will end up with a private key and a certificate which
+      includes the public key for that private key. You can use the same key
+      and certificate as for the TLS encrypted SIP transport. Because of
+      this, details on the process of acquiring them can be found in 
+      <serdoc:module>tls</serdoc:module>.
+    </para>
+    <para>
+      Once you have the certificate and private key, you set the module
+      parameter
+      <serdoc:modparam module="auth_identity">certificate_path</serdoc:modparam>
+      to point to the file containing the certificate and
+      <serdoc:modparam module="auth_identity">privatekey_path</serdoc:modparam>
+      to point to the file with the private key.
+    </para>
+    <para>
+      Finally, you have to put a public version of your certificate onto
+      a web server somewhere. This version must be DER encoded and,
+      obviously, must not contain the private key. You can create this
+      by issuing
+    </para>
+    <programlisting><![CDATA[
+# openssl x509 -in your_certificate.crt -outform der -out your_certificate.der
+]]></programlisting>
+    <para>
+      You then place the DER encoded certificate into a publicly
+      available directory on your webserver and state the URL to the
+      file in the parameter
+      <serdoc:modparam module="auth_identity">certificate_url</serdoc:modparam>.
+    </para>
+  </refsect1>
+
+  <!--
+    Compilation sections only go into the Admin Guide since we assume
+    that if you read a manpage you already have installed the module.
+  --> 
+  <refsect1 role="admin-guide">
+    <title>Compilation</title>
+    <para>
+      This <command>auth_identity</command> module needs the following
+      headers and libraries:
+    </para>
+    <itemizedlist>
+      <listitem>
+        <emphasis>OpenSSL</emphasis> (version 0.9.8 or higher) for
+        cryptographic functions,
+      </listitem>
+      <listitem>
+        <emphasis>libcURL</emphasis> for HTTP, HTTPS functions.
+      </listitem>
+    </itemizedlist>
+    <para>
+      If you'd like to use the <emphasis>TLS</emphasis> module, too,
+      you will have to change <varname>LIB</varname> variable in the
+      Makefile to the value that is by default commented out.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Known Limitations</title>
+    <para>
+      Both authorizer and verifier currently only support SIP requests
+      except for responses to CANCEL and REGISTER requests.
+    </para>
+    <para>
+      The verifier does not support the subjectAltName extension of
+      certificates.
+    </para>
+  </refsect1>
+
+  <refsect1 role="manpage">
+    <title>Authors</title>
+    <para>
+      Written by Gergely Kovacs and Martin Hoffmann.
+    </para>
+  </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>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+
+<!-- vim:sw=2 sta et sts=2 ai
+  -->

+ 575 - 0
modules_s/auth_identity/auth_tables.c

@@ -0,0 +1,575 @@
+/*
+ * $Id$ 
+ *
+ * Copyright (c) 2007 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/pem.h>
+#include <openssl/err.h>
+#include <openssl/sha.h>
+
+#include "../../mem/shm_mem.h"
+#include "../../hashes.h"
+#include "auth_identity.h"
+
+#define lock_element(_cell)            lock_get(&((_cell)->lock))
+#define release_element(_cell)         lock_release(&((_cell)->lock))
+
+static int insert_into_table(ttable *ptable, void *pdata, unsigned int uhash);
+static void remove_from_table_unsafe(ttable *ptable, titem *pitem);
+static void remove_least(ttable *ptable, unsigned int uhash);
+static void* search_item_in_table_unsafe(ttable *ptable,
+								  		 const void *pneedle,
+								  		 unsigned int uhash);
+
+time_t glb_tnow=0;	/* we need for this for certificate expiration check when
+					 * we've to remove the least item from a table */
+
+int init_table(ttable **ptable,	/* table we'd like to init */
+			   unsigned int ubucknum,	/* number of buckets */
+			   unsigned int uitemlim,	/* maximum number of table intems */
+			   table_item_cmp *fcmp,	/* compare funcion used by search */
+			   table_item_searchinit *fsinit, /* inits the least item searcher funcion */
+			   table_item_cmp *fleast,	/* returns the less item;
+										 * used by item remover */
+			   table_item_free *ffree,	/* frees the data part of an item */
+			   table_item_gc *fgc)	/* tells whether an item is garbage  */
+{
+	int i1;
+
+	if (!(*ptable = (ttable *) shm_malloc(sizeof(**ptable)))) {
+		LOG(L_ERR, "AUTH_IDENTITY:init_table: Not enough shared memory error\n");
+		return -1;
+	}
+	memset(*ptable, 0, sizeof(**ptable));
+
+	if (!((*ptable)->entries = (tbucket *) shm_malloc(sizeof(tbucket)*ubucknum))) {
+		LOG(L_ERR, "AUTH_IDENTITY:init_table: Not enough shared memory error\n");
+		return -1;
+	}
+	memset((*ptable)->entries, 0, sizeof(tbucket)*ubucknum);
+	for (i1=0; i1<ubucknum; i1++) {
+		(*ptable)->entries[i1].pfirst = NULL;
+		lock_init(&(*ptable)->entries[i1].lock);
+	}
+
+	(*ptable)->uitemlim=uitemlim;
+	(*ptable)->ubuckets=ubucknum;
+
+	(*ptable)->fcmp=fcmp;
+	(*ptable)->fsearchinit=fsinit;
+	(*ptable)->fleast=fleast;
+	(*ptable)->ffree=ffree;
+	(*ptable)->fgc=fgc;
+
+	return 0;
+}
+
+void free_table(ttable *ptable)
+{
+	unsigned int u1;
+	titem *pitem, *previtem;
+
+	if (ptable) {
+		for (u1=0; u1 < ptable->ubuckets; u1++)
+		{
+			pitem=ptable->entries[u1].pfirst;
+			while (pitem) {
+				previtem=pitem;
+				pitem=pitem->pnext;
+
+				ptable->ffree(previtem->pdata);
+				shm_free(previtem);
+			}
+		}
+		shm_free(ptable->entries);
+		shm_free(ptable);
+	}
+}
+
+/* appends an item at the end of the bucket specified by uhash */
+static int insert_into_table(ttable *ptable, void *pdata, unsigned int uhash)
+{
+	tbucket *pbucket;
+	titem *pitem;
+	char bneed2remove=0;
+
+	if (!(pitem=(titem *)shm_malloc(sizeof(*pitem)))) {
+		LOG(L_ERR, "AUTH_IDENTITY:insert_into_table: Not enough shared memory error\n");
+		return -1;
+	}
+
+	memset(pitem, 0, sizeof(*pitem));
+	pitem->uhash=uhash;
+	pitem->pdata=pdata;
+
+	lock_element(ptable);
+	/* if there is not enough room for this item then we'll remove one */
+	if (ptable->unum >= ptable->uitemlim)
+		bneed2remove=1;
+	ptable->unum++;
+	release_element(ptable);
+
+	if (bneed2remove)
+		remove_least(ptable, uhash);
+
+	/* locates the appropriate bucket */
+	pbucket = &ptable->entries[uhash];
+
+	/* insert into that bucket */
+	lock_element(pbucket);
+	if (pbucket->plast) {
+		pbucket->plast->pnext = pitem;
+		pitem->pprev = pbucket->plast;
+	} else pbucket->pfirst = pitem;
+	pbucket->plast = pitem;
+	release_element(pbucket);
+
+	return 0;
+}
+
+
+/*  Un-link a cell from hash_table */
+static void remove_from_table_unsafe(ttable *ptable, titem *pitem)
+{
+	tbucket *pbucket = &(ptable->entries[pitem->uhash]);
+
+	/* unlink the cell from entry list */
+	if (pitem->pprev)
+		pitem->pprev->pnext = pitem->pnext;
+	else
+		pbucket->pfirst = pitem->pnext;
+
+	if (pitem->pnext)
+		pitem->pnext->pprev = pitem->pprev;
+	else
+		pbucket->plast = pitem->pprev;
+
+	if (ptable->ffree)
+		ptable->ffree(pitem->pdata);
+
+	shm_free(pitem);
+}
+
+/* removes the least important item from its bucket or from the following first
+   bucket which contains item */
+static void remove_least(ttable *ptable, unsigned int uhash)
+{
+	tbucket *pbucket;
+	unsigned int u1, uhashnow;
+	titem *pleastitem=NULL, *pnow;
+	int ires;
+
+	if (!ptable->fleast)
+		return ;
+	if (ptable->fsearchinit)
+		ptable->fsearchinit();
+
+	for (uhashnow=uhash,u1=0, pbucket=&(ptable->entries[uhash]);
+		 u1 < ptable->ubuckets;
+		 u1++,pbucket=&(ptable->entries[uhashnow])) {
+
+		lock_element(pbucket);
+		/* if there any item in this bucket */
+		for (pnow=pbucket->pfirst;pnow;pnow=pnow->pnext) {
+			if (!pleastitem) {
+				pleastitem=pnow;
+				continue;
+			}
+
+			/*
+ 			fleast() return values:
+			 1	s2 is less than s1
+			 0	s1 and s2 are equal
+			-1  s1 is less than s2
+			-2	s1 is the least
+			-3  s2 is the least
+			 */
+			ires=ptable->fleast(pleastitem->pdata, pnow->pdata);
+			if (ires==1)
+				pleastitem=pnow;
+			if (ires==-2)
+				break;
+			if (ires==-3) {
+				pleastitem=pnow;
+				break;
+			}
+		}
+		/* we found the least item in this bucket */
+		if (pleastitem) {
+
+			lock_element(ptable);
+			ptable->unum--;
+			release_element(ptable);
+
+			remove_from_table_unsafe(ptable, pleastitem);
+			release_element(pbucket);
+			return ;
+		}
+		release_element(pbucket);
+
+
+		/* we're in the last bucket so we start with the first one */
+		if (uhashnow + 1 == ptable->ubuckets)
+			uhashnow=0;
+		else
+		/* we step to the next bucket */
+			uhashnow++;
+	}
+}
+
+/* looks for an item in the scepifiad bucket */
+static void* search_item_in_table_unsafe(ttable *ptable,
+										 const void *pneedle,
+										 unsigned int uhash)
+{
+	tbucket *pbucket = &(ptable->entries[uhash]);
+	titem *pnow;
+	void *pret=NULL;
+
+	if (!ptable->fcmp)
+		return NULL;
+
+	for (pnow=pbucket->pfirst;pnow;pnow=pnow->pnext) {
+		if (!ptable->fcmp(pneedle, pnow->pdata)) {
+			pret=pnow->pdata;
+			break;
+		}
+	}
+
+	return pret;
+}
+
+/* looks for garbage in the hash interval specified by ihashstart and ihashend */
+void garbage_collect(ttable *ptable, int ihashstart, int ihashend)
+{
+	unsigned int unum, uremoved;
+	int i1;
+	tbucket *pbucket;
+	titem *pnow;
+
+
+	/* there is not any garbage collector funcion available */
+	if (!ptable->fgc)
+		return;
+
+	if (ptable->fsearchinit)
+		ptable->fsearchinit();
+
+	lock_element(ptable);
+	unum=ptable->unum;
+	release_element(ptable);
+
+	/* if the half of the table is used or there is not so many items in a bucket
+	   then we return */
+// 	if (unum < ptable->uitemlim/2 && unum < ptable->ubuckets*ITEM_IN_BUCKET_LIMIT)
+// 		return ;
+ 	if (!unum)
+ 		return ;
+
+	for (i1=ihashstart; i1<=ihashend; i1++) {
+		uremoved=0;
+		pbucket=&(ptable->entries[i1]);
+
+		lock_element(pbucket);
+		for (pnow=pbucket->pfirst;pnow;pnow=pnow->pnext) {
+			if (ptable->fgc(pnow->pdata)) {
+				remove_from_table_unsafe(ptable, pnow);
+				uremoved++;
+			}
+		}
+		/* if we removed any item from table then we would update the item counter */
+		if (uremoved) {
+			lock_element(ptable);
+			ptable->unum-=uremoved;
+			release_element(ptable);
+		}
+		release_element(pbucket);
+	}
+}
+
+
+/*
+ * Make a copy of a str structure using shm_malloc
+ */
+static int str_duplicate(str* _d, str* _s)
+{
+
+	_d->s = (char *)shm_malloc(sizeof(char)*(_s->len));
+	if (!_d->s) {
+		LOG(L_ERR, "AUTH_IDENTITY:str_duplicate: No enough shared memory\n");
+		return -1;
+	}
+
+	memcpy(_d->s, _s->s, _s->len);
+	_d->len = _s->len;
+	return 0;
+}
+
+/*
+ *
+ * Certificate table specific funcions
+ *
+ */
+int cert_item_cmp(const void *s1, const void *s2)
+{
+	tcert_item *p1=(tcert_item*)s1, *p2=(tcert_item*)s2;
+
+	return !(p1->surl.len==p2->surl.len && !memcmp(p1->surl.s, p2->surl.s, p2->surl.len));
+}
+
+void cert_item_init()
+{
+	/* we need for this for certificate expiration check when
+	 * we've to remove an item from the table */
+	glb_tnow=time(0);
+}
+
+/* we remove a certificate if expired or if accessed less than an other */
+int cert_item_least(const void *s1, const void *s2)
+{
+	if (((tcert_item *)s1)->ivalidbefore < glb_tnow)
+		return -2;
+	if (((tcert_item *)s2)->ivalidbefore < glb_tnow)
+		return -3;
+	return (((tcert_item *)s1)->uaccessed < ((tcert_item *)s2)->uaccessed) ? -1 : 1;
+}
+
+/* frees a certificate item */
+void cert_item_free(const void *sitem)
+{
+	shm_free(((tcert_item *)sitem)->surl.s);
+	shm_free(((tcert_item *)sitem)->scertpem.s);
+	shm_free((tcert_item *)sitem);
+}
+
+/* looks for a certificate in a table and increases access counter of that
+   table item */
+int get_cert_from_table(ttable *ptable, str *skey, tcert_item *ptarget)
+{
+	tcert_item* tmp_tcert_item;
+	unsigned int uhash;
+	int iret=0;
+
+	uhash=get_hash1_raw(skey->s, skey->len) & (CERTIFICATE_TABLE_ENTRIES-1);
+
+	/* we lock the whole bucket */
+	lock_element(&ptable->entries[uhash]);
+
+	tmp_tcert_item = search_item_in_table_unsafe(ptable,
+												 (const void *)skey,
+												 uhash);
+	/* make a copy of found certificate and after the certificate
+	 * verification we'll add it to certificate table */
+	if (tmp_tcert_item) {
+		memcpy(ptarget->scertpem.s, tmp_tcert_item->scertpem.s, tmp_tcert_item->scertpem.len);
+		ptarget->scertpem.len=tmp_tcert_item->scertpem.len;
+		/* we accessed this certificate */
+		tmp_tcert_item->uaccessed++;
+	}
+	else
+		iret=1;
+
+	release_element(&ptable->entries[uhash]);
+
+	return iret;
+}
+
+/* inserts an item to table, and removes the least item if the table is full */
+int addcert2table(ttable *ptable, tcert_item *pcert)
+{
+	tcert_item *pshmcert;
+	unsigned int uhash;
+
+	if (!(pshmcert=(tcert_item *)shm_malloc(sizeof(*pshmcert)))) {
+		LOG(L_ERR, "AUTH_IDENTITY:addcert2table: No enough shared memory\n");
+		return -1;
+	}
+	memset(pshmcert, 0, sizeof(*pshmcert));
+	if (str_duplicate(&pshmcert->surl, &pcert->surl))
+		return -2;
+
+	if (str_duplicate(&pshmcert->scertpem, &pcert->scertpem))
+		return -3;
+
+	pshmcert->ivalidbefore=pcert->ivalidbefore;
+	pshmcert->uaccessed=1;
+
+	uhash=get_hash1_raw(pcert->surl.s, pcert->surl.len) & (CERTIFICATE_TABLE_ENTRIES-1);
+
+	if (insert_into_table(ptable, (void*)pshmcert, uhash))
+		return -4;
+
+	return 0;
+}
+
+/*
+ *
+ * Call-ID table specific funcions
+ *
+ */
+
+int cid_item_cmp(const void *s1, const void *s2)
+{
+	tcid_item *p1=(tcid_item*)s1, *p2=(tcid_item*)s2;
+
+	return !(p1->scid.len==p2->scid.len && !memcmp(p1->scid.s, p2->scid.s, p2->scid.len));
+}
+
+void cid_item_init()
+{
+	glb_tnow=time(0);
+}
+
+/* we remove a call-id if older than an other */
+int cid_item_least(const void *s1, const void *s2)
+{
+	if (((tcid_item *)s1)->ivalidbefore < glb_tnow)
+		return -2;
+	if (((tcid_item *)s2)->ivalidbefore < glb_tnow)
+		return -3;
+
+	return (((tcid_item *)s1)->ivalidbefore < ((tcid_item *)s2)->ivalidbefore) ? -1 : 1;
+}
+
+/* tells whether an item is garbage */
+int cid_item_gc(const void *s1)
+{
+	return (((tcid_item *)s1)->ivalidbefore < glb_tnow);
+}
+
+/* frees a call-id item */
+void cid_item_free(const void *sitem)
+{
+	tcid_item *pcid=(tcid_item *)sitem;
+	tdlg_item *pdlgs, *pdlgs_next;
+
+	shm_free(pcid->scid.s);
+
+	pdlgs_next=pcid->pdlgs;
+	while (pdlgs_next) {
+		pdlgs=pdlgs_next;
+		pdlgs_next=pdlgs_next->pnext;
+		shm_free (pdlgs->sftag.s);
+		shm_free (pdlgs);
+	}
+
+	shm_free((tcert_item *)sitem);
+}
+
+/* inserts a callid item to table, and removes the least item if the table is full */
+int proc_cid(ttable *ptable,
+			 str *scid,
+			 str *sftag,
+			 unsigned int ucseq,
+			 time_t ivalidbefore)
+{
+	tcid_item *pshmcid, *pcid_item;
+	tdlg_item *pshmdlg, *pdlg_item, *pdlg_item_prev;
+	unsigned int uhash;
+
+	/* we suppose that this SIP request is not replayed so it doesn't exist in
+	   the table so we prepare to insert */
+	if (!(pshmdlg=(tdlg_item *)shm_malloc(sizeof(*pshmdlg)))) {
+		LOG(L_ERR, "AUTH_IDENTITY:addcid2table: No enough shared memory\n");
+		return -1;
+	}
+	memset(pshmdlg, 0, sizeof(*pshmdlg));
+	if (str_duplicate(&pshmdlg->sftag, sftag))
+		return -2;
+	pshmdlg->ucseq=ucseq;
+
+
+	/* we're looking for this call-id item if exists */
+	uhash=get_hash1_raw(scid->s, scid->len) & (CALLID_TABLE_ENTRIES-1);
+
+	lock_element(&ptable->entries[uhash]);
+
+	pcid_item = search_item_in_table_unsafe(ptable,
+											(const void *)scid, /* Call-id is the key */
+											uhash);
+	/* we've found one call-id so we're looking for the required SIP request */
+	if (pcid_item) {
+		for (pdlg_item=pcid_item->pdlgs, pdlg_item_prev=NULL;
+		     pdlg_item;
+			 pdlg_item=pdlg_item->pnext) {
+			if (pdlg_item->sftag.len==sftag->len
+				&& !memcmp(pdlg_item->sftag.s, sftag->s, sftag->len)) {
+				/* we found this call with this from tag */
+				if (pdlg_item->ucseq>=ucseq) {
+					/* we've found this or older request in the table!
+					   this call is replayed! */
+					release_element(&ptable->entries[uhash]);
+
+					shm_free(pshmdlg->sftag.s);
+					shm_free(pshmdlg);
+					return AUTH_FOUND;
+				} else {
+					/* this is another later request whithin this dialog so we
+					   update the saved cseq */
+					pdlg_item->ucseq=ucseq;
+					release_element(&ptable->entries[uhash]);
+
+					shm_free(pshmdlg->sftag.s);
+					shm_free(pshmdlg);
+					return 0;
+				}
+			}
+			/* we save the previous dialog item in order to append a new item more easily */
+			pdlg_item_prev ?
+				(pdlg_item_prev=pdlg_item_prev->pnext) :
+				(pdlg_item_prev=pdlg_item);
+		}
+		/* we append this to item dialogs*/
+		pdlg_item_prev->pnext=pshmdlg;
+		/* this is the latest request; we hold all request concerned this
+		   call-id until the latest request is valid */
+		pcid_item->ivalidbefore=ivalidbefore;
+	}
+
+	release_element(&ptable->entries[uhash]);
+
+	if (!pcid_item) {
+		/* this is the first request with this call-id */
+		if (!(pshmcid=(tcid_item *)shm_malloc(sizeof(*pshmcid)))) {
+			LOG(L_ERR, "AUTH_IDENTITY:addcid2table: No enough shared memory\n");
+			return -4;
+		}
+		memset(pshmcid, 0, sizeof(*pshmcid));
+		if (str_duplicate(&pshmcid->scid, scid)) {
+			return -5;
+		}
+		pshmcid->ivalidbefore=ivalidbefore;
+		pshmcid->pdlgs=pshmdlg;
+		if (insert_into_table(ptable, (void*)pshmcid, uhash))
+			return -6;
+	}
+
+	return 0;
+}

+ 29 - 0
modules_s/auth_identity/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = auth_identity
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 558 - 0
modules_s/auth_identity/doc/auth_identity.xml

@@ -0,0 +1,558 @@
+<?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>
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Gergely</firstname>
+		<surname>Kovacs</surname>
+		<affiliation><orgname>Iptel.org</orgname></affiliation>
+		<address>
+		    <email>[email protected]</email>
+		</address>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2007</year>
+	    <holder>Iptel.org</holder>
+	</copyright>
+    </sectioninfo>
+
+    <title>Auth Identity Module</title>
+
+    <section>
+	<title>Overview</title>
+	<para>
+		Auth Identity module provides functionalities for securely identifying
+		originators of SIP messages. This module has two basic service:
+		<itemizedlist>
+		<listitem>
+			<para>
+				<emphasis>authorizer</emphasis> - authorizes a message and adds Identity and
+				Identity-Info headers
+			</para>
+		</listitem>
+		<listitem>
+			<para>
+				<emphasis>verifier</emphasis> - verifies an authorized message
+			</para>
+		</listitem>
+		</itemizedlist>
+	</para>
+	<para>
+	    Known limitations in this version:
+	</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+			authorizer and verifier support only SIP requests except for
+			<emphasis>CANCEL</emphasis> and <emphasis>REGISTER</emphasis>
+		</para>
+	    </listitem>
+		<listitem>
+			<para>
+				verifier does not support the subjectAltName extension of
+				certificates
+			</para>
+		</listitem>
+	</itemizedlist>
+    </section>
+
+    <section id="auth_identity.dep">
+	<title>Dependencies</title>
+	<para>
+		This module does not depend any other module.
+	</para>
+    </section>
+
+	<section id="auth_identity.compilation">
+		<title>Compilation</title>
+		<para>
+		This module needs the following headers and libraries:
+		<itemizedlist>
+			<listitem>
+				<para>
+					<emphasis>OpenSSL</emphasis> (version 0.9.8 or higher) for cryptographic functions
+				</para>
+			</listitem>
+			<listitem>
+				<para>
+					<emphasis>libcURL</emphasis> for HTTP, HTTPS functions
+				</para>
+			</listitem>
+		</itemizedlist>
+		If you'd like to use <emphasis>TLS</emphasis> module too then use the
+		corresponding LIB line in auth_identity's Makefile
+		</para>
+	</section>
+
+	<section id="auth_identity.install_and_run">
+	<title>Installation And Running</title>
+	<para>
+		<emphasis>Authorizer</emphasis> service needs an opportunity to make the public key,
+		which conveyed in a certificate, available over HTTPS or HTTP for
+		verifiers. The domain the authorizer is responsible for and the
+		domain part of the URL of the certificate must be the same. This
+		service needs its private key too.
+	</para>
+    </section>
+
+
+	<section>
+		<title>Authorizer service parameters</title>
+		<section>
+			<title><varname>privatekey_path</varname> (string)</title>
+			<para>
+				The path of private key of the authentication service. The key
+				must be in PEM format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>privatekey_path</varname> parameter</title>
+				<programlisting>
+...
+modparam("auth_identity","privatekey_path","/etc/ssl/private/key.pem")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>certificate_path</varname> (string)</title>
+			<para>
+				The path of certificate of the authentication service. The
+				certificate must be in PEM format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>certificate_path</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_path","/var/www/ssl/mycert.pem")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>certificate_url</varname> (string)</title>
+			<para>
+				The url where certificate is available for other verifier
+				services. (value of Identity-info header) The
+				certificate should be in DER format.
+			</para>
+			<para>
+				This parameter is required by authentication service.
+			</para>
+			<example>
+				<title>Set <varname>certificate_url</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_url","https://foo.bar/mycert.der")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section>
+			<title><varname>msg_timeout</varname> (integer)</title>
+			<para>
+				If the Date header of message which is needed to be authenticated
+				contains a time different by more than this seconds from the current
+				time noted by the authentication service then it rejects the
+				message.
+			</para>
+			<para>
+				This parameter is optional. The default value is "600".
+			</para>
+			<example>
+				<title>Set <varname>msg_timeout</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","msg_timeout",600)
+...
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+
+	<section>
+		<title>Authorizer service functions</title>
+
+		<section>
+			<title>
+				<function>auth_date_proc()</function>
+			</title>
+			<para>
+				If a message, the auth service should authorize, contains Date header
+				then this function checks whether it falls in message timeout (set by
+				<emphasis>msg_timeout</emphasis> parameter). If there is not any Date
+				header then adds one. This function also checks whether the certificate
+				of auth service (set by <emphasis>certificate_path</emphasis> parameter)
+				has not been expired.
+			</para>
+			<section>
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section>
+			<title>
+				<function>auth_add_identity()</function>
+			</title>
+			<para>
+				Assembles digest-string from the message, calculates its SHA1 hash,
+				encrypt it with the private key (set by <emphasis>privatekey_path</emphasis>
+				parameter) of authorizer service, base64 encodes it and adds to the
+				outgoing message as the value of <emphasis>Identity</emphasis> header.
+				This function also adds Identity-Info header which contains an URI
+				(set by <emphasis>certificate_url</emphasis> parameter) from which
+				the certificate of auth service can be acquired.
+			</para>
+			<para>
+				<emphasis>Note: this function needs the final outgoing
+				message for authorization, so no module may modify any
+				digest string related headers (From, To, Call-ID, CSeq,
+				Date, Contact) and body after auth_add_identity()'s been called</emphasis>
+			</para>
+			<section>
+				<title>Dependencies</title>
+				<para>
+					auth_date_proc() must be called before
+				</para>
+			</section>
+		</section>
+	</section>
+
+	<section>
+		<title>Authorizer service examples</title>
+
+			<programlisting><![CDATA[
+...
+route[INIT]
+{
+	# we process new transactions only
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+...
+route[OUTBOUND]
+{
+	# If we are responsible for the domain of the sender of this message
+	if ($f.did && !$t.did) {
+		# Authentication service
+		if (method=="INVITE" || method=="BYE"
+			|| method=="OPTION" || method=="ACK") {
+			# Identity and Identity-info headers must not exist
+			if (@identity) {
+				t_reply("403", "Invalid Identity header");
+				drop;
+			}
+			if (@identity_info) {
+				t_reply("403", "Invalid Identity-info header");
+				drop;
+			}
+
+			if (!auth_date_proc()) {
+				t_reply("403", "Invalid Date value");
+				drop;
+			}
+
+			if (!auth_add_identity()) {
+				t_reply("480", "Authentication error");
+				drop;
+			}
+		}
+		route(FORWARD);
+	}
+}
+...
+]]></programlisting>
+	</section>
+
+
+
+
+	<section>
+		<title>Verifier service parameters</title>
+
+		<section id="auth_validity_time">
+			<title><varname>auth_validity_time</varname> (integer)</title>
+			<para>
+				The validity time of an authenticated message. The message
+				will be refused if it contains a time different by more
+				than this seconds from the current time noted by the verification
+				service.
+			</para>
+			<para>
+				This parameter is optional. The default value is "3600".
+			</para>
+			<example>
+				<title>Set <varname>auth_validity_time</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","auth_validity_time",3600)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="callid_cache_limit">
+			<title><varname>callid_cache_limit</varname> (integer)</title>
+			<para>
+				The number of Call-IDs stored in order to recognize call replay
+				attacks. A Call-ID is stored <varname>auth_validity_time</varname> long and
+				uses approximately 100 bytes memory.
+			</para>
+			<para>
+				This parameter is optional. The default value is "32768".
+				(you should increase the size of shared memory with -m
+				 command line switch if you liked to store more callid than
+				 10000)
+			</para>
+			<example>
+				<title>Set <varname>auth_validity_time</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","callid_cache_limit",32768)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="certificate_cache_limit">
+			<title><varname>certificate_cache_limit</varname> (integer)</title>
+			<para>
+				The number of certificates stored in order to avoid needless
+				download. A certificate is stored until its expiration date and
+				uses approximately 600 bytes memory.
+			</para>
+			<para>
+				This parameter is optional. The default value is "4096".
+			</para>
+			<example>
+				<title>Set <varname>certificate_cache_limit</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","certificate_cache_limit",4096)
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="cainfo_path">
+			<title><varname>cainfo_path</varname> (string)</title>
+			<para>
+				A file of trusted certificates. The file should contain multiple
+				certificates in PEM format concatenated together. It could be useful
+				for verifying a certificate not signed by a trusted CA.
+			</para>
+			<para>
+				This parameter is optional. It has not got default value.
+			</para>
+			<example>
+				<title>Set <varname>cainfo_path</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","cainfo_path","/etc/ssl/certs/ca-certificates.crt")
+...
+				</programlisting>
+			</example>
+		</section>
+
+		<section id="accept_pem_certs">
+			<title><varname>accept_pem_certs</varname> ([0|1])</title>
+			<para>
+				Enables the acquired certificate processing if it is in PEM
+				format.
+			</para>
+			<para>
+				This parameter is optional. The default value is "0".
+			</para>
+			<example>
+				<title>Set <varname>accept_pem_certs</varname> parameter
+				</title>
+				<programlisting>
+...
+modparam("auth_identity","accept_pem_certs",1)
+...
+				</programlisting>
+			</example>
+		</section>
+	</section>
+
+	<section id="auth_identity.vfyfunctions" xmlns:xi="http://www.w3.org/2001/XInclude">
+		<title>Verifier service functions</title>
+
+		<section id="vrfy_check_date">
+			<title>
+				<function>vrfy_check_date()</function>
+			</title>
+			<para>
+				Checks Date header of the incoming message whether falls in validity
+				time (set by <emphasis>auth_validity_time</emphasis> parameter)
+			</para>
+			<section id="vrfy_check_date.dep">
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_get_certificate">
+			<title>
+				<function>vrfy_get_certificate()</function>
+			</title>
+			<para>
+				Tries to get certificate defined by the value of
+				<emphasis>Identity-info</emphasis> header from certificate table
+				(which size is set by <emphasis>certificate_cache_limit</emphasis>
+				parameter). If the required certificate is not found there then
+				this function downloads it.
+			</para>
+			<section id="vrfy_get_certificate.dep">
+				<title>Dependencies</title>
+				<para>
+					No dependencies
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_certificate">
+			<title>
+				<function>vrfy_check_certificate()</function>
+			</title>
+			<para>
+				Checks whether the downloaded certificate is valid (is not expired,
+				its subject and the domain part of the URL are the same) and adds it
+				to certificate table.
+			</para>
+			<section id="vrfy_check_certificate.dep">
+				<title>Dependencies</title>
+				<para>
+					vrfy_get_certificate() must be called before
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_msgvalidity">
+			<title>
+				<function>vrfy_check_msgvalidity()</function>
+			</title>
+			<para>
+				Assembles digest-string from the message, create SHA1 hash and
+				compares it with the decrypted value of <emphasis>Identity</emphasis>
+				header.
+			</para>
+			<section id="vrfy_check_msgvalidity.dep">
+				<title>Dependencies</title>
+				<para>
+					vrfy_get_certificate() must be called before and
+					vrfy_check_certificate() should be called before
+				</para>
+			</section>
+		</section>
+
+		<section id="vrfy_check_callid">
+			<title>
+				<function>vrfy_check_callid()</function>
+			</title>
+			<para>
+				Checks whether the current call's been already processed in validity
+				time (set by <emphasis>auth_validity_time</emphasis>) to recognize
+				call replay attacks. If this call (identified by Call-id, Cseq,
+				and tag of From header triple) has not been replayed then adds it to
+				callid table (which size is set by <emphasis>callid_cache_limit</emphasis>
+				parameter).
+			</para>
+			<section id="vrfy_check_callid.dep">
+				<title>Dependencies</title>
+				<para>
+					This function should be called for the last time.
+				</para>
+			</section>
+		</section>
+	</section>
+
+	<section>
+		<title>Verifier service examples</title>
+
+		<programlisting><![CDATA[
+...
+route[INIT]
+{
+	# we process new transactions only
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+...
+route[VERIFY]
+{
+	# if we've already processed this message then we drop it
+	if (!t_newtran()) {
+		sl_reply("500", "Internal error newtran");
+		drop;
+	}
+
+	if (method=="INVITE" || method=="BYE"
+		|| method=="OPTION" || method=="ACK") {
+		# Identity and Identity-info are required for verification
+		if (!@identity) {
+			t_reply("428", "Use Identity Header");
+			drop;
+		}
+		if (!@identity_info) {
+			t_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_date()) {
+			t_reply("403", "Outdated Date header value");
+			drop;
+		}
+
+		if (!vrfy_get_certificate()) {
+			t_reply("436", "Bad Identity-Info");
+			drop;
+		}
+
+		if (!vrfy_check_certificate()) {
+			t_reply("437", "Unsupported Certificate");
+			drop;
+		}
+
+		if (!vrfy_check_msgvalidity()) {
+			t_reply("438", "Invalid Identity Header");
+			drop;
+		}
+
+		if (!vrfy_check_callid()) {
+			t_reply("403", "Message is replayed");
+			drop;
+		}
+	}
+}
+...
+]]></programlisting>
+	</section>
+</section>

+ 17 - 0
modules_s/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

+ 189 - 0
modules_s/auth_radius/README

@@ -0,0 +1,189 @@
+
+Auth_radius Module
+
+Jan Janak
+
+   FhG Fokus
+
+Juha Heinanen
+
+   Song Networks
+
+Stelios Sidiroglou-Douskos
+
+Edited by
+
+Jan Janak
+
+   Copyright © 2002, 2003 FhG FOKUS
+     _________________________________________________________
+
+   Table of Contents
+   1. User's Guide
+
+        1.1. Overview
+        1.2. Dependencies
+        1.3. Exported Parameters
+
+              1.3.1. radius_config (string)
+              1.3.2. service_type (integer)
+
+        1.4. Exported Functions
+
+              1.4.1. radius_www_authorize(realm)
+              1.4.2. radius_proxy_authorize(realm)
+
+   2. Developer's Guide
+   3. Frequently Asked Questions
+
+   List of Examples
+   1-1. radius_config parameter usage
+   1-2. radius_config usage
+   1-3. radius_www_authorize usage
+   1-4. proxy_authorize usage
+     _________________________________________________________
+
+Chapter 1. User's Guide
+
+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.4.1 or higher which is available from
+   http://developer.berlios.de/projects/radiusclient-ng/.
+
+   How to configure radius server -- more detailed description --
+   TBD.
+
+   Warning
+
+   The detailed description of radius authentication setup is
+   important since many people will use it and we want to make
+   the setup painless.
+     _________________________________________________________
+
+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. Exported 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-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 1-2. radius_config usage
+modparam("auth_radius", "service_type", 15)
+     _________________________________________________________
+
+1.4. Exported 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 1-3. 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 1-4. proxy_authorize usage
+...
+if (!radius_proxy_authorize("")) {
+    proxy_challenge("", "1");  # Realm will be autogenerated
+};
+...
+     _________________________________________________________
+
+Chapter 2. Developer's Guide
+
+   To be done.
+     _________________________________________________________
+
+Chapter 3. Frequently Asked Questions
+
+   3.1. What is the meaning of life ?
+
+   3.1. What is the meaning of life ?
+
+   42

+ 302 - 0
modules_s/auth_radius/authorize.c

@@ -0,0 +1,302 @@
+/*
+ * $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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 "../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 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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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_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_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_t)find_export("bind_auth", 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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on auth_mod.h from radius_authorize (janakj)
+ */
+
+
+#ifndef AUTHRAD_MOD_H
+#define AUTHRAD_MOD_H
+
+#include "../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_t auth_api;
+
+#endif /* AUTHRAD_MOD_H */

+ 29 - 0
modules_s/auth_radius/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = auth_radius
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 80 - 0
modules_s/auth_radius/doc/auth_radius.xml

@@ -0,0 +1,80 @@
+<?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>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </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>

+ 110 - 0
modules_s/auth_radius/doc/functions.xml

@@ -0,0 +1,110 @@
+<?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>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </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>

+ 76 - 0
modules_s/auth_radius/doc/params.xml

@@ -0,0 +1,76 @@
+<?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>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * History:
+ * -------
+ * 2003-03-09: Based on digest.c from radius_auth module (janakj)
+ */
+
+
+#include "../../mem/mem.h"
+#include "../../dprint.h"
+#include "../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
modules_s/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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 */

+ 15 - 0
modules_s/avp/Makefile

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

+ 432 - 0
modules_s/avp/README

@@ -0,0 +1,432 @@
+Avp Module
+
+Jiri Kuthan
+
+   FhG Fokus
+   <[email protected]>
+
+Michal Matyska
+
+   iptel
+   <[email protected]>
+
+   Copyright © 2004, 2005, 2006 FhG FOKUS, iptelorg GmbH
+   Revision History
+   Revision $Revision$ $Date$
+     __________________________________________________________________
+
+Overview
+
+   This module contains several functions that can be used to manipulate
+   the contents of AVPs (Attribute-Value pairs). The AVPs are variables
+   attached to the SIP message being processed. Each variable has its name
+   and value. AVPs can be used to store arbitrary data or as a means of
+   inter-module comminication.
+
+   You may also want to check the avpops module which is more flexible and
+   contains more functions. In future SER releases the avp module will be
+   probably deprecated in favor of avpops module.
+
+Functions
+
+   Revision History
+   Revision $Revision$ $Date$
+
+set_iattr(attribute,value)
+
+   Create an AVP of type integer.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP to be created.
+     * value - Integer value of the AVP.
+
+   Example 1. set_iattr usage
+...
+set_iattr("fr_inv_timer", "60")
+...
+
+flags2attr("$avp")
+
+   Store the current state of SER flags into the sepcified avp.
+
+   Example 2. flags2attr usage
+...
+flags2attr("$msg_flags")
+...
+
+set_sattr(attribute,value)
+
+   Create an AVP of type string.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP to be created.
+     * value - String value of the AVP.
+
+   Example 3. set_sattr usage
+...
+set_sattr("called_number", "1234")
+...
+
+print_attr($attribute)
+
+   Print the value of an AVP to syslog.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP.
+
+attr2uri($attribute[,uri-part])
+
+   Rewrite the whole Request-URI of the message being processed with the
+   value of an AVP, or if an uri-part is specified, rewrite only that
+   specific part.
+
+   Meaning of the parameter is as follows:
+     * $attribute - The name of the AVP.
+     * uri-part - The name of the part of the uri that will be rewritten.
+       The supported values are: "prefix", "uri", "username", "user",
+       "usernamepassword", "userpass", "domain", "host", "domainport",
+       "hostport", "port", "strip", "strip_tail". "prefix" will add the
+       AVP as a prefix to the username (equivalent to prefix("string")),
+       "strip" and "strip_tail" expect a number in the AVP and they will
+       remove the specified number of characters from the beginning,
+       respective the end of the username part of the uri. The rest of the
+       uri-part values names are self-explaining.
+
+attr_exists(attribute)
+
+   Test for the existence of AVP with given name. The function returns 1
+   if given AVP exists and -1 if not.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP.
+
+   Example 4. attr_exists usage
+...
+if (attr_exists("saved_ruri")) {
+  attr2uri("saved_uri");
+} else {
+  rewriteuri("sip:[email protected]");
+};
+...
+
+attr_equals(attribute, value)
+
+   Test whether an AVP with given name and value exists. The function
+   returns 1 if the AVP with given name and value exists and -1 if not.
+   The value of the AVP is compared string-wise. The comparison is case
+   sensitive.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP.
+     * value - The AVP value to look for.
+
+attr_equals_xl(attribute, xl_format)
+
+   Test whether an AVP with given name and value exists. The function
+   returns 1 if the AVP with given name and value exists and -1 if not.
+   The value of the AVP is compared string-wise to the result of xlog
+   formatting call. The comparison is case sensitive.
+
+   Meaning of the parameter is as follows:
+     * attribute - The name of the AVP.
+     * xl_format - The xlog formatting string, result of which is looked
+       for in AVP.
+
+   Note: You must ensure, that the xlog module is loaded to be able to use
+   this function.
+
+   Example 5. attr_equals_xl usage
+...
+if (attr_equals_xl("my_avp", "%ct")) {
+  # my_avp has value equal to current Contact header field
+} else {
+  # my_avp was different
+}
+...
+
+dump_attrs()
+
+   Dumps all AVPs in user lists to the debug output (with level INFO).
+
+   The function does not require any parameters.
+
+xlset_attr($attribute, xl_format)
+
+   Creates new AVP identified by attribute and assigns the result string
+   of xlog formatting rules as its value.
+
+   Meaning of the parameter is as follows:
+     * $attribute - The name of the AVP.
+     * xl_format - String used for xlog formatting. For detailed info see
+       documentation of xlog module.
+
+   Note: You must ensure, that the xlog module is loaded to be able to use
+   this function.
+
+xlfix_attr($attribute)
+
+   Fixes an xl formatted attribute value to pure string.
+
+   Meaning of the parameter is as follows:
+     * $attribute - The name of the AVP.
+
+insert_attr_hf(name)
+
+   Inserts new header into the request, which is beeing forwarded. The AVP
+   name is the name of the header field. If you need to insert header with
+   name which differs from the AVP name use insert_attr_hf(header_name,
+   $avp_name) instead.
+
+   Inserting means putting the header to the beginning of the request,
+   before any others.
+
+   Meaning of the parameter is as follows:
+     * name - The name of the header field which is inserted into
+       forwarded request as well as name of AVP which's value is put as
+       the header field value.
+
+insert_attr_hf(header_name, $avp_name)
+
+   Inserts new header into the request, which is beeing forwarded.
+
+   Inserting means putting the header to the beginning of the request,
+   before any others.
+
+   Meaning of the parameter is as follows:
+     * header_name - The name of the header field which is inserted into
+       forwarded request.
+     * $avp_name - The name of AVP which's value is put as the header
+       field value.
+
+   Example 6. insert_attr_hf usage
+...
+set_sattr("my_route","<sip:user@host:port;lr>");
+insert_attr_hf("Route", "$my_route");
+...
+
+append_attr_hf(name)
+
+   Appends new header into the request, which is beeing forwarded. The AVP
+   name is the name of the header field. If you need to append header with
+   name which differs from the AVP name use append_attr_hf(header_name,
+   $avp_name) instead.
+
+   Appending means putting the header to the end of the request, after any
+   others.
+
+   Meaning of the parameter is as follows:
+     * name - The name of the header field which is appended into
+       forwarded request as well as name of AVP which's value is put as
+       the header field value.
+
+append_attr_hf(header_name, $avp_name)
+
+   Appends new header into the request, which is beeing forwarded.
+
+   Appending means putting the header to the end of the request, after any
+   others.
+
+   Meaning of the parameter is as follows:
+     * header_name - The name of the header field which is appended into
+       forwarded request.
+     * $avp_name - The name of AVP which's value is put as the header
+       field value.
+
+replace_attr_hf(name)
+
+   Replaces header in the request, which is beeing forwarded. The AVP name
+   is the same as the name of the header field. If you need to replace
+   header with name which differs from the AVP name use
+   replace_attr_hf(header_name, $avp_name) instead.
+
+   Replacing means removing all the headers with specified name and
+   appending new one at the end, with the value from AVP.
+
+   Meaning of the parameter is as follows:
+     * name - The name of the header field which is replaced in forwarded
+       request as well as name of AVP which's value is put as the header
+       field value.
+
+replace_attr_hf(header_name, $avp_name)
+
+   Replaces header in the request, which is beeing forwarded.
+
+   Replacing means removing all the headers with specified name and
+   appending new one at the end, with the value from AVP.
+
+   Meaning of the parameter is as follows:
+     * header_name - The name of the header field which is replaced in
+       forwarded request.
+     * $avp_name - The name of AVP which's value is put as the header
+       field value.
+
+attr_to_reply(name)
+
+   Appends new header into the reply at the request time processing. The
+   AVP name is the name of the header field. If you need to append header
+   with name which differs from the AVP name use
+   attr_to_reply(header_name, $avp_name) instead.
+
+   If you need to append headers during reply processing you can use
+   insert_attr_hf and append_attr_hf. This function stores data and waits
+   for the reply being created.
+
+   Meaning of the parameter is as follows:
+     * name - The name of the header field which is appended into reply as
+       well as name of AVP which's value is put as the header field value.
+
+attr_to_reply(header_name, $avp_name)
+
+   Appends new header into the reply at the request time processing.
+
+   Meaning of the parameter is as follows:
+     * header_name - The name of the header field which is appended into
+       reply.
+     * $avp_name - The name of AVP which's value is put as the header
+       field value.
+
+   Example 7. attr_to_reply usage
+...
+xlset_attr("$my_route","<sip:%Hf:5080;lr>";
+attr_to_reply("P-Hint-Route", "my_route");
+...
+
+attr_destination($avp_name)
+
+   Sets the destination of the forwarded request to the value of AVP,
+   which must be either a SIP URI or a string in nameaddr format (e.g.
+   "Foo Bar" <sip:uri>).
+
+   Meaning of the parameter is as follows:
+     * $avp_name - The name of AVP which's value is used for further
+       request forwarding.
+
+   Example 8. attr_destination usage
+...
+xlset_attr("$my_route","<sip:%<next_host>:%<next_port>;myparam=a123;lr>");
+insert_attr_hf("Route", "$my_route");
+attr_destination("$my_route");
+t_relay();
+...
+
+xlset_destination(xl_format)
+
+   Sets the destination of the forwarded request to the value of result of
+   xlog formatted string. Either SIP URI or nameaddr format is allowed.
+
+   Meaning of the parameter is as follows:
+     * xl_format - xlog module formatting string, the result is used for
+       request forwarding.
+
+   Note: You must ensure, that the xlog module is loaded to be able to use
+   this function.
+
+   Example 9. xlset_destination usage
+...
+xlset_destination("%<next_host>:%<next_port>");
+t_relay();
+...
+
+subst_attr($avp_name, subst_re)
+
+   The value of the AVP identified by $avp_name name is matched and
+   substitued according to the subst_re sed like expression. Result of the
+   substitution is then stored in the original AVP.
+
+   Meaning of the parameter is as follows:
+     * $avp_name - Name of the AVP which will be used for the
+       substitution.
+     * subst_re - SED like match&replace regullar expression.
+
+   Example 10. subst_attr usage
+...
+subst_attr("$uri","/tel:[0-9]*/sip:\[email protected];user=phone/");
+...
+
+del_attr($avp_name)
+
+   The AVP identified by $avp_name name is deleted.
+
+   Meaning of the parameter is as follows:
+     * $avp_name - Name of the AVP which will be deleted.
+
+   Example 11. del_attr usage
+...
+failure_route[1] {
+        if (status=~4[0-9][0-9]) {
+                if (attr_exists("backup_gw") {
+                        append_branch;
+                        attr_destination("backup_gw");
+                        del_attr("backup_gw");
+                        t_relay();
+                }
+        }
+...
+
+hdr_body2attrs(headername, prefix)
+
+   Function parses a header body content scans for fld1=val1,fld2=val2,...
+   and creates bunch of avps prefixfld1:= val1, prefixfld2:= val2, .... If
+   possible stores values as integers.
+
+   Meaning of the parameter is as follows:
+     * headername - The header name, which will be scanned for the
+       name=value pairs.
+       If you want to create only AVPs with integer value use "/i" postfix
+       to the header name.
+       If you want to create only AVPs with string value use "/s" postfix
+       to the header name.
+     * prefix - The prefix, which is added before the name parsed from the
+       header body.
+
+hdr_body2attrs2(headername, prefix)
+
+   Function parses a header body content scans for
+   fld1=val1,val2;fld2=val3,... and creates bunch of avps prefixfld1#1:=
+   val1, prefixfld1#2:= val2, prefixfld2:=val3 .... If possible stores
+   values as integers.
+
+   Meaning of the parameter is as follows:
+     * headername - The header name, which will be scanned for the
+       name=value pairs.
+       If you want to create only AVPs with integer value use "/i" postfix
+       to the header name.
+       If you want to create only AVPs with string value use "/s" postfix
+       to the header name.
+     * prefix - The prefix, which is added before the name parsed from the
+       header body.
+
+   Example 12. hdr_body2attrs and hdr_body2attrs2 usage
+if (method=="BYE") {
+        # QoS reporting
+        if (search("^User-Agent: AVM FRITZ!Box Fon*")) {
+                hdr_body2attrs2("X-RTP-Stat/i", "QoS_");
+                xlog("L_INFO", "QoS: %Ts, %fu, %tu, %ci, %{User-Agent}, %{X-RTP-
+Stat}\n");
+        } else if (search("^User-Agent: Sipura/*")) {
+                hdr_body2attrs("P-RTP-Stat/i", "QoS_");
+                xlog("L_INFO", "QoS: %Ts, %fu, %tu, %ci, %{User-Agent}, %{P-RTP-
+Stat}\n");
+        }
+}
+# AVP QoS_xx now contain the values from appropriate header
+# e.g. QoS_JI is jitter
+
+Parameters
+
+   Revision History
+   Revision $Revision$ $Date$
+
+xlbuf_size (integer)
+
+   Defines size of internal buffer for all xlog formatting calls. If you
+   don't use xlog formatting calls, you can set it to 0 to preserve some
+   memory, if you get errors while formatting due to buffer size, you can
+   enlarge it.
+
+   Default value is 256.
+
+   Example 13. Set xlbuf_size parameter
+...
+modparam("avp", "xlbuf_size", 1024)
+...

+ 1658 - 0
modules_s/avp/avp.c

@@ -0,0 +1,1658 @@
+/*
+ * $Id$
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ *  2005-03-28  avp_destination & xlset_destination - handle both nameaddr & uri texts (mma)
+ *  2005-12-22  merge changes from private branch (mma)
+ *  2006-01-03  avp_body merged (mma)
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#ifdef EXTRA_DEBUG
+#include <assert.h>
+#endif
+#include "../../sr_module.h"
+#include "../../error.h"
+#include "../../lump_struct.h"
+#include "../../data_lump.h"
+#include "../../data_lump_rpl.h"
+#include "../../usr_avp.h"
+#include "../../mem/mem.h"
+#include "../../parser/parse_uri.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/parse_nameaddr.h"
+#include "../../ut.h"
+#include "../../dset.h"
+#include "../../trim.h"
+#include "../../str.h"
+#include "../../dprint.h"
+#include "../../re.h"
+#include "../../action.h"
+
+#include "../../parser/parse_hname2.h"
+#include "../xlog/xl_lib.h"
+#define NO_SCRIPT -1
+
+MODULE_VERSION
+
+/* name of attributed used to store flags with command flags2attr */
+#define HDR_ID 0
+#define HDR_STR 1
+
+#define PARAM_DELIM '/'
+#define VAL_TYPE_INT (1<<0)
+#define VAL_TYPE_STR (1<<1)
+
+struct hdr_name {
+	enum {hdrId, hdrStr} kind;
+	union {
+		int n;
+		str s;
+	} name;
+	char field_delimiter;
+	char array_delimiter;
+	int val_types;
+};
+
+static int xlbuf_size=256;
+static char* xlbuf=NULL;
+static str* xl_nul=NULL;
+static xl_print_log_f* xl_print=NULL;
+static xl_parse_format_f* xl_parse=NULL;
+static xl_elog_free_all_f* xl_free=NULL;
+static xl_get_nulstr_f* xl_getnul=NULL;
+
+static int mod_init();
+static int set_iattr(struct sip_msg* msg, char* p1, char* p2);
+static int set_sattr(struct sip_msg* msg, char* p1, char* p2);
+static int print_attr(struct sip_msg* msg, char* p1, char* p2);
+static int del_attr(struct sip_msg* msg, char* p1, char* p2);
+static int subst_attr(struct sip_msg* msg, char* p1, char* p2);
+static int flags2attr(struct sip_msg* msg, char* p1, char* p2);
+static int attr2uri(struct sip_msg* msg, char* p1, char* p2);
+static int dump_attrs(struct sip_msg* msg, char* p1, char* p2);
+static int attr_equals(struct sip_msg* msg, char* p1, char* p2);
+static int attr_exists(struct sip_msg* msg, char* p1, char* p2);
+static int attr_equals_xl(struct sip_msg* msg, char* p1, char* p2);
+static int xlset_attr(struct sip_msg* msg, char* p1, char* p2);
+static int xlfix_attr(struct sip_msg* msg, char* p1, char* p2);
+static int insert_req(struct sip_msg* msg, char* p1, char* p2);
+static int append_req(struct sip_msg* msg, char* p1, char* p2);
+static int replace_req(struct sip_msg* msg, char* p1, char* p2);
+static int append_reply(struct sip_msg* msg, char* p1, char* p2);
+static int attr_destination(struct sip_msg* msg, char* p1, char* p2);
+static int xlset_destination(struct sip_msg* msg, char* p1, char* p2);
+static int attr_hdr_body2attrs(struct sip_msg* msg, char* p1, char* p2);
+static int attr_hdr_body2attrs2(struct sip_msg* msg, char* p1, char* p2);
+static int del_attrs(struct sip_msg* msg, char* p1, char* p2);
+
+static int set_iattr_fixup(void**, int);
+static int avpid_fixup(void**, int);
+static int subst_attr_fixup(void**, int);
+static int fixup_part(void**, int);
+static int fixup_xl_1(void**, int);
+static int fixup_attr_1_xl_2(void**, int);
+static int fixup_str_1_attr_2(void**, int);
+static int xlfix_attr_fixup(void** param, int param_no);
+static int attr_hdr_body2attrs_fixup(void**, int);
+static int attr_hdr_body2attrs2_fixup(void**, int);
+static int avpgroup_fixup(void**, int);
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+    {"set_iattr",         set_iattr,            2, set_iattr_fixup,            REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"set_sattr",         set_sattr,            2, fixup_var_str_12,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"set_attr",          set_sattr,            2, fixup_var_str_12,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"print_attr",        print_attr,           1, avpid_fixup,                REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"del_attr",          del_attr,             1, avpid_fixup,                REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE}, 
+    {"del_attrs",         del_attrs,            1, avpgroup_fixup,             REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE}, 
+    {"subst_attr",        subst_attr,           2, subst_attr_fixup,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"flags2attr",        flags2attr,           1, avpid_fixup,                REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"attr2uri",          attr2uri,             1, fixup_part,                 REQUEST_ROUTE | FAILURE_ROUTE},
+    {"attr2uri",          attr2uri,             2, fixup_part,                 REQUEST_ROUTE | FAILURE_ROUTE},
+    {"dump_attrs",	  dump_attrs,           0, 0,                          REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"dump_attrs",	  dump_attrs,           1, avpgroup_fixup,             REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"attr_equals",       attr_equals,          2, fixup_var_str_12,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"attr_exists",       attr_exists,          1 , fixup_var_str_1,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"attr_equals_xl",    attr_equals_xl,       2, fixup_attr_1_xl_2,          REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"xlset_attr",        xlset_attr,           2, fixup_attr_1_xl_2,          REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"xlfix_attr",        xlfix_attr,           1, xlfix_attr_fixup,           REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE}, 
+    {"insert_attr_hf",    insert_req,           2, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"insert_attr_hf",    insert_req,           1, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"append_attr_hf",    append_req,           2, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"append_attr_hf",    append_req,           1, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"replace_attr_hf",   replace_req,          2, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"replace_attr_hf",   replace_req,          1, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"attr_to_reply",     append_reply,         2, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"attr_to_reply",     append_reply,         1, fixup_str_1_attr_2,         REQUEST_ROUTE | FAILURE_ROUTE},
+    {"attr_destination",  attr_destination,     1, avpid_fixup,                REQUEST_ROUTE | FAILURE_ROUTE}, 
+    {"xlset_destination", xlset_destination,    1, fixup_xl_1,                 REQUEST_ROUTE},
+    {"hdr_body2attrs",    attr_hdr_body2attrs,  2, attr_hdr_body2attrs_fixup,  REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"hdr_body2attrs2",   attr_hdr_body2attrs2, 2, attr_hdr_body2attrs2_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+    {"xlbuf_size", PARAM_INT, &xlbuf_size},
+    {0, 0, 0}
+};
+
+
+struct module_exports exports = {
+    "avp",
+    cmds,       /* Exported commands */
+    0,          /* RPC */
+    params,     /* Exported parameters */
+    mod_init,          /* module initialization function */
+    0,          /* response function*/
+    0,          /* destroy function */
+    0,          /* oncancel function */
+    0           /* per-child init function */
+};
+
+
+static int set_iattr_fixup(void** param, int param_no)
+{
+    if (param_no == 1) {
+	return fixup_var_str_12(param, param_no);
+    } else {
+	return fixup_var_int_12(param, param_no);
+    }
+}
+
+
+static int get_avp_id(avp_ident_t* id, fparam_t* p, struct sip_msg* msg)
+{
+    str str_id;
+    avp_t* avp;
+    avp_value_t val;
+    int ret;
+
+    switch(p->type) {
+    case FPARAM_AVP:
+	avp = search_avp(p->v.avp, &val, 0);
+	if (!avp) {
+	    DBG("get_avp_id: AVP %s does not exist\n", p->orig);
+	    return -1;
+	}
+	if ((avp->flags & AVP_VAL_STR) == 0) {
+	    DBG("get_avp_id: Not a string AVP\n");
+	    return -1;
+	}
+	str_id = val.s;
+	break;
+
+    case FPARAM_SELECT:
+	ret = run_select(&str_id, p->v.select, msg);
+	if (ret < 0 || ret > 0) return -1;
+	break;
+
+	case FPARAM_STR:
+	str_id = p->v.str;
+	break;
+
+    default:
+	ERR("Invalid parameter type in get_avp_id\n");
+	return -1;
+    }
+
+    return parse_avp_ident(&str_id, id);
+}
+
+
+static int set_iattr(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t avpid;
+    int_str value;
+
+    if (get_avp_id(&avpid, (fparam_t*)p1, msg) < 0) {
+	return -1;
+    }
+    
+    if (get_int_fparam(&value.n, msg, (fparam_t*)p2) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+    if (add_avp(avpid.flags | AVP_NAME_STR, avpid.name, value) != 0) {
+	ERR("add_avp failed\n");
+	return -1;
+    }
+    return 1;
+}
+
+
+static int set_sattr(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t avpid;
+    int_str value;
+    
+    if (get_avp_id(&avpid, (fparam_t*)p1, msg) < 0) {
+	return -1;
+    }
+
+    if (get_str_fparam(&value.s, msg, (fparam_t*)p2) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p2)->orig);
+	return -1;
+    }
+    
+    if (add_avp(avpid.flags | AVP_NAME_STR | AVP_VAL_STR, avpid.name, value) != 0) {
+	ERR("add_avp failed\n");
+	return -1;
+    }
+    
+    return 1;
+}
+
+
+static int avpid_fixup(void** param, int param_no)
+{
+    if (param_no == 1) {
+		if (fix_param(FPARAM_AVP, param) == 0) return 0;
+		ERR("Invalid AVP identifier: '%s'\n", (char*)*param);
+		return -1;
+    }
+    return 0;
+}
+
+
+static int print_attr(struct sip_msg* msg, char* p1, char* p2)
+{
+    fparam_t* fp;
+    int_str value;
+    avp_t *avp;
+
+    fp = (fparam_t*)p1;
+    
+    avp = search_avp(fp->v.avp, &value, NULL);
+    if (avp == 0) {
+	LOG(L_INFO, "AVP '%s' not found\n", fp->orig);
+	return -1;
+    }
+    
+    if (avp->flags & AVP_VAL_STR) {
+	LOG(L_INFO, "AVP: '%s'='%.*s'\n", 
+	    fp->orig, value.s.len, ZSW(value.s.s));
+    } else {
+	LOG(L_INFO, "AVP: '%s'=%d\n", fp->orig, value.n);
+    }
+    return 1;
+}
+
+
+static int del_attr(struct sip_msg* msg, char* p1, char* p2)
+{
+    fparam_t* fp;
+    avp_t* avp;
+    struct search_state st;	
+    
+    fp = (fparam_t*)p1;
+    
+    avp = search_avp(fp->v.avp, 0, &st);
+    while (avp) {
+	destroy_avp(avp);
+	avp = search_next_avp(&st, 0);
+    }
+    return 1;
+}
+
+
+static int del_attrs(struct sip_msg* msg, char* p1, char* p2)
+{
+    return (reset_avp_list((unsigned long)p1) == 0) ? 1 : -1;
+}
+			
+
+static int subst_attr_fixup(void** param, int param_no)
+{
+    if (param_no == 1) {
+		return avpid_fixup(param, 1);
+    }
+    if (param_no == 2) {
+		if (fix_param(FPARAM_SUBST, param) != 0) return -1;
+    }
+    return 0;
+}
+
+
+static int subst_attr(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_t* avp;
+    avp_value_t val;
+    str *res = NULL;
+    int count;
+    avp_ident_t* name = &((fparam_t*)p1)->v.avp;
+
+    if ((avp = search_avp(*name, &val, NULL))) {
+	if (avp->flags & AVP_VAL_STR) {
+	    res = subst_str(val.s.s, msg, ((fparam_t*)p2)->v.subst, &count);
+	    if (res == NULL) {
+		ERR("avp_subst: error while running subst\n");
+		goto error;
+	    }
+
+	    DBG("avp_subst: %d, result %.*s\n", count, res->len, ZSW(res->s));
+	    val.s = *res;
+	    
+	    if (add_avp_before(avp, name->flags | AVP_VAL_STR, name->name, val)) {
+		ERR("avp_subst: error while adding new AVP\n");
+		goto error;
+	    }
+	    
+	    destroy_avp(avp);
+	    return 1;
+	} else {
+	    ERR("avp_subst: AVP has numeric value\n");
+	    goto error;
+	}
+    } else {
+	ERR("avp_subst: AVP[%.*s] index %d, flags %x not found\n", 
+	    name->name.s.len, name->name.s.s,
+	    name->index, name->flags);
+	goto error;
+    }
+
+ error:
+    if (res) pkg_free(res);
+    return -1;
+}
+
+
+static int flags2attr(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t* id;
+    int_str value;
+    
+    value.n = msg->flags;
+    
+    id = &((fparam_t*)p1)->v.avp;
+
+    if (add_avp(id->flags, id->name, value) != 0) {
+	ERR("add_avp failed\n");
+	return -1;
+    }
+    
+    return 1;
+}
+
+
+static int fixup_part(void** param, int param_no) 
+{
+    int i;
+    fparam_t* fp;
+    
+    static struct {
+		char* s;
+		int i;
+    } fixup_parse[] = {
+		{"", SET_URI_T},
+		{"prefix", PREFIX_T},
+		{"uri", SET_URI_T},
+		{"username", SET_USER_T},
+		{"user", SET_USER_T},
+		{"usernamepassword", SET_USERPASS_T},
+		{"userpass", SET_USERPASS_T},
+		{"domain", SET_HOST_T},
+		{"host", SET_HOST_T},
+		{"domainport", SET_HOSTPORT_T},
+		{"hostport", SET_HOSTPORT_T},
+		{"port", SET_PORT_T},
+		{"strip", STRIP_T},
+		{"strip_tail", STRIP_TAIL_T},
+		{0, 0}
+    };
+    
+    if (param_no == 1) {
+		return avpid_fixup(param, 1);
+    } else if (param_no == 2) {
+		/* Create fparam structure */
+		if (fix_param(FPARAM_STRING, param) != 0) return -1;
+		
+		/* We will parse the string now and store the value
+		 * as int
+		 */
+		fp = (fparam_t*)*param;
+		fp->type = FPARAM_INT;
+		
+		for(i = 0; fixup_parse[i].s; i++) {
+			if (!strcasecmp(fp->orig, fixup_parse[i].s)) {
+				fp->v.i = fixup_parse[i].i;
+				return 1;
+			}
+		}
+		
+		ERR("Invalid parameter value: '%s'\n", fp->orig);
+		return -1;
+    }
+    return 0;
+}
+
+
+static int attr2uri(struct sip_msg* msg, char* p1, char* p2)
+{
+    int_str value;
+    avp_t* avp_entry;
+    struct action act;
+	struct run_act_ctx ra_ctx;
+    int pnr;
+    unsigned int u;
+    
+    if (p2) {
+		pnr = ((fparam_t*)p2)->v.i;
+    } else {
+		pnr = SET_URI_T;
+    }
+    
+    avp_entry = search_avp(((fparam_t*)p1)->v.avp, &value, NULL);
+    if (avp_entry == 0) {
+		ERR("attr2uri: AVP '%s' not found\n", ((fparam_t*)p1)->orig);
+		return -1;
+    }
+    
+    memset(&act, 0, sizeof(act));
+	
+    if ((pnr == STRIP_T) || (pnr == STRIP_TAIL_T)) {
+		/* we need integer value for these actions */
+        if (avp_entry->flags & AVP_VAL_STR) {
+			if (str2int(&value.s, &u)) {
+				ERR("not an integer value: %.*s\n",
+					value.s.len, value.s.s);
+				return -1;
+			}
+			act.val[0].u.number = u;
+		} else {
+			act.val[0].u.number = value.n;
+		}
+		act.val[0].type = NUMBER_ST;
+    } else {
+		/* we need string value */
+		if ((avp_entry->flags & AVP_VAL_STR) == 0) {
+			act.val[0].u.string = int2str(value.n, NULL);
+		} else {
+			act.val[0].u.string = value.s.s;
+		}
+		act.val[0].type = STRING_ST;
+    }
+    act.type = pnr;
+    init_run_actions_ctx(&ra_ctx);
+    if (do_action(&ra_ctx, &act, msg) < 0) {
+		ERR("failed to change ruri part.\n");
+		return -1;
+    }
+    return 1;
+}
+
+
+/*
+ * sends avp list to log in readable form
+ *
+ */
+static void dump_avp_reverse(avp_t* avp)
+{
+    str* name;
+    int_str val;
+    
+    if (avp) {
+	     /* AVPs are added to front of the list, reverse by recursion */
+	dump_avp_reverse(avp->next);
+	
+	name=get_avp_name(avp);
+	get_avp_val(avp, &val);
+	switch(avp->flags&(AVP_NAME_STR|AVP_VAL_STR)) {
+	case 0:
+		 /* avp type ID, int value */
+	    LOG(L_INFO,"AVP[%d]=%d\n", avp->id, val.n);
+	    break;
+
+	case AVP_NAME_STR:
+		 /* avp type str, int value */
+	    name=get_avp_name(avp);
+	    LOG(L_INFO,"AVP[\"%.*s\"]=%d\n", name->len, name->s, val.n);
+	    break;
+
+	case AVP_VAL_STR:
+		 /* avp type ID, str value */
+	    LOG(L_INFO,"AVP[%d]=\"%.*s\"\n", avp->id, val.s.len, val.s.s);
+	    break;
+
+	case AVP_NAME_STR|AVP_VAL_STR:
+		 /* avp type str, str value */
+	    name=get_avp_name(avp);
+	    LOG(L_INFO,"AVP[\"%.*s\"]=\"%.*s\"\n", name->len, name->s, val.s.len, val.s.s);
+	    break;
+	}
+    }
+}
+
+
+static int dump_attrs(struct sip_msg* m, char* x, char* y)
+{
+    avp_list_t avp_list;
+    unsigned long flags;
+
+    if (x) {
+	flags = (unsigned long)x;
+    } else {
+	flags = AVP_CLASS_ALL | AVP_TRACK_ALL;
+    }
+
+
+    if (flags & AVP_CLASS_GLOBAL) {
+	avp_list = get_avp_list(AVP_CLASS_GLOBAL);
+	INFO("class=GLOBAL\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+
+    if (flags & AVP_CLASS_DOMAIN && flags & AVP_TRACK_FROM) {
+	avp_list = get_avp_list(AVP_CLASS_DOMAIN | AVP_TRACK_FROM);
+	INFO("track=FROM class=DOMAIN\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+
+    if (flags & AVP_CLASS_DOMAIN && flags & AVP_TRACK_TO) {
+	avp_list = get_avp_list(AVP_CLASS_DOMAIN | AVP_TRACK_TO);
+	INFO("track=TO class=DOMAIN\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+
+    if (flags & AVP_CLASS_USER && flags & AVP_TRACK_FROM) {
+	avp_list = get_avp_list(AVP_CLASS_USER | AVP_TRACK_FROM);
+	INFO("track=FROM class=USER\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+
+    if (flags & AVP_CLASS_USER && flags & AVP_TRACK_TO) {
+	avp_list = get_avp_list(AVP_CLASS_USER | AVP_TRACK_TO);
+	INFO("track=TO class=USER\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+
+    if (flags & AVP_CLASS_URI && flags & AVP_TRACK_FROM) {
+	avp_list = get_avp_list(AVP_CLASS_URI | AVP_TRACK_FROM);
+	INFO("track=FROM class=URI\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }	
+
+    if (flags & AVP_CLASS_URI && flags & AVP_TRACK_TO) {
+	avp_list = get_avp_list(AVP_CLASS_URI | AVP_TRACK_TO);
+	INFO("track=TO class=URI\n");
+	if (!avp_list) {
+	    LOG(L_INFO,"INFO: No AVP present\n");
+	} else {
+	    dump_avp_reverse(avp_list);
+	}
+    }
+    return 1;
+}
+
+
+/*
+ *  returns 1 if msg contains an AVP with the given name and value,
+ *  returns -1 otherwise
+ */
+static int attr_equals(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t avpid;
+    int_str value, avp_value;
+    avp_t* avp;
+    struct search_state st;
+
+    if (get_avp_id(&avpid, (fparam_t*)p1, msg) < 0) {
+	return -1;
+    }
+
+    if (p2 && get_str_fparam(&value.s, msg, (fparam_t*)p2) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p2)->orig);
+	return -1;
+    }
+
+    avp = search_avp(avpid, &avp_value, &st);
+    if (avp == 0) return -1;
+
+    if (!p2) return 1;
+    
+    while (avp != 0) {
+	if (avp->flags & AVP_VAL_STR) {
+	    if ((avp_value.s.len == value.s.len) &&
+		!memcmp(avp_value.s.s, value.s.s, avp_value.s.len)) {
+		return 1;
+	    }
+	} else {
+	    if (avp_value.n == str2s(value.s.s, value.s.len, 0)) {
+		return 1;
+	    }
+	}
+	avp = search_next_avp(&st, &avp_value);
+    }
+    
+    return -1;
+}
+
+
+static int attr_exists(struct sip_msg* msg, char* p1, char* p2)
+{
+	return attr_equals(msg, p1, NULL);
+}
+
+
+static int xl_printstr(struct sip_msg* msg, xl_elog_t* format, char** res, int* res_len)
+{
+    int len;
+    
+    if (!format || !res) {
+	LOG(L_ERR, "xl_printstr: Called with null format or res\n");
+	return -1;
+    }
+    
+    if (!xlbuf) {
+	xlbuf = pkg_malloc((xlbuf_size+1)*sizeof(char));
+	if (!xlbuf) {
+	    LOG(L_CRIT, "xl_printstr: No memory left for format buffer\n");
+	    return -1;
+	}
+    }
+    
+    len = xlbuf_size;
+    if (xl_print(msg, format, xlbuf, &len)<0) {
+	LOG(L_ERR, "xl_printstr: Error while formating result\n");
+	return -1;
+    }
+    
+    if ((xl_nul) && (xl_nul->len == len) && !strncmp(xl_nul->s, xlbuf, len)) {
+	return 0;
+    }
+
+    *res = xlbuf;
+    if (res_len) {
+	*res_len=len;
+    }
+    return len;
+}
+
+
+static int attr_equals_xl(struct sip_msg* msg, char* p1, char* format)
+{
+    avp_ident_t* avpid;
+    avp_value_t avp_val;
+    struct search_state st;
+    str xl_val;
+    avp_t* avp;
+    
+    avpid = &((fparam_t*)p1)->v.avp;
+
+    if (xl_printstr(msg, (xl_elog_t*) format, &xl_val.s, &xl_val.len) > 0) {
+	for (avp = search_avp(*avpid, &avp_val, &st); avp; avp = search_next_avp(&st, &avp_val)) {
+	    if (avp->flags & AVP_VAL_STR) {
+		if ((avp_val.s.len == xl_val.len) &&
+		    !memcmp(avp_val.s.s, xl_val.s, avp_val.s.len)) return 1;
+	    } else {
+		if (avp_val.n == str2s(xl_val.s, xl_val.len, 0)) return 1;
+	    }
+	}
+	return -1;
+    }
+    
+    ERR("avp_equals_xl:Error while expanding xl_format\n");
+    return -1;
+}
+
+/* get the pointer to the xl lib functions */
+static int get_xl_functions(void)
+{
+    if (!xl_print) {
+	xl_print=(xl_print_log_f*)find_export("xprint", NO_SCRIPT, 0);
+	
+	if (!xl_print) {
+	    LOG(L_CRIT,"ERROR: cannot find \"xprint\", is module xlog loaded?\n");
+	    return -1;
+	}
+    }
+    
+    if (!xl_parse) {
+	xl_parse=(xl_parse_format_f*)find_export("xparse", NO_SCRIPT, 0);
+	
+	if (!xl_parse) {
+	    LOG(L_CRIT,"ERROR: cannot find \"xparse\", is module xlog loaded?\n");
+	    return -1;
+	}
+    }
+
+    if (!xl_free) {
+	xl_free=(xl_elog_free_all_f*)find_export("xfree", NO_SCRIPT, 0);
+	
+	if (!xl_free) {
+	    LOG(L_CRIT,"ERROR: cannot find \"xfree\", is module xlog loaded?\n");
+	    return -1;
+	}
+    }
+
+    if (!xl_nul) {
+	xl_getnul=(xl_get_nulstr_f*)find_export("xnulstr", NO_SCRIPT, 0);
+	if (xl_getnul) {
+	    xl_nul=xl_getnul();
+	}
+	
+	if (!xl_nul){
+	    LOG(L_CRIT,"ERROR: cannot find \"xnulstr\", is module xlog loaded?\n");
+	    return -1;
+	} else {
+	    LOG(L_INFO,"INFO: xlog null is \"%.*s\"\n", xl_nul->len, xl_nul->s);
+	}
+	
+    }
+
+    return 0;
+}
+
+/*
+ * Convert xl format string to xl format description
+ */
+static int fixup_xl_1(void** param, int param_no)
+{
+    xl_elog_t* model;
+
+    if (get_xl_functions()) return -1;
+
+    if (param_no == 1) {
+	if(*param) {
+	    if(xl_parse((char*)(*param), &model)<0) {
+		LOG(L_ERR, "ERROR: xl_fixup: wrong format[%s]\n", (char*)(*param));
+		return E_UNSPEC;
+	    }
+	    
+	    *param = (void*)model;
+	    return 0;
+	} else {
+	    LOG(L_ERR, "ERROR: xl_fixup: null format\n");
+	    return E_UNSPEC;
+	}
+    }
+    
+    return 0;
+}
+
+static int fixup_attr_1_xl_2(void** param, int param_no)
+{
+    if (param_no == 1) {
+	return avpid_fixup(param, 1);
+    } else  if (param_no == 2) {
+	return fixup_xl_1(param, 1);
+    }
+    return 0;
+}
+
+
+static int xlset_attr(struct sip_msg* msg, char* p1, char* format)
+{
+    avp_ident_t* avpid;
+    avp_value_t val;
+    
+    avpid = &((fparam_t*)p1)->v.avp;
+
+    if (xl_printstr(msg, (xl_elog_t*)format, &val.s.s, &val.s.len) > 0) {
+	if (add_avp(avpid->flags | AVP_VAL_STR, avpid->name, val)) {
+	    ERR("xlset_attr:Error adding new AVP\n");
+	    return -1;
+	}
+	return 1;
+    }
+    
+    ERR("xlset_attr:Error while expanding xl_format\n");
+    return -1;
+}
+
+/*
+ * get the xl function pointers and fix up the AVP parameter
+ */
+static int xlfix_attr_fixup(void** param, int param_no)
+{
+    if (get_xl_functions()) return -1;
+
+    if (param_no == 1)
+	return avpid_fixup(param, 1);
+
+    return 0;
+}
+
+/* fixes an attribute containing xl formatted string to pure string runtime */
+static int xlfix_attr(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_t* avp;
+    avp_ident_t* avpid;
+    avp_value_t val;
+    xl_elog_t* format=NULL;
+    int ret=-1;
+    
+    avpid = &((fparam_t*)p1)->v.avp;
+
+    /* search the AVP */
+    avp = search_avp(*avpid, &val, 0);
+    if (!avp) {
+	DBG("xlfix_attr: AVP does not exist\n");
+	goto error;
+    }
+    if ((avp->flags & AVP_VAL_STR) == 0) {
+	DBG("xlfix_attr: Not a string AVP\n");
+	goto error;
+    }
+
+    /* parse the xl syntax -- AVP values are always
+    zero-terminated */
+    if (xl_parse(val.s.s, &format)<0) {
+	LOG(L_ERR, "ERROR: xlfix_attr: wrong format[%s]\n", val.s.s);
+	goto error;
+    }
+
+    if (xl_printstr(msg, format, &val.s.s, &val.s.len) > 0) {
+	/* we must delete and re-add the AVP again */
+	destroy_avp(avp);
+	if (add_avp(avpid->flags | AVP_VAL_STR, avpid->name, val)) {
+	    ERR("xlfix_attr:Error adding new AVP\n");
+	    goto error;
+	}
+	/* everything went OK */
+	ret = 1;
+    }
+
+error:
+    /* free the parsed xl expression */
+    if (format) xl_free(format);
+
+    return ret;
+}
+
+
+static int request_hf_helper(struct sip_msg* msg, str* hf, avp_ident_t* ident, struct lump* anchor, struct search_state* st, int front, int reverse, int reply)
+{
+    struct lump* new_anchor;
+    static struct search_state state;
+    avp_t* avp;
+    char* s;
+    str fin_val;
+    int len, ret;
+    int_str val;
+    struct hdr_field* pos, *found = NULL;
+    
+    if (!anchor && !reply) {
+	
+	if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+	    LOG(L_ERR, "ERROR: request_hf_helper: Error while parsing message\n");
+	    return -1;
+	}
+	
+	pos = msg->headers;
+	while (pos && (pos->type != HDR_EOH_T)) {
+	    if ((hf->len == pos->name.len)
+		&& (!strncasecmp(hf->s, pos->name.s, pos->name.len))) {
+		found = pos;
+		if (front) {
+		    break;
+		}
+	    }
+	    pos = pos->next;
+	}
+	
+	if (found) {
+	    if (front) {
+		len = found->name.s - msg->buf;
+	    } else {
+		len = found->name.s + found->len - msg->buf;
+	    }
+	} else {
+	    len = msg->unparsed - msg->buf;
+	}
+	
+	new_anchor = anchor_lump(msg, len, 0, 0);
+	if (new_anchor == 0) {
+	    LOG(L_ERR, "ERROR: request_hf_helper: Can't get anchor\n");
+	    return -1;
+	}
+    } else {
+	new_anchor = anchor;
+    }
+    
+    if (!st) {
+	st = &state;
+	avp = search_avp(*ident, NULL, st);
+	ret = -1;
+    } else {
+	avp = search_next_avp(st, NULL);
+	ret = 1;
+    }
+    
+    if (avp) {
+	if (reverse && (request_hf_helper(msg, hf, ident, new_anchor, st, front, reverse, reply) == -1)) {
+	    return -1;
+	}
+	
+	get_avp_val(avp, &val);
+	if (avp->flags & AVP_VAL_STR) {
+	    fin_val = val.s;
+	} else {
+	    fin_val.s = int2str(val.n, &fin_val.len);
+	}
+	
+	len = hf->len + 2 + fin_val.len + 2;
+	s = (char*)pkg_malloc(len);
+	if (!s) {
+	    LOG(L_ERR, "ERROR: request_hf_helper: No memory left for data lump\n");
+	    return -1;
+	}
+	
+	memcpy(s, hf->s, hf->len);
+	memcpy(s + hf->len, ": ", 2 );
+	memcpy(s + hf->len+2, fin_val.s, fin_val.len );
+	memcpy(s + hf->len + 2 + fin_val.len, CRLF, CRLF_LEN);
+	
+	if (reply) {
+	    if (add_lump_rpl( msg, s, len, LUMP_RPL_HDR | LUMP_RPL_NODUP) == 0) {
+		LOG(L_ERR, "ERROR: request_hf_helper: Can't insert RPL lump\n");
+		pkg_free(s);
+		return -1;
+	    }
+	} else {
+	    if ((front && (insert_new_lump_before(new_anchor, s, len, 0) == 0))
+		|| (!front && (insert_new_lump_after(new_anchor, s, len, 0) == 0))) {
+		LOG(L_ERR, "ERROR: request_hf_helper: Can't insert lump\n");
+		pkg_free(s);
+		return -1;
+	    }
+	}
+	if (!reverse && (request_hf_helper(msg, hf, ident, new_anchor, st, front, reverse, reply) == -1)) {
+	    return -1;
+	}
+	return 1;
+    };
+    
+	 /* in case of topmost call (st==NULL) return error */
+	 /* otherwise it's OK, no more AVPs found */
+    return ret; 
+}
+
+
+static int fixup_str_1_attr_2(void** param, int param_no)
+{
+    if (param_no == 1) {
+	return fixup_var_str_12(param, 1);
+    } else if (param_no == 2) {
+	return avpid_fixup(param, 1);
+    }
+    return 0;
+}
+
+
+static int insert_req(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t ident, *avp;
+    str hf;
+    
+    if (get_str_fparam(&hf, msg, (fparam_t*)p1) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+
+    if (p2) {
+	avp = &((fparam_t*)p2)->v.avp;
+    } else {
+	ident.name.s = hf;
+	ident.flags = AVP_NAME_STR;
+	ident.index = 0;
+	avp = &ident;
+    }
+    return (request_hf_helper(msg, &hf, avp, NULL, NULL, 1, 0, 0));
+}
+
+
+static int append_req(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t ident, *avp;
+    str hf;
+
+    if (get_str_fparam(&hf, msg, (fparam_t*)p1) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+    if (p2) {
+	avp = &((fparam_t*)p2)->v.avp;
+    } else {
+	ident.name.s = hf;
+	ident.flags = AVP_NAME_STR;
+	ident.index = 0;
+	avp = &ident;
+    }
+    return (request_hf_helper(msg, &hf, avp, NULL, NULL, 0, 1, 0));
+}
+
+
+static int replace_req(struct sip_msg* msg, char* p1, char* p2)
+{
+    struct hdr_field* pos;
+    str hf;
+    
+    if (get_str_fparam(&hf, msg, (fparam_t*)p1) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+    if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
+	LOG(L_ERR, "ERROR: replace_req: Error while parsing message\n");
+	return -1;
+    }
+    
+    pos = msg->headers;
+    while (pos && (pos->type != HDR_EOH_T)) {
+	if (hf.len == pos->name.len
+	    && !strncasecmp(hf.s, pos->name.s, pos->name.len)) {
+	    if (del_lump(msg, pos->name.s - msg->buf, pos->len, 0) == 0) {
+		LOG(L_ERR,"ERROR: Can't insert del lump\n");
+		return -1;
+	    }
+	}
+	pos = pos->next;
+    }
+    return append_req(msg, p1, p2);
+}
+
+
+static int append_reply(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_ident_t ident, *avp;
+    str hf;
+    
+    if (get_str_fparam(&hf, msg, (fparam_t*)p1) < 0) {
+	ERR("Error while obtaining attribute value from '%s'\n", ((fparam_t*)p1)->orig);
+	return -1;
+    }
+    
+    if (p2) {
+	avp = &((fparam_t*)p2)->v.avp;
+    } else {
+	ident.name.s = hf;
+	ident.flags = AVP_NAME_STR;
+	ident.index = 0;
+	avp = &ident;
+    }
+    return (request_hf_helper(msg, &hf, avp, NULL, NULL, 0, 1, 1));
+}
+
+
+static int set_destination(struct sip_msg* msg, str* dest)
+{
+    name_addr_t nameaddr;
+    
+    if (!parse_nameaddr(dest, &nameaddr)) {
+	return set_dst_uri(msg, &nameaddr.uri);
+    } else {
+	     /* it is just URI, pass it through */
+	return set_dst_uri(msg, dest);
+    }
+}
+
+
+static int attr_destination(struct sip_msg* msg, char* p1, char* p2)
+{
+    avp_t* avp;
+    avp_value_t val;
+    
+    if ((avp = search_avp(((fparam_t*)p1)->v.avp, &val, NULL))) {
+	if (avp->flags & AVP_VAL_STR) {
+	    if (set_destination(msg, &val.s)) {
+		LOG(L_ERR, "ERROR: avp_destination: Can't set dst uri\n");
+		return -1;
+	    };
+	    return 1;
+	} else {
+	    ERR("avp_destination:AVP has numeric value\n");
+	    return -1;
+	}
+    }
+    return -1;
+}
+
+
+static int xlset_destination(struct sip_msg* msg, char* format, char* p2)
+{
+    str val;
+    
+    if (xl_printstr(msg, (xl_elog_t*) format, &val.s, &val.len) > 0) {
+	DBG("Setting dest to: '%.*s'\n", val.len, val.s);
+	if (set_destination(msg, &val) == 0) {
+	    return 1;
+	}
+    }
+    
+    return -1;
+}
+
+
+static int attr_hdr_body2attrs(struct sip_msg* m, char* header_, char* prefix_)
+{
+    char name_buf[50];
+    str *prefix = (str*) prefix_;
+    struct hdr_name *header = (void*) header_;
+    struct hdr_field *hf;
+    str s, name, val;
+    int_str name2, val2;
+    int val_type, arr;
+    if (header->kind == HDR_STR) {
+	if (parse_headers(m, HDR_EOH_F, 0) == -1) {
+	    LOG(L_ERR, "ERROR: attr_hdr_body2attrs: Error while parsing message\n");
+	    return -1;
+	}
+	
+	for (hf=m->headers; hf; hf=hf->next) {
+	    if ( (header->name.s.len == hf->name.len)
+		 && (!strncasecmp(header->name.s.s, hf->name.s, hf->name.len)) ) {
+		break;
+	    }
+	}
+    }
+    else {
+	if (parse_headers(m, header->name.n, 0) == -1) {
+	    LOG(L_ERR, "ERROR: attr_hdr_body2attrs: Error while parsing message\n");
+	    return -1;
+	}
+	switch (header->name.n) {
+		 //	HDR_xxx:
+	default:
+	    hf = NULL;
+	    break;
+	}
+    }
+    if (!hf || !hf->body.len)
+	return 1;
+    
+	 // parse body of hf
+    s = hf->body;
+    name_buf[0] = '\0';
+    while (s.len) {
+	trim_leading(&s);
+	name.s = s.s;
+	while ( s.len &&
+		( (s.s[0] >= 'a' && s.s[0] <= 'z') ||
+		  (s.s[0] >= 'A' && s.s[0] <= 'Z') ||
+		  (s.s[0] >= '0' && s.s[0] <= '9') ||
+		  s.s[0] == '_' || s.s[0] == '-'
+		  ) ) {
+	    s.s++;
+	    s.len--;
+	}
+	if (s.s == name.s)
+	    break;
+	name.len = s.s - name.s;
+	trim_leading(&s);
+	if (!s.len)
+	    break;
+	if (s.s[0] == '=') {
+	    s.s++;
+	    s.len--;
+	    arr = -1;
+	    
+	    while (s.len) {
+		trim_leading(&s);
+		val_type = 0;
+		if (!s.len)
+		    break;
+		if (s.s[0] == '"') {
+		    s.s++;
+		    s.len--;
+		    val.s = s.s;
+		    
+		    s.s = q_memchr(s.s, '\"', s.len);
+		    if (!s.s)
+			break;
+		    val.len = s.s - val.s;
+		    val_type = AVP_VAL_STR;
+		    s.s++;
+		    s.len -= s.s - val.s;
+		}
+		else {
+		    int r;
+		    val.s = s.s;
+		    if (s.s[0] == '+' || s.s[0] == '-') {
+			s.s++;
+			s.len--;
+		    }
+		    val2.n = 0; r = 0;
+		    while (s.len) {
+			if (s.s[0] == header->field_delimiter || (header->array_delimiter && header->array_delimiter == s.s[0]))
+			    goto token_end;
+			switch (s.s[0]) {
+			case ' ':
+			case '\t':
+			case '\n':
+			case '\r':
+			    goto token_end;
+			}
+			if (!val_type && s.s[0] >= '0' && s.s[0]<= '9') {
+			    r++;
+			    val2.n *= 10;
+			    val2.n += s.s[0] - '0';
+				 // overflow detection ???
+			}
+			else {
+			    val_type = AVP_VAL_STR;
+			}
+			s.s++;
+			s.len--;
+		    }
+		token_end:
+		    if (r == 0) val_type = AVP_VAL_STR;
+		    if (!val_type && val.s[0] == '-') {
+			val2.n = -val2.n;
+		    }
+		    val.len = s.s - val.s;
+		}
+		trim_leading(&s);
+		if (arr >= 0 || (s.len && header->array_delimiter && header->array_delimiter == s.s[0])) {
+		    arr++;
+		    if (arr == 100)
+			LOG(L_ERR, "ERROR: avp index out of limit\n");
+		}
+		if (val.len && arr < 100) {
+		    if (prefix != NULL || arr >= 0) {
+			if ((prefix?prefix->len:0)+name.len+1+((arr>=0)?3/*#99*/:0) > sizeof(name_buf)) {
+			    if (arr <= 0)
+				LOG(L_ERR, "ERROR: avp name too long\n");
+			    goto cont;
+			}
+			name2.s.len = 0;
+			name2.s.s = name_buf;
+			if (prefix != NULL) {
+			    if (name_buf[0] == '\0') {
+				memcpy(&name_buf[0], prefix->s, prefix->len);
+			    }
+			    name2.s.len += prefix->len;
+			}
+			if (arr <= 0) {
+			    memcpy(&name_buf[name2.s.len], name.s, name.len);
+			}
+			name2.s.len += name.len;
+			if (arr >= 0) {
+			    name_buf[name2.s.len] = '#';
+			    name2.s.len++;
+			    if (arr >= 10) {
+				name_buf[name2.s.len] = '0'+ (arr / 10);
+				name2.s.len++;
+			    }
+			    name_buf[name2.s.len] = '0'+ (arr % 10);
+			    name2.s.len++;
+			}
+		    }
+		    else {
+			name2.s.s = name.s;
+			name2.s.len = name.len;
+		    }
+		    if ( ((val_type & AVP_VAL_STR) && (header->val_types & VAL_TYPE_STR)) ||
+			 ((val_type & AVP_VAL_STR) == 0 && (header->val_types & VAL_TYPE_INT))  ) {
+			if (val_type) {
+			    val2.s.s = val.s;
+			    val2.s.len = val.len;
+			    DBG("DEBUG: attr_hdr_body2attrs: adding avp '%.*s', sval: '%.*s'\n", name2.s.len, (char*) name2.s.s, val.len, val.s);
+			} else {
+			    DBG("DEBUG: attr_hdr_body2attrs: adding avp '%.*s', ival: '%d'\n", name2.s.len, (char*) name2.s.s, val2.n);
+			}
+			if ( add_avp(AVP_NAME_STR | val_type, name2, val2)!=0) {
+			    LOG(L_ERR, "ERROR: attr_hdr_body2attrs: add_avp failed\n");
+			    return 1;
+			}
+		    }
+		}
+	    cont:
+		if (s.len && header->array_delimiter && header->array_delimiter == s.s[0]) {
+		    s.s++;
+		    s.len--;
+		}
+		else {
+		    break;
+		}
+	    };
+	}
+	if (s.len && s.s[0] == header->field_delimiter) {
+	    s.s++;
+	    s.len--;
+	}
+	else {
+	    break;
+	}
+    }
+    return 1;
+}
+
+
+static int attr_hdr_body2attrs2(struct sip_msg* msg, char* header_, char* prefix_) 
+{
+    return attr_hdr_body2attrs(msg, header_, prefix_);
+}
+
+
+static int attr_hdr_body2attrs_fixup(void** param, int param_no) {
+    char *c, *params;
+    struct hdr_name *h;
+    int n;
+    str *s;
+    if (param_no == 1) {
+	c = *param;
+	if (*c == '#') {
+	    c++;
+	    n = strtol(c, &params, 10);
+	    switch (*params) {
+	    case PARAM_DELIM:
+		break;
+	    case 0:
+		params = 0;
+		break;
+	    default:
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: bad AVP value\n");
+		return E_CFG;
+	    }
+	    switch (n) {
+		//				case HDR_xxx:
+		//				case HDR_xxx:
+		//					break;
+	    default:
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: header name is not valid and supported HDR_xxx id '%s' resolved as %d\n", c, n);
+		return E_CFG;
+	    }
+	    h = pkg_malloc(sizeof(*h));
+	    if (!h) {
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: out of memory\n");
+		return E_OUT_OF_MEM;
+	    }
+	    
+	    h->kind = HDR_ID;
+	    h->name.n = n;
+	    pkg_free(*param);
+	    
+	}
+	else {
+	    params = strchr(c, PARAM_DELIM);
+	    if (params)
+		n = params-c;
+	    else
+		n = strlen(c);
+	    if (n == 0) {
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: header name is empty\n");
+		return E_CFG;
+	    }
+	    h = pkg_malloc(sizeof(*h)+n+1);
+	    if (!h) {
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: out of memory\n");
+		return E_OUT_OF_MEM;
+	    }
+	    h->kind = HDR_STR;
+	    h->name.s.len = n;
+	    h->name.s.s = (char *) h + sizeof(*h);
+	    memcpy(h->name.s.s, c, n+1);
+	}
+	if (params) {
+	    h->val_types = 0;
+	    while (*params) {
+		switch (*params) {
+		case 'i':
+		case 'I':
+		    h->val_types = VAL_TYPE_INT;
+		    break;
+		case 's':
+		case 'S':
+		    h->val_types = VAL_TYPE_STR;
+		    break;
+		case PARAM_DELIM:
+		    break;
+		default:
+		    LOG(L_ERR, "attr_hdr_body2attrs_fixup: bad field param modifier near '%s'\n", params);
+		    return E_CFG;
+		}
+		params++;
+	    }
+	    if (!h->val_types) {
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: no field param modifier specified\n");
+		return E_CFG;
+	    }
+	}
+	else {
+	    h->val_types = VAL_TYPE_INT|VAL_TYPE_STR;
+	}
+	pkg_free(*param);
+	h->field_delimiter = ',';
+	h->array_delimiter = '\0';
+	
+	*param = h;
+    }
+    else if (param_no == 2) {
+	n = strlen(*param);
+	if (n == 0) {
+	    s = NULL;
+	}
+	else {
+	    s = pkg_malloc(sizeof(*s)+n+1);
+	    if (!s) {
+		LOG(L_ERR, "attr_hdr_body2attrs_fixup: out of memory\n");
+		return E_OUT_OF_MEM;
+	    }
+	    s->len = n;
+	    s->s = (char *) s + sizeof(*s);
+	    memcpy(s->s, *param, n+1);
+	}
+	pkg_free(*param);
+	*param = s;
+    }
+    return 0;
+}
+
+static int attr_hdr_body2attrs2_fixup(void** param, int param_no) 
+{
+    struct hdr_name *h;
+    int res = attr_hdr_body2attrs_fixup(param, param_no);
+    if (res == 0 && param_no == 1) {
+	h = *param;
+	h->field_delimiter = ';';
+	h->array_delimiter = ',';
+    }
+    return res;
+}
+
+
+
+static int avpgroup_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 && strlen(s) != 2)) {
+	    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;
+
+	case 0x4644: /* $fd */
+	case 0x6664:
+	case 0x4664:
+	case 0x6644:
+	    flags = AVP_TRACK_FROM | AVP_CLASS_DOMAIN;
+	    break;
+
+	case 0x5444: /* $td */
+	case 0x7464:
+	case 0x5464:
+	case 0x7444:
+	    flags = AVP_TRACK_TO | AVP_CLASS_DOMAIN;
+	    break;
+
+	case 0x6700: /* $td */
+	case 0x4700:
+	    flags = AVP_CLASS_GLOBAL;
+	    break;
+	    
+	default:
+	    ERR("Invalid parameter value: '%s'\n", s);
+	    return -1;
+	}
+	
+	pkg_free(*param);
+	*param = (void*)flags;
+	return 1;
+    }
+    return 0;
+}
+
+
+
+static int select_attr_fixup(str* res, select_t* s, struct sip_msg* msg)
+{
+	avp_ident_t *avp_ident;
+
+#define SEL_PARAM_IDX	1
+
+	if (! msg) { /* fixup call */
+		str attr_name;
+		
+		if (s->params[SEL_PARAM_IDX].type != SEL_PARAM_STR) {
+			ERR("attribute name expected.\n");
+			return -1;
+		}
+
+		attr_name = s->params[SEL_PARAM_IDX].v.s;
+		DEBUG("fix up for attribute '%.*s'\n", STR_FMT(&attr_name));
+
+		if (! (avp_ident = pkg_malloc(sizeof(avp_ident_t)))) {
+			ERR("out of mem; requested: %zd.\n", sizeof(avp_ident_t));
+			return -1;
+		}
+		memset(avp_ident, 0, sizeof(avp_ident_t));
+
+		/* skip leading `$' */
+		if ((1 < attr_name.len) && (attr_name.s[0] == '$')) {
+			attr_name.len --;
+			attr_name.s ++;
+		}
+		if (parse_avp_ident(&attr_name, avp_ident) < 0) {
+			ERR("failed to parse attribute name: `%.*s'.\n", STR_FMT(&attr_name));
+			pkg_free(avp_ident);
+		}
+		s->params[SEL_PARAM_IDX].v.p = avp_ident;
+		s->params[SEL_PARAM_IDX].type = SEL_PARAM_PTR;
+	} else { /* run time call */
+		avp_t *ret;
+		avp_value_t val;
+
+#ifdef EXTRA_DEBUG
+		assert(s->params[SEL_PARAM_IDX].type == SEL_PARAM_PTR);
+#endif
+		avp_ident = s->params[SEL_PARAM_IDX].v.p;
+		ret = search_first_avp(avp_ident->flags, avp_ident->name, &val, NULL);
+		if (ret && ret->flags & AVP_VAL_STR)
+			*res = val.s;
+	}
+
+	return 0;
+
+#undef SEL_PARAM_IDX
+}
+
+SELECT_F(select_any_nameaddr)
+ABSTRACT_F(select_attr);
+
+select_row_t sel_declaration[] = {
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("avp"), select_attr, SEL_PARAM_EXPECTED},
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("attr"), select_attr, SEL_PARAM_EXPECTED},
+	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("attribute"), select_attr, SEL_PARAM_EXPECTED},
+	{ select_attr, SEL_PARAM_STR, STR_NULL, select_attr_fixup, FIXUP_CALL | CONSUME_NEXT_STR},
+
+	{ select_attr_fixup, SEL_PARAM_STR, STR_STATIC_INIT("nameaddr"), select_any_nameaddr, NESTED},
+
+	{ NULL, SEL_PARAM_INT, STR_NULL, NULL, 0}
+};
+
+static int mod_init()
+{
+	DBG("%s - initializing\n", exports.name);
+	return register_select_table(sel_declaration);
+}

+ 543 - 0
modules_s/avp/avp.xml

@@ -0,0 +1,543 @@
+<?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 id="module.avp"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>avp</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>avp</refname>
+    <refpurpose>Manipulation of Attributes</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>avp</command> module provides various functions for
+      the manipulation of attributes.
+    </para>
+    <para>
+      The name is a left-over from earlier versions of SER where attributes
+      were called ‘attribute-value pairs’ or AVPs for short.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.append_attr_hf">
+      <title>
+        <function>append_attr_hf</function>
+        (<symbol>hf</symbol>,
+         <optional><symbol>attribute</symbol></optional>)
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>append_attr_hf()</function> function appends a
+        new header field value to the header field named by the argument
+        <symbol>hf</symbol>. The new value will be populated with the
+        content of the attribute named by the argument
+        <symbol>attribute</symbol>. If the argument is missing, the content
+        is taken from the attribute with same name as the header field.
+      </para>
+     </refsect2>
+
+    <refsect2 xml:id="function.attr2uri">
+      <title>
+        <function>attr2uri</function>
+        (<symbol>attribute</symbol>,
+        <serdoc:optional><symbol>part</symbol></serdoc:optional>)
+       </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>attr2uri()</function> function sets the Request-URI
+        to the content of the attribute named by the argument
+        <symbol>attribute</symbol>. If the argument <symbol>part</symbol>
+        is present, only the part of the Request-URI identified by it
+        will be changed. This mimiks the behavior of many of the core
+        functions that allow manipulation of the Request-URI.
+      </para>
+      <para>
+        The following values are allowed for the <symbol>part</symbol>
+        argument:
+      </para>
+      <variablelist>
+        <varlistentry>
+          <term><literal>domain</literal>, <literal>host</literal></term>
+          <listitem>
+            replace the host part of the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>domainport</literal>, <literal>hostport</literal>
+          </term>
+          <listitem>
+            replace the host and port parts of the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>port</literal></term>
+          <listitem>
+            replace the port part of the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>prefix</literal></term>
+          <listitem>
+            prefix the content of the attribute to the username part of
+            the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>strip</literal></term>
+          <listitem>
+            the attribute contains an integer specifying the number of
+            characters to be stripped off the beginning of the username
+            part of the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>strip_tail</literal></term>
+          <listitem>
+            the attribute contains an integer specifyin the number of
+            characters to be stripped off the end of the username part of
+            the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>uri</literal></term>
+          <listitem>
+            replace the entire Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><literal>user</literal>, <literal>username</literal></term>
+          <listitem>
+            replace the username part of the Request-URI,
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term>
+            <literal>userpass</literal>,
+            <literal>usernamepassword</literal>
+          </term>
+          <listitem>
+            replace the username and password parts of the Request-URI.
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 xml:id="function.attr_destination">
+      <title>
+        <function>attr_destination</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>attr_destination()</function> function sets the
+        destination URI to the content of the attribute named by the
+        argument <symbol>attribute</symbol>.
+      </para>
+      <para>
+        If the destination URI is set, the request will be send to the
+        address determined from that URI instead of the Request-URI. It
+        will carried over into branches together with the Request-URI when a
+        call to <serdoc:func>append_branch</serdoc:func> is made.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.attr_equals">
+      <title>
+        <function>attr_equals</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>attr_equals()</function> function checks whether
+        the attribute identified by the argument <symbol>attribute</symbol>
+        exists and its value is identical the the value given by the
+        argument <symbol>value</symbol>. If so, it returns
+        <literal>true</literal> and <literal>false</literal> otherwise.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.attr_equals_xl">
+      <title>
+        <function>attr_equals_xl</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>attr_equals()</function> function checks whether
+        the attribute identified by the argument <symbol>attribute</symbol>
+        exists and its value is identical to the xl string given by the
+        argument <symbol>value</symbol>.  If so, it returns
+        <literal>true</literal> and <literal>false</literal> otherwise.
+      </para>
+      <para>
+        For more information on xl strings, see
+        <serdoc:module>xlog</serdoc:module>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.attr_exists">
+      <title>
+        <function>attr_exists</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>attr_exists()</function> function checks whether
+        an attribute by the name given by the name of
+        <symbol>attribute</symbol> exists and returns
+        <literal>true</literal> in this case or <literal>false</literal>
+        otherwise.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.attr_to_reply">
+      <title>
+        <function>attr_to_reply</function>
+        (<symbol>hf</symbol>,
+        <serdoc:optional><symbol>attribute</symbol></serdoc:optional>)
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>attr_to_reply()</function> function adds a new
+        header fields value to the final response that will eventually
+        be sent upstream. The name of the header field is given by the
+        argument <symbol>hf</symbol>. The content will be taken from an
+        attribute. If the argument <symbol>attribute</symbol> is present,
+        it contains the name of the attribute. Otherwise, the header field
+        name from the argument <symbol>hf</symbol> will be taken as the
+        name.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.del_attr">
+      <title>
+        <function>del_attr</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>del_attr</function> function deletes the attribute
+        identified by the argument <symbol>attribute</symbol>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.del_attrs">
+      <title>
+        <function>del_attrs</function>
+        (<symbol>group</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>del_attrs()</function> function deletes all
+        attributes from the group of attributes identified by the
+        attribute prefix given through the argument <symbol>group</symbol>.
+        For example, to delete all attributes of the From user, call
+      </para>
+      <programlisting>
+        del_attrs("$fu");
+      </programlisting>
+    </refsect2>
+
+    <refsect2 xml:id="function.dump_attrs">
+      <title>
+        <function>dump_attrs</function>
+        (<serdoc:optional><symbol>group</symbol></serdoc:optional>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>dump_attrs()</function> dumps a list of the existing
+        attributes and their values to the system log. If the
+        <symbol>group</symbol> argument is present, it contains an
+        attribute namespace prefix and limits the output to the attrbutes
+        in that namespace.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.flags2attr">
+      <title>
+        <function>flags2attr</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>flags2attr()</function> function stores the current
+        state of all the flags in compact form in the attribute identified
+        by the argument <symbol>attribute</symbol>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.insert_attr_hf">
+      <title>
+        <function>insert_attr_hf</function>
+        (<symbol>hf</symbol>,
+         <serdoc:optional><symbol>attribute</symbol></serdoc:optional>)
+       </title>
+       <para>
+         Allowed in request and failure processing.
+       </para>
+       <para>
+         The <function>insert_attr_hf()</function> function inserts a
+         header field value to the header field named by the argument
+         <symbol>hf</symbol> with the value of the attribute identified
+         by the argument <symbol>attribute</symbol>. If there are already
+         values for that header field, the new value will be prepended
+         before any existing value.
+       </para>
+       <para>
+         If the optional argument <symbol>attribute</symbol> is missing,
+         the name of the attribute is taken from the name of the header
+         field in argument <symbol>hf</symbol>.
+       </para>
+     </refsect2>
+
+    <refsect2 xml:id="function.print_attr">
+      <title>
+        <function>print_attr</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>print_attr()</function> function prints the content
+        of the attribute identified by the argument
+        <symbol>attribute</symbol> to the system log.
+      </para>
+      <para>
+        For a more flexible way of printing messages to the system log
+        that allows to print extra information together with the attribute
+        value, see the <serdoc:module>xlog</serdoc:module> module.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.replace_attr_hf">
+      <title>
+        <function>replace_attr_hf</function>
+        (<symbol>hf</symbol>,
+        <serdoc:optional><symbol>attribute</symbol></serdoc:optional>)
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>replace_attr_hf()</function> function removes all
+        header field values of the header field named by the argument
+        <symbol>hf</symbol> and adds a new header field value with the
+        content of the attribute identified by the argument
+        <symbol>attribute</symbol>. If the argument
+        <symbol>attribute</symbol> is missing, the content of the new
+        header field value will be taken from an argument with the same
+        name as the header field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.set_attr">
+      <title>
+        <function>set_attr</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>set_attr()</function> function is a synonym for
+        the <serdoc:func>set_sattr</serdoc:func> function and sets the
+        attribute identified by the argument <symbol>attribute</symbol>
+        to the string contained in the argument <symbol>value</symbol>.
+      </para>
+      <para>
+        Like <serdoc:func>set_sattr</serdoc:func>, it is identical to
+        assinging an string using the assignment operator and is
+        deprecated.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.set_iattr">
+      <title>
+        <function>set_iattr</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>set_iattr()</function> function sets the attribute
+        identified by the argument <symbol>attribute</symbol> to the
+        integer value contained in the argument <symbol>value</symbol>.
+        If the attribute does not yet exist, it is created.
+      </para>
+      <para>
+        This function is identical to assinging an integer using the
+        assignment operator and
+        is only kept to retain compatibility with earlier versions of SER.
+        It therefore is deprecated and may be removed in the future.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.set_sattr">
+      <title>
+        <function>set_sattr</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>set_sattr()</function> function sets the attribute
+        identified by the argument <symbol>attribute</symbol> to the
+        string contained in the argument <symbol>value</symbol>. If the
+        attribute does not yet exist, it is created.
+      </para>
+      <para>
+        This function is identical to assinging an string using the
+        assignment operator and
+        is only kept to retain compatibility with earlier versions of SER.
+        It therefore is deprecated and may be removed in the future.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.subst_attr">
+      <title>
+        <function>subst_attr</function>
+        (<symbol>attribute</symbol>, <symbol>subst</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>subst_attr()</function> function manipulates the
+        content of the attribute identified by the argument
+        <symbol>attribute</symbol> using a subst expression.
+      </para>
+      <para>
+        A subst expression is identical to the expression used by the
+        <serdoc:bin>sed</serdoc:bin> program’s s command.
+      </para>
+      <!-- XXX We should either explain them in detail here, or in textops
+               or in the admin guide. In any case, there needs to be a
+               reference to the explanation.
+        -->
+    </refsect2>
+
+    <refsect2 xml:id="function.xlfix_attr">
+      <title>
+        <function>xlfix_attr</function>
+        (<symbol>attribute</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>xlfix_attr()</function> function evaluates the xl
+        string contained in the attributed identified by the argument
+        <symbol>attribute</symbol> and stores the result of the evaluation
+        in the same attribute.
+      </para>
+      <para>
+        For more information on xl strings, see
+        <serdoc:module>xlog</serdoc:module>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.xlset_attr">
+      <title>
+        <function>xlset_attr</function>
+        (<symbol>attribute</symbol>, <symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>xlset_attr()</function> function sets the attribute
+        identified by the argument <symbol>attribute</symbol> to the
+        evaluated xl string given by the argument <symbol>value</symbol>.
+      </para>
+      <para>
+        For more information on xl strings, see
+        <serdoc:module>xlog</serdoc:module>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.xlset_destination">
+      <title>
+        <function>xlset_destination</function>
+        (<symbol>value</symbol>)
+      </title>
+      <para>
+        Allowed in request processing only.
+      </para>
+      <para>
+        The <function>xlset_destination()</function> function sets the
+        destination URI to the evaluated content of the xl string given
+        by the argument <symbol>value</symbol>.
+      </para>
+      <para>
+        For details on xl strings, see <serdoc:module>xlog</serdoc:module>.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.avp.xlbuf_size">
+      <title><parameter>xlbuf_size</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>integer</serdoc:paramtype>
+        <serdoc:paramdefault>256</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>xlbuf_size</parameter> parameter defines the size
+        of the buffer that used for evaluation of xl strings. The evaluated
+        string cannot be larger than the size given by this parameter.
+      </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>avp_db</serdoc:module></member>
+      <member><serdoc:module>avp_radius</serdoc:module></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+
+<!-- vim:sw=2 sta et sts=2 ai tw=76
+  -->

+ 29 - 0
modules_s/avp/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = avp
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 59 - 0
modules_s/avp/doc/avp.xml

@@ -0,0 +1,59 @@
+<?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" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<authorgroup>
+	    <author>
+		<firstname>Jiri</firstname>
+		<surname>Kuthan</surname>
+		<affiliation><orgname>FhG Fokus</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+		<author>
+		<firstname>Michal</firstname>
+		<surname>Matyska</surname>
+		<affiliation><orgname>iptel</orgname></affiliation>
+		<email>[email protected]</email>
+		</author>
+	</authorgroup>
+	<copyright>
+	    <year>2004</year>
+	    <year>2005</year>
+	    <holder>FhG FOKUS</holder>
+		<year>2006</year>
+		<holder>iptelorg GmbH</holder>
+	</copyright>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Avp Module</title>
+
+    <section>
+	<title>Overview</title>
+	<para>
+	    This module contains several functions that can be used to
+	    manipulate the contents of AVPs (Attribute-Value pairs). The AVPs
+	    are variables attached to the SIP message being processed. Each
+	    variable has its name and value. AVPs can be used to store
+	    arbitrary data or as a means of inter-module comminication.
+	</para>
+	<para>
+	    You may also want to check the avpops module which is more flexible
+	    and contains more functions. In future SER releases the avp module
+	    will be probably deprecated in favor of avpops module.
+	</para>
+    </section>
+
+    <xi:include href="functions.xml"/>
+    <xi:include href="params.xml"/>
+
+</section>
+
+

+ 644 - 0
modules_s/avp/doc/functions.xml

@@ -0,0 +1,644 @@
+<?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.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Functions</title>
+
+    <section id="set_iattr">
+	<title><function>set_iattr(attribute,value)</function></title>
+	<para>
+	    Create an AVP of type integer.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para>
+		    <emphasis>attribute</emphasis> - The name of the AVP to
+		    be created.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para>
+		    <emphasis>value</emphasis> - Integer value of the AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	
+	<example>
+	    <title><function>set_iattr</function> usage</title>
+	    <programlisting>
+...
+set_iattr("fr_inv_timer", "60")
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="flags2attr">
+	<title><function>flags2attr("$avp")</function></title>
+	<para>
+	    Store the current state of SER flags into the sepcified avp.
+	</para>
+	<example>
+	    <title><function>flags2attr</function> usage</title>
+	    <programlisting>
+...
+flags2attr("$msg_flags")
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+    <section id="set_sattr">
+	<title><function>set_sattr(attribute,value)</function></title>
+	<para>
+	    Create an AVP of type string.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>attribute</emphasis> - The name of the
+		    AVP to be created.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>value</emphasis> - String value of the AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	
+	<example>
+	    <title><function>set_sattr</function> usage</title>
+	    <programlisting>
+...
+set_sattr("called_number", "1234")
+...
+	    </programlisting>
+	</example>
+    </section>
+
+    <section id="print_aattr">
+	<title><function>print_attr($attribute)</function></title>
+	<para>
+	    Print the value of an AVP to syslog.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+    
+    <section id="attr2uri">
+	<title><function>attr2uri($attribute[,uri-part])</function></title>
+	<para>
+		Rewrite the whole Request-URI of the message being processed with
+		the value of an AVP, or if an <varname>uri-part</varname> is specified,
+		rewrite only that specific part.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>$attribute</emphasis> - The name of the AVP.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>uri-part</emphasis> - The name of the part of the uri
+			 that will be rewritten.  The supported values are: "prefix",
+			 "uri", "username", "user",  "usernamepassword", "userpass",
+			 "domain", "host", "domainport", "hostport", "port", "strip",
+			 "strip_tail". "prefix" will add the AVP as a prefix to the 
+			 username (equivalent to prefix("string")), "strip" and 
+			 "strip_tail" expect a number in the AVP and they will remove the
+			 specified number of characters from the beginning, respective the
+			  end of the username part of the uri. The rest of the uri-part
+			  values names are self-explaining.
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+
+	<section id="attr_exists">
+	<title><function>attr_exists(attribute)</function></title>
+	<para>
+	    Test for the existence of AVP with given name. The function returns
+	    1 if given AVP exists and -1 if not.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<example>
+	    <title><function>attr_exists</function> usage</title>
+	    <programlisting>
+...
+if (attr_exists("saved_ruri")) {
+  attr2uri("saved_uri");
+} else {
+  rewriteuri("sip:[email protected]");
+};
+...
+		</programlisting>
+	</example>
+    </section>
+
+    
+    <section id="attr_equals">
+	<title><function>attr_equals(attribute, value)</function></title>
+	<para>
+	    Test whether an AVP with given name and value exists. The function
+	    returns 1 if the AVP with given name and value exists and -1 if
+	    not. The value of the AVP is compared string-wise. The comparison
+	    is case sensitive.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>value</emphasis> - The AVP value to look for.
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+    
+    <section id="attr_equals_xl">
+	<title><function>attr_equals_xl(attribute, xl_format)</function></title>
+	<para>
+	    Test whether an AVP with given name and value exists. The function
+		returns 1 if the AVP with given name and value exists and -1 if
+	    not. The value of the AVP is compared string-wise to the result of
+		xlog formatting call. The comparison is case sensitive.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>xl_format</emphasis> - The xlog formatting string,
+		    result of which is looked for in AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<para>Note: You must ensure, that the xlog module is loaded to be able to use this function.</para>
+	<example>
+	    <title><function>attr_equals_xl</function> usage</title>
+	    <programlisting>
+...
+if (attr_equals_xl("my_avp", "%ct")) {
+  # my_avp has value equal to current Contact header field
+} else {
+  # my_avp was different
+}
+...
+	    </programlisting>
+	</example>
+    </section>
+    
+	<section id="dump_attrs">
+	<title><function>dump_attrs()</function></title>
+	<para>
+		Dumps all AVPs in user lists to the debug output (with level INFO).
+	</para>
+	<para>The function does not require any parameters.</para>
+    </section>
+    
+	<section id="xlset_attr">
+	<title><function>xlset_attr($attribute, xl_format)</function></title>
+	<para>
+		Creates new AVP identified by <emphasis>attribute</emphasis> and assigns the result string of xlog formatting rules as its value.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>$attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>xl_format</emphasis> - String used for xlog formatting.
+		    For detailed info see documentation of xlog module.
+		</para>
+	    </listitem>
+	</itemizedlist>
+	<para>Note: You must ensure, that the xlog module is loaded to be able to use this function.</para>
+    </section>
+
+	<section id="xlfix_attr">
+	<title><function>xlfix_attr($attribute)</function></title>
+	<para>
+		Fixes an xl formatted <emphasis>attribute</emphasis>  value to pure
+		string.
+	</para>
+	<para>Meaning of the parameter is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>$attribute</emphasis> - The name of the
+		    AVP.
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+    
+    
+	<section id="insert_attr_hf_1">
+	<title><function>insert_attr_hf(name)</function></title>
+	<para>
+		Inserts new header into the request, which is beeing forwarded. The AVP name is the name of the header field.
+		If you need to insert header with name which differs from the AVP name use <function>insert_attr_hf(header_name, $avp_name)</function> instead.
+	</para>
+	<para>
+		Inserting means putting the header to the beginning of the request, before any others.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>name</emphasis>
+		- The name of the header field which is inserted into forwarded
+		request as well as name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="insert_attr_hf_2">
+	<title><function>insert_attr_hf(header_name, $avp_name)</function></title>
+	<para>
+		Inserts new header into the request, which is beeing forwarded. 
+	</para>
+	<para>
+		Inserting means putting the header to the beginning of the request, before any others.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>header_name</emphasis>
+		- The name of the header field which is inserted into forwarded
+		request.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- The name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	<example>
+	    <title><function>insert_attr_hf</function> usage</title>
+	    <programlisting>
+...
+set_sattr("my_route","&lt;sip:user@host:port;lr&gt;");
+insert_attr_hf("Route", "$my_route");
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="append_attr_hf_1">
+	<title><function>append_attr_hf(name)</function></title>
+	<para>
+		Appends new header into the request, which is beeing forwarded. The AVP name is the name of the header field.
+		If you need to append header with name which differs from the AVP name use <function>append_attr_hf(header_name, $avp_name)</function> instead.
+	</para>
+	<para>
+		Appending means putting the header to the end of the request, after any others.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>name</emphasis>
+		- The name of the header field which is appended into forwarded
+		request as well as name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="append_attr_hf_2">
+	<title><function>append_attr_hf(header_name, $avp_name)</function></title>
+	<para>
+		Appends new header into the request, which is beeing forwarded. 
+	</para>
+	<para>
+		Appending means putting the header to the end of the request, after any others.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>header_name</emphasis>
+		- The name of the header field which is appended into forwarded
+		request.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- The name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="replace_attr_hf_1">
+	<title><function>replace_attr_hf(name)</function></title>
+	<para>
+		Replaces header in the request, which is beeing forwarded. The AVP name is the same as the name of the header field.
+		If you need to replace header with name which differs from the AVP name use <function>replace_attr_hf(header_name, $avp_name)</function> instead.
+	</para>
+	<para>
+		Replacing means removing all the headers with specified name and appending new one at the end, with the value from AVP.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>name</emphasis>
+		- The name of the header field which is replaced in forwarded
+		request as well as name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="replace_attr_hf_2">
+	<title><function>replace_attr_hf(header_name, $avp_name)</function></title>
+	<para>
+		Replaces header in the request, which is beeing forwarded. 
+	</para>
+	<para>
+		Replacing means removing all the headers with specified name and appending new one at the end, with the value from AVP.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>header_name</emphasis>
+		- The name of the header field which is replaced in forwarded
+		request.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- The name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="attr_to_reply_1">
+	<title><function>attr_to_reply(name)</function></title>
+	<para>
+		Appends new header into the reply at the request time processing. The AVP name is the name of the header field.
+		If you need to append header with name which differs from the AVP name use <function>attr_to_reply(header_name, $avp_name)</function> instead.
+	</para>
+	<para>
+		If you need to append headers during reply processing you can use insert_attr_hf and append_attr_hf. This function stores data and waits for the reply being created.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>name</emphasis>
+		- The name of the header field which is appended into 
+		reply as well as name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	</section>
+	
+	<section id="attr_to_reply_2">
+	<title><function>attr_to_reply(header_name, $avp_name)</function></title>
+	<para>
+		Appends new header into the reply at the request time processing. 
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+        <para><emphasis>header_name</emphasis>
+		- The name of the header field which is appended into
+		reply.
+		</para>
+	    </listitem>
+	    <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- The name of AVP which's value is put as the header field
+		value.
+		</para>
+        </listitem>
+    </itemizedlist>
+	<example>
+	    <title><function>attr_to_reply</function> usage</title>
+	    <programlisting>
+...
+xlset_attr("$my_route","&lt;sip:%Hf:5080;lr&gt;";
+attr_to_reply("P-Hint-Route", "my_route");
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="attr_destination">
+	<title><function>attr_destination($avp_name)</function></title>
+	<para>
+		Sets the destination of the forwarded request to the value of AVP, 
+		which must be either a SIP URI or a string in nameaddr format
+		(e.g. "Foo Bar" &lt;sip:uri&gt;).
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- The name of AVP which's value is used for further request forwarding.
+		</para>
+        </listitem>
+    </itemizedlist>
+	<example>
+	    <title><function>attr_destination</function> usage</title>
+	    <programlisting>
+...
+xlset_attr("$my_route","&lt;sip:%&lt;next_host&gt;:%&lt;next_port&gt;;myparam=a123;lr&gt;");
+insert_attr_hf("Route", "$my_route");
+attr_destination("$my_route");
+t_relay();
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="xlset_destination">
+	<title><function>xlset_destination(xl_format)</function></title>
+	<para>
+		Sets the destination of the forwarded request to the value of result of xlog formatted string. Either SIP URI or nameaddr format is allowed.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>xl_format</emphasis>
+		- xlog module formatting string, the result is used for request forwarding.
+		</para>
+        </listitem>
+    </itemizedlist>
+	<para>Note: You must ensure, that the xlog module is loaded to be able to use this function.</para>
+	<example>
+	    <title><function>xlset_destination</function> usage</title>
+	    <programlisting>
+...
+xlset_destination("%&lt;next_host&gt;:%&lt;next_port&gt;");
+t_relay();
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="subst_attr">
+	<title><function>subst_attr($avp_name, subst_re)</function></title>
+	<para>
+		The value of the AVP identified by <emphasis>$avp_name</emphasis>
+		name is matched and substitued according to the
+		<emphasis>subst_re</emphasis> sed like expression.
+		Result of the substitution is then stored in the original AVP.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- Name of the AVP which will be used for the substitution.
+		</para>
+        </listitem>
+		<listitem>
+		<para><emphasis>subst_re</emphasis>
+		- SED like match&amp;replace regullar expression.
+		</para>
+		</listitem>
+    </itemizedlist>
+	<example>
+	    <title><function>subst_attr</function> usage</title>
+	    <programlisting>
+...
+subst_attr("$uri","/tel:[0-9]*/sip:\[email protected];user=phone/");
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="del_attr">
+	<title><function>del_attr($avp_name)</function></title>
+	<para>
+		The AVP identified by <emphasis>$avp_name</emphasis> name is deleted.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>$avp_name</emphasis>
+		- Name of the AVP which will be deleted.
+		</para>
+        </listitem>
+    </itemizedlist>
+	<example>
+	    <title><function>del_attr</function> usage</title>
+	    <programlisting>
+...
+failure_route[1] {
+	if (status=~4[0-9][0-9]) {
+		if (attr_exists("backup_gw") {
+			append_branch;
+			attr_destination("backup_gw");
+			del_attr("backup_gw");
+			t_relay();
+		}
+	}
+...
+	    </programlisting>
+	</example>
+	</section>
+	
+	<section id="hdr_body2attrs">
+	<title><function>hdr_body2attrs(headername, prefix)</function></title>
+	<para>
+		Function parses a header body content scans for fld1=val1,fld2=val2,... and creates bunch of avps prefixfld1:= val1, prefixfld2:= val2, .... 
+		If possible stores values as integers.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>headername</emphasis>
+		- The header name, which will be scanned for the name=value pairs.
+		</para>
+		<para>If you want to create only AVPs with integer value use "/i" postfix to the header name.</para>
+		<para>If you want to create only AVPs with string  value use "/s" postfix to the header name.</para>
+        </listitem>
+		<listitem>
+		<para><emphasis>prefix</emphasis>
+		- The prefix, which is added before the name parsed from the header body.
+		</para>
+		</listitem>
+    </itemizedlist>
+	</section>
+
+	<section id="hdr_body2attrs2">
+	<title><function>hdr_body2attrs2(headername, prefix)</function></title>
+	<para>
+		Function parses a header body content scans for fld1=val1,val2;fld2=val3,... and creates bunch of avps prefixfld1#1:= val1, prefixfld1#2:= val2, prefixfld2:=val3 .... 
+		If possible stores values as integers.
+	</para>
+    <para>Meaning of the parameter is as follows:</para>
+    <itemizedlist>
+        <listitem>
+		<para><emphasis>headername</emphasis>
+		- The header name, which will be scanned for the name=value pairs.
+		</para>
+		<para>If you want to create only AVPs with integer value use "/i" postfix to the header name.</para>
+		<para>If you want to create only AVPs with string  value use "/s" postfix to the header name.</para>
+        </listitem>
+		<listitem>
+		<para><emphasis>prefix</emphasis>
+		- The prefix, which is added before the name parsed from the header body.
+		</para>
+		</listitem>
+    </itemizedlist>
+	<example>
+	<title><function>hdr_body2attrs</function> and <function>hdr_body2attrs2</function> usage</title>
+	<programlisting>
+if (method=="BYE") {
+	# QoS reporting
+	if (search("^User-Agent: AVM FRITZ!Box Fon*")) {
+		hdr_body2attrs2("X-RTP-Stat/i", "QoS_");
+		xlog("L_INFO", "QoS: %Ts, %fu, %tu, %ci, %{User-Agent}, %{X-RTP-Stat}\n");
+	} else if (search("^User-Agent: Sipura/*")) {
+		hdr_body2attrs("P-RTP-Stat/i", "QoS_");
+		xlog("L_INFO", "QoS: %Ts, %fu, %tu, %ci, %{User-Agent}, %{P-RTP-Stat}\n");
+	}
+}
+# AVP QoS_xx now contain the values from appropriate header
+# e.g. QoS_JI is jitter
+	</programlisting>
+	</example>
+	</section>
+
+</section>

+ 37 - 0
modules_s/avp/doc/params.xml

@@ -0,0 +1,37 @@
+<?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.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Parameters</title>
+
+    <section id="xlbuf_size">
+    <title><varname>xlbuf_size</varname> (integer)</title>
+    <para>
+		Defines size of internal buffer for all xlog formatting calls. If you
+don't use xlog formatting calls, you can set it to 0 to preserve some memory, if
+you get errors while formatting due to buffer size, you can enlarge it.
+	</para>
+    <para>
+        Default value is 256.
+    </para>
+    <example>
+        <title>Set <varname>xlbuf_size</varname> parameter</title>
+        <programlisting>
+...
+modparam("avp", "xlbuf_size", 1024)
+...
+        </programlisting>
+    </example>
+    </section>
+
+</section>

+ 18 - 0
modules_s/avp_db/Makefile

@@ -0,0 +1,18 @@
+# $Id$
+#
+# Digest Authentication - Database support
+#
+# 
+# WARNING: do not run this directly, it should be run by the master Makefile
+
+include ../../Makefile.defs
+auto_gen=
+NAME=avp_db.so
+LIBS=
+DEFS += -Wall
+
+DEFS+=-DSER_MOD_INTERFACE
+
+SERLIBPATH=../../lib
+SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2
+include ../../Makefile.modules

+ 207 - 0
modules_s/avp_db/README

@@ -0,0 +1,207 @@
+
+avp_db Module
+
+Jiri Kuthan
+
+   FhG Fokus
+
+   Copyright © 2004, 2005 FhG FOKUS
+     _________________________________________________________
+
+   Table of Contents
+   1. User's Guide
+
+        1.1. Overview
+        1.2. Dependencies
+        1.3. Exported Parameters
+
+              1.3.1. caller_uuid_avp (integer)
+              1.3.2. callee_uuid_avp (integer)
+              1.3.3. db_url (string)
+              1.3.4. pref_table (string)
+              1.3.5. uuid_column (string)
+              1.3.6. username_column (string)
+              1.3.7. domain_column (string)
+              1.3.8. attr_column (string)
+              1.3.9. val_column (string)
+              1.3.10. use_domain (int)
+              1.3.11. pref_list_table (string)
+              1.3.12. attr_name_column (string)
+              1.3.13. att_type_column (string)
+              1.3.14. att_dval_column (string)
+
+        1.4. Exported Functions
+
+              1.4.1. avp_load(type)
+
+        1.5. FIFO Commands
+
+              1.5.1. avp_list_reload
+     _________________________________________________________
+
+Chapter 1. User's Guide
+
+1.1. Overview
+
+   This module contains several functions that can be used to
+   manipulate the contents of AVPs (Attribute-Value pairs). The
+   AVPs are variables attached to the SIP message being
+   processed. Each variable has its name and value. AVPs can be
+   used to store arbitrary data or as a means of inter-module
+   comminication.
+
+   You may also want to check the avpops module which is more
+   flexible and contains more functions. In future SER releases
+   the avp module will be probably deprecated in favor of avpops
+   module.
+
+   Domain module operates in caching mode. Domain module reads
+   the default values of AVPs into cache memory when the module
+   is loaded. After that default values is re-read only when
+   module is given avp_list_reload fifo command. Any changes in
+   usr_preferences_types table must thus be followed by
+   avp_list_reload command in order to reflect them in module
+   behavior.
+     _________________________________________________________
+
+1.2. Dependencies
+
+   A database module, such as mysql, postgres, or dbtext.
+     _________________________________________________________
+
+1.3. Exported Parameters
+
+1.3.1. caller_uuid_avp (integer)
+
+   Number of the AVP that contains the UUID of caller.
+
+   Default value is 1.
+     _________________________________________________________
+
+1.3.2. callee_uuid_avp (integer)
+
+   Number of the AVP that contains the UUID of callee.
+
+   Default value is 2.
+     _________________________________________________________
+
+1.3.3. db_url (string)
+
+   The URL of the database to be used.
+
+   Default value is "mysql://ser:heslo@localhost/ser".
+     _________________________________________________________
+
+1.3.4. pref_table (string)
+
+   Name of the user preferences table from which the contents of
+   AVPs will be loaded.
+
+   Default value is "usr_preferences".
+     _________________________________________________________
+
+1.3.5. uuid_column (string)
+
+   Name of the column that stores UUID in the preferences table.
+
+   Default value is "uuid".
+     _________________________________________________________
+
+1.3.6. username_column (string)
+
+   Name of the column containing the username of the subscriber.
+
+   Default value is "username".
+     _________________________________________________________
+
+1.3.7. domain_column (string)
+
+   Name of the column containing the domain that the subscriber
+   belongs to. This is useful when SER is running in multi-domain
+   mode.
+
+   Default value is "domain".
+     _________________________________________________________
+
+1.3.8. attr_column (string)
+
+   The name of the column containing attribute names.
+
+   Default value is "attribute".
+     _________________________________________________________
+
+1.3.9. val_column (string)
+
+   The name of the column containing attribute value.
+
+   Default value is "value".
+     _________________________________________________________
+
+1.3.10. use_domain (int)
+
+   This parameter controls where the module should use also the
+   domain part, in addition to the username, as the key. This
+   parameter should be set to 1 in multi-domain mode.
+
+   Default value is 0.
+     _________________________________________________________
+
+1.3.11. pref_list_table (string)
+
+   Name of the table storing user preferences types and default
+   values.
+
+   Default value is "usr_preferences_types".
+     _________________________________________________________
+
+1.3.12. attr_name_column (string)
+
+   The name of the column in usr_preferences_types table storing
+   names of AVPs.
+
+   Default value is "att_name".
+     _________________________________________________________
+
+1.3.13. att_type_column (string)
+
+   The name of the column in usr_preferences_types table storing
+   types of AVPs.
+
+   Default value is "att_raw_type".
+     _________________________________________________________
+
+1.3.14. att_dval_column (string)
+
+   The name of the column in usr_preferences_types table
+   containing default values of AVPs.
+
+   Default value is "default_value".
+     _________________________________________________________
+
+1.4. Exported Functions
+
+1.4.1. avp_load(type)
+
+   Load AVPs from the database.
+
+   Meaning of the parameters is as follows:
+
+     * type - One of:
+          + caller_uuid - Load AVPs for caller identified by
+            UUID. All AVP names will have "caller_" prefix.
+          + callee_uuid - Load AVPs for callee identified by
+            UUID. All AVP names will have "callee_" prefix.
+          + caller - Load AVPs for caller based on the URI of the
+            caller (From). All AVP names will have "caller_"
+            prefix.
+          + callee - Load AVPs for callee based on the URI of the
+            callee (Request-URI). All AVP names will have
+            "callee_" prefix.
+     _________________________________________________________
+
+1.5. FIFO Commands
+
+1.5.1. avp_list_reload
+
+   Causes avp_db module to re-read the contents of
+   pref_list_table table into cache memory.

+ 400 - 0
modules_s/avp_db/avp_db.c

@@ -0,0 +1,400 @@
+/*
+ * $Id$
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * History:
+ * --------
+ * 2004-06-14 added ability to read default values from DB table usr_preferences_types (kozlik)
+ */
+
+#include <string.h>
+#include "../../sr_module.h"
+#include "../../mem/mem.h"
+#include "../../parser/msg_parser.h"
+#include "../../parser/hf.h"
+#include "../../parser/parse_from.h"
+#include "../../parser/parse_to.h"
+#include "../../parser/parse_uri.h"
+#include "../../str.h"
+#include "../../lib/srdb2/db.h"
+#include "../../config.h"
+#include "../../usr_avp.h"
+#include "../../ut.h"
+#include "../../id.h"
+#include "../domain/domain.h"
+#include "avp_db.h"
+#include "extra_attrs.h"
+
+MODULE_VERSION
+
+static char* db_url           = DEFAULT_RODB_URL;    /* Database URL */
+static char* user_attrs_table = "user_attrs";
+static char* uri_attrs_table  = "uri_attrs";
+static char* uid_column       = "uid";
+static char* username_column  = "username";
+static char* did_column       = "did";
+static char* name_column      = "name";
+static char* type_column      = "type";
+static char* val_column       = "value";
+static char* flags_column     = "flags";
+static char* scheme_column    = "scheme";
+int   auto_unlock      = 0;
+
+db_ctx_t* ctx = 0;
+db_cmd_t *load_user_attrs_cmd = NULL;
+db_cmd_t *load_uri_attrs_cmd = NULL;
+
+static int mod_init(void);
+static int child_init(int);
+static int load_attrs(struct sip_msg* msg, char* s1, char* s2);
+static int attrs_fixup(void** param, int param_no);
+
+
+static domain_get_did_t dm_get_did = NULL;
+
+/*
+ * Exported functions
+ */
+static cmd_export_t cmds[] = {
+    {"load_attrs", load_attrs, 2, attrs_fixup, REQUEST_ROUTE | FAILURE_ROUTE},
+	
+	/* functions for loading/storing flagged attributes into DB */
+    {"load_extra_attrs", load_extra_attrs, 2, extra_attrs_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"save_extra_attrs", save_extra_attrs, 2, extra_attrs_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"remove_extra_attrs", remove_extra_attrs, 2, extra_attrs_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+
+	/* locking attrs - needed for proper work! */
+    {"lock_extra_attrs", lock_extra_attrs, 2, extra_attrs_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},
+    {"unlock_extra_attrs", unlock_extra_attrs, 2, extra_attrs_fixup, REQUEST_ROUTE | ONREPLY_ROUTE | FAILURE_ROUTE},	
+	
+    {0, 0, 0, 0, 0}
+};
+
+
+/*
+ * Exported parameters
+ */
+static param_export_t params[] = {
+    {"db_url",           PARAM_STRING, &db_url          },
+    {"user_attrs_table", PARAM_STRING, &user_attrs_table},
+    {"uri_attrs_table",  PARAM_STRING, &uri_attrs_table },
+    {"uid_column",       PARAM_STRING, &uid_column      },
+    {"username_column",  PARAM_STRING, &username_column },
+    {"did_column",       PARAM_STRING, &did_column      },
+    {"name_column",      PARAM_STRING, &name_column     },
+    {"type_column",      PARAM_STRING, &type_column     },
+    {"value_column",     PARAM_STRING, &val_column      },
+    {"flags_column",     PARAM_STRING, &flags_column    },
+    {"scheme_column",    PARAM_STRING, &scheme_column   },
+
+	{"attr_group", PARAM_STR | PARAM_USE_FUNC, (void*)declare_attr_group },
+	{"auto_unlock_extra_attrs", PARAM_INT, &auto_unlock },
+    {0, 0, 0}
+};
+
+
+struct module_exports exports = {
+    "avp_db",
+    cmds,        /* Exported commands */
+    0,           /* RPC methods */
+    params,      /* Exported parameters */
+    mod_init,    /* module initialization function */
+    0,           /* response function*/
+    0,           /* destroy function */
+    0,           /* oncancel function */
+    child_init   /* per-child init function */
+};
+
+
+static int mod_init(void)
+{
+	return init_extra_avp_locks();
+}
+
+
+static int child_init(int rank)
+{
+	db_fld_t res_cols[] = {
+		{.name = name_column, .type = DB_STR},
+		{.name = type_column, .type = DB_INT},
+		{.name = val_column, .type = DB_STR},
+		{.name = flags_column, .type = DB_BITMAP},
+		{.name = NULL}
+	};
+	db_fld_t match_uri[] = {
+		{.name = username_column, .type = DB_STR, .op = DB_EQ},
+		{.name = did_column, .type = DB_STR, .op = DB_EQ},
+		{.name = scheme_column, .type = DB_STR, .op = DB_EQ},
+		{.name = NULL}
+	};
+	db_fld_t match_user[] = {
+		{.name = uid_column, .type = DB_STR, .op = DB_EQ},
+		{.name = NULL}
+	};
+
+	if (rank==PROC_INIT || rank==PROC_MAIN || rank==PROC_TCP_MAIN)
+		return 0; /* do nothing for the main process */
+	
+	ctx = db_ctx("avp_db");
+	if (!ctx) goto err;
+	if (db_add_db(ctx, db_url) < 0) goto err;
+	if (db_connect(ctx) < 0) goto err;
+	
+	load_uri_attrs_cmd = db_cmd(DB_GET, ctx, uri_attrs_table, res_cols, match_uri, NULL);
+	if (!load_uri_attrs_cmd) goto err;
+
+	load_user_attrs_cmd = db_cmd(DB_GET, ctx, user_attrs_table, res_cols, match_user, NULL);
+	if (!load_user_attrs_cmd) goto err;
+
+	if (init_extra_avp_queries(ctx) < 0) goto err;
+
+    return 0;
+
+err:
+	if (load_uri_attrs_cmd) db_cmd_free(load_uri_attrs_cmd);
+	if (load_user_attrs_cmd) db_cmd_free(load_user_attrs_cmd);
+
+	if (ctx) db_ctx_free(ctx);
+
+	ERR("Error while initializing database layer\n");
+	return -1;
+}
+
+#define IS_DB_NULL(f)	(f.flags & DB_NULL)
+
+static void read_attrs(db_res_t *res, unsigned long flags)
+{
+    int_str name, v;
+    str avp_val;
+    int type, n, found;
+	db_rec_t* row;
+
+	n = 0;
+	found = 0;
+	/* AVP names from DB are always strings */
+	flags |= AVP_NAME_STR;
+	if (res) row = db_first(res);
+	else row = NULL;
+	while (row) {
+		found++;
+
+		if (IS_DB_NULL(row->fld[0]) || 
+				IS_DB_NULL(row->fld[1]) ||
+				IS_DB_NULL(row->fld[3])) {
+			ERR("Skipping row containing NULL entries\n");
+			row = db_next(res);
+			continue;
+		}
+
+		if ((row->fld[3].v.int4 & SRDB_LOAD_SER) == 0) {
+			row = db_next(res);
+			continue;
+		}
+
+		n++;
+		/* Get AVP name */
+		name.s = row->fld[0].v.lstr;
+
+		/* Get AVP type */
+		type = row->fld[1].v.int4;
+
+		/* Test for NULL value */
+		if (IS_DB_NULL(row->fld[2])) {
+			avp_val.s = 0;
+			avp_val.len = 0;
+		} else {
+			avp_val = row->fld[2].v.lstr;
+		}
+
+		if (type == AVP_VAL_STR) {
+			/* String AVP */
+			v.s = avp_val;
+			flags |= AVP_VAL_STR;
+		} else {
+			/* Integer AVP */
+			str2int(&avp_val, (unsigned*)&v.n);
+			/* reset val_str as the value could be an integer */
+			flags &= ~AVP_VAL_STR;
+		}
+
+		if (add_avp(flags, name, v) < 0) {
+			ERR("Error while adding user attribute %.*s, skipping\n",
+					name.s.len, ZSW(name.s.s));
+		}
+		row = db_next(res);
+
+	}
+	DBG("avp_db:load_attrs: %d attributes found, %d loaded\n", found, n);
+}
+
+static int load_uri_attrs(struct sip_msg* msg, unsigned long flags, fparam_t* fp)
+{
+    db_res_t* res;
+    str uri;
+    struct sip_uri puri;
+	static str default_did = STR_STATIC_INIT(DEFAULT_DID);
+
+	if (get_str_fparam(&uri, msg, (fparam_t*)fp) != 0) {
+		DBG("Unable to get URI from load_uri_attrs parameters\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;
+	}
+
+    load_uri_attrs_cmd->match[0].v.lstr = puri.user;
+
+	if (puri.host.len) {
+		/* domain name is present */
+		if (dm_get_did(&load_uri_attrs_cmd->match[1].v.lstr, &puri.host) < 0) {
+			DBG("Cannot lookup DID for domain %.*s, using default value\n", puri.host.len, ZSW(puri.host.s));
+			load_uri_attrs_cmd->match[1].v.lstr = default_did;
+		}
+	} else {
+		/* domain name is missing -- can be caused by tel: URI */
+		DBG("There is no domain name, using default value\n");
+		load_uri_attrs_cmd->match[1].v.lstr = default_did;
+	}
+
+    uri_type_to_str(puri.type, &(load_uri_attrs_cmd->match[2].v.lstr));
+
+	if (db_exec(&res, load_uri_attrs_cmd) < 0) {
+		ERR("Error while quering database\n");
+		return -1;
+    }
+    
+	if (res) {
+		read_attrs(res, flags);
+		db_res_free(res);
+	}
+	return 1;
+}
+
+
+static int load_user_attrs(struct sip_msg* msg, unsigned long flags, fparam_t* fp)
+{
+    db_res_t* res;
+
+	if (get_str_fparam(&load_user_attrs_cmd->match[0].v.lstr, msg, (fparam_t*)fp) < 0) {
+		DBG("Unable to get UID from load_user_attrs parameter\n");
+		return -1;
+	}
+
+	if (db_exec(&res, load_user_attrs_cmd) < 0) {
+		ERR("Error while quering database\n");
+		return -1;
+    }
+    
+	if (res) {
+		read_attrs(res, flags);
+		db_res_free(res);
+	}
+	return 1;
+}
+
+
+/*
+ * Load user attributes
+ */
+static int 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;
+}

+ 31 - 0
modules_s/avp_db/avp_db.h

@@ -0,0 +1,31 @@
+/*
+ * $Id$
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../../lib/srdb2/db.h"
+
+extern db_ctx_t* ctx;
+extern int auto_unlock;

+ 750 - 0
modules_s/avp_db/avp_db.xml

@@ -0,0 +1,750 @@
+<?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.avp_db"
+          xmlns:serdoc="http://sip-router.org/xml/serdoc">
+  <refmeta>
+    <refentrytitle>avp_db</refentrytitle>
+    <manvolnum>7</manvolnum>
+  </refmeta>
+  <refnamediv>
+    <refname>avp_db</refname>
+    <refpurpose>Storage of Attributes in a Database</refpurpose>
+  </refnamediv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      The <command>avp_db</command> module provides a number of function
+      that allow access to attributes stored in a database.
+    </para>
+    <para>
+      The module provides two features. For one, it loads attributes
+      associated to users and URIs by means of the
+      <serdoc:func>load_attrs</serdoc:func> function.
+    </para>
+    <para>
+      Additionally, it can store and load attributes in a database in
+      so called extra attribute groups. See <serdoc:link
+        linkend="module.avp_db.extra_attribute_groups">Extra Attribute
+        Groups</serdoc:link> below for more on those.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Functions</title>
+
+    <refsect2 xml:id="function.load_attrs">
+      <title>
+        <function>load_attrs</function>
+        (<symbol>group</symbol>, <symbol>id</symbol>)
+      </title>
+      <para>
+        Allowed in request and failure processing.
+      </para>
+      <para>
+        The <function>load_attrs()</function> function loads the attributes
+        assigned to a certain user or a URI. The action performed is
+        specified by the <symbol>group</symbol>. It contain an attribute
+        group prefix. The loaded attributes will be placed in that group.
+        Additionally, it specifies the meaning of the <symbol>id</symbol>
+        argument.
+      </para>
+      <para>
+        If the group prefix refers to one of the user groups,
+        ie., it is <literal>$fu</literal> or <literal>$tu</literal>, the
+        <symbol>id</symbol> argument contains the user ID of the user
+        whose attributes should be loaded.
+      </para>
+      <para>
+        If the group prefix refers to one of the URI groups, ie., it is
+        <literal>$fr</literal> or <literal>$tr</literal>, the
+        <symbol>id</symbol> argument contains the URI whose attributes
+        should be loaded.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.load_extra_attrs">
+      <title>
+        <function>load_extra_attrs</function>
+        (<symbol>group</symbol>, <symbol>key</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>load_extra_attrs()</function> function loads the
+        attributes of the extra attribute group identified by the argument
+        <symbol>group</symbol> that have been stored previously with the
+        key given by the argument <symbol>key</symbol>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.lock_extra_attrs">
+      <title>
+        <function>lock_extra_attrs</function>
+        (<symbol>group</symbol>, <symbol>key</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>lock_extra_attrs()</function> function places a
+        lock on the key given by the argument <symbol>key</symbol>
+        of the extra attribute group given by the
+        argument <symbol>group</symbol>.
+      </para>
+      <para>
+        Despite the name, the function actually enters a mutex assigned
+        to the group. Thus, what really happens is that if you try to
+        call the function again before
+        <serdoc:func>unlock_extra_attrs</serdoc:func> has been called,
+        processing will halt until such a call. It does not actually lock
+        access to the database.
+      </para>
+      <para>
+        Be extra careful when using the function. If you exit from
+        processing without calling
+        <serdoc:func>unlock_extra_attrs</serdoc:func>, you may eventually
+        block SER completely.
+      </para>
+      <para>
+        Note that currently, the mutex is on the group only and not on
+        the key.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.remove_extra_attrs">
+      <title>
+        <function>remove_extra_attrs</function>
+        (<symbol>group</symbol>, <symbol>key</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>remove_extra_attrs()</function> deletes all
+        attributes assigned to the key given by the argument
+        <symbol>key</symbol> from the extra attribute group given by the
+        argument <symbol>group</symbol>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.save_extra_attrs">
+      <title>
+        <function>save_extra_attrs</function>
+        (<symbol>group</symbol>, <symbol>key</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>save_extra_attrs()</function> function stores all
+        attributes with the attribute flag stated in the extra attribute
+        group definition given by the argument <symbol>group</symbol> in
+        the database given in that group definition. The attributes will
+        be stored using the key given by the argument
+        <symbol>key</symbol>.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="function.unlock_extra_attrs">
+      <title>
+        <function>unlock_extra_attrs</function>
+        (<symbol>group</symbol>, <symbol>key</symbol>)
+      </title>
+      <para>
+        Allowed in request, reply, and failure processing.
+      </para>
+      <para>
+        The <function>unlock_extra_attrs()</function> function releaes the
+        lock on the key given by the argument <symbol>key</symbol>
+        of the extra attribute group given by the argument
+        <symbol>group</symbol> previously imposed by a call to the function
+        <serdoc:func>lock_extra_attrs</serdoc:func>.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1>
+    <title>Module Parameters</title>
+
+    <refsect2 xml:id="module.avp_db.attr_group">
+      <title><parameter>attr_group</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>attr_group</parameter> parameter defines a extra
+        attribute group. See <serdoc:link
+          linkend="module.avp_db.extra_attribute_groups">Extra Attribute
+        Groups</serdoc:link> below for more information on the content
+        and usage of the parameter.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.db_url">
+      <title><parameter>db_url</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"mysql://serro:47serro11@localhost/ser"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>db_url</parameter> parameter specifies the database
+        to be used in form of a URL. See the module of you intended database
+        for information on the format of the URL for that database.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.did_column">
+      <title><parameter>did_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"did"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>did_column</parameter> parameter specifies the name
+        of the database column for the
+        <serdoc:field table="uri_attrs">did</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_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 specifies the name
+        of the database column for the
+        <serdoc:field table="user_attrs">flags</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.name_column">
+      <title><parameter>name_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"name"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>name_column</parameter> parameter specifies the name
+        of the database column for the
+        <serdoc:field table="user_attrs">name</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.scheme_column">
+      <title><parameter>scheme_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"scheme"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>scheme_column</parameter> parameter specifies the
+        name of the database column for the
+        <serdoc:field table="uri_attrs">scheme</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.type_column">
+      <title><parameter>type_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"type"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>type_column</parameter> parameter specifies the name
+        of the database column for the
+        <serdoc:field table="user_attrs">type</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.uid_column">
+      <title><parameter>uid_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"uid"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>uid_column</parameter> parameter specifies the name
+        of the database column for the
+        <serdoc:field table="user_attrs">uid</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.uri_attrs_table">
+      <title><parameter>uri_attrs_table</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"uri_attrs"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>uri_attrs_table</parameter> parameter specifies the
+        name of the database table that holds the attributes assigned to
+        URIs.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.user_attrs_table">
+      <title><parameter>user_attrs_table</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"user_attrs"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>user_attrs_table</parameter> parameter specifies the
+        name of the database table that contains the attributes assigned to
+        users.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.username_column">
+      <title><parameter>username_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"username"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>username_column</parameter> parameter specifies
+        the name of the database column for the
+        <serdoc:field table="uri_attrs">username</serdoc:field> field.
+      </para>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.value_column">
+      <title><parameter>value_column</parameter></title>
+      <serdoc:paraminfo>
+        <serdoc:paramtype>string</serdoc:paramtype>
+        <serdoc:paramdefault>"value"</serdoc:paramdefault>
+      </serdoc:paraminfo>
+      <para>
+        The <parameter>value_column</parameter> parameter specifies the name
+        of the database column for the 
+        <serdoc:field table="user_attrs">value</serdoc:field> field.
+      </para>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 xml:id="module.avp_db.extra_attribute_groups">
+    <title>Extra Attribute Groups</title>
+    <para>
+      Extra attributes allow it to load an arbitrary set of attributes.
+      The set is identified by a pair of values: First, the extra attribute
+      group defines the database where the attributes can be found. Within
+      each group the specific attributes are identified by a key which is
+      simply a string.
+    </para>
+    <para>
+      Extra attribute groups are defined through the module parameter
+      <serdoc:modparam module="avp_db">attr_group</serdoc:modparam>. Each
+      occurence of the parameter defines a group. The value of the parameter
+      contains key/value pairs. Key and value are separated by an equals
+      sign, several pairs are separated by a comma.
+    </para>
+    <para>
+      The following keys exist:
+    </para>
+
+    <refsect2>
+      <title><parameter>id</parameter></title>
+      <para>
+        The <parameter>id</parameter> key defines the name of the extra
+        attribute group as it will be used to address the group in the
+        functions.
+      </para><para>
+        The key must be present.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>flag</parameter></title>
+      <para>
+        The <parameter>flag</parameter> key indentifies the name of the
+        attribute flag that must be set on an attribute in order to be
+        included in the attributes stored with the
+        <serdoc:func>store_extra_attrs</serdoc:func> function.
+      </para>
+      <para>
+        The key must be present.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>flags_column</parameter></title>
+      <para>
+        The <parameter>flags_column</parameter> key specifies the name of the
+        database column that contains the
+        <serdoc:field table="extra_attrs">flags</serdoc:field> field.
+      </para>
+      <para>
+        If the key is missing, the default of <literal>"flags"</literal>
+        will be used.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>key_column</parameter></title>
+      <para>
+        The <parameter>key_column</parameter> key specifies the name of the
+        database column that contains the
+        <serdoc:field table="extra_attrs">key</serdoc:field> field.
+      </para>
+      <para>
+        If the key is missing, the default of <literal>"id"</literal>
+        will be used.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>name_column</parameter></title>
+      <para>
+        The <parameter>name_column</parameter> key specifies the name of
+        the database column that contains the
+        <serdoc:field table="extra_attrs">name</serdoc:field> field.
+      </para>
+      <para>
+        If the key is missing, the default of <literal>"name"</literal>
+        will be used.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>table</parameter></title>
+      <para>
+        The <parameter>table</parameter> key defines the name of the
+        database table to be used for the group.
+      </para>
+      <para>
+        The key must be present.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>type_column</parameter></title>
+      <para>
+        The <parameter>type_column</parameter> key specifies the name of
+        the database column for the
+        <serdoc:field table="extra_attrs">type</serdoc:field> field.
+      </para>
+      <para>
+        If the key is missing, the default of <literal>"type"</literal>
+        will be used.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title><parameter>value_column</parameter></title>
+      <para>
+        The <parameter>value_column</parameter> key specifies the name of
+        the database column for the
+        <serdoc:field table="extra_attrs">value</serdoc:field> field.
+      </para>
+      <para>
+        If the key is missing, the default of <literal>"value"</literal>
+        will be used.
+      </para>
+    </refsect2>
+
+    <refsect2>
+      <title>Example</title>
+      <para>
+        Since this all looks a bit tricky, here are two examples for the
+        definition of a extra attribute group.
+      </para>
+      <programlisting>
+        modparam("avp_db", "attr_group",
+                 "id=dlg,flag=dialog_flag,table=dlg_attrs,key_column=dlg_id")
+      </programlisting>
+    </refsect2>
+
+  </refsect1>
+
+  <refsect1 xml:id="module.avp_db.database">
+    <title>Database Scheme</title>
+
+    The <command>avp_db</command> uses three different database schemes.
+    The attributes associated with a user are stored in the
+    user_attrs table. The attributes associated with a
+    URI are stored in the uri_attrs table. Finally, the
+    extra attributes use various tables which shall be described here as the
+    extra_attrs table.
+
+    <refsect2 xml:id="module.avp_db.database.user_attrs">
+      <title>The user_attrs table</title>
+
+      <variablelist>
+        <varlistentry xml:id="table.user_attrs.uid">
+          <term><varname>uid</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>uid</varname> field contains the user ID of the user
+              the attribute is assigned to. It is used as the key for lookups.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="tyble.user_attrs.flags">
+          <term><varname>flags</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>
+                INT UNSIGNED NOT NULL DEFAULT 0
+              </serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>flags</varname> field is a bitfield with flags
+              regarding the usage of the database entry. Only the
+              <literal>DB_LOAD_SER</literal> flag in bit 0 is being used.
+              The bit must be set in the <varname>flags</varname> field
+              for SER to load the attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.user_attrs.name">
+          <term><varname>name</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(32) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>name</varname> field contains the name of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.user_attrs.type">
+          <term><varname>type</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>INT NOT NULL DEFAULT 0</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>type</varname> field defines the type of the
+              attribute. If it is set to 2, the attribute is of type string.
+              Otherwise, it is of type integer.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.user_attrs.value">
+          <term><varname>value</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>value</varname> field contans the value of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.database.uri_attrs">
+      <title>The uri_attrs table</title>
+
+      <variablelist>
+        <varlistentry xml:id="table.uri_attrs.did">
+          <term><varname>did</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>did</varname> field contains the domain ID of the
+              domain in the domain part of the URI the attribute is assigned
+              to. The field is used as part of the key for lookups into the
+              table.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="tyble.uri_attrs.flags">
+          <term><varname>flags</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>
+                INT UNSIGNED NOT NULL DEFAULT 0
+              </serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>flags</varname> field is a bitfield with flags
+              regarding the usage of the database entry. Only the
+              <literal>DB_LOAD_SER</literal> flag in bit 0 is being used.
+              The bit must be set in the <varname>flags</varname> field
+              for SER to load the attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.uri_attrs.username">
+          <term><varname>username</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(64) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>username</varname> field contains the username
+              part of the URI the attribute is assigned to. The field is
+              used as part of the key for lookups into the table.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.uri_attrs.name">
+          <term><varname>name</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(32) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>name</varname> field contains the name of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.uri_attrs.scheme">
+          <term><varname>scheme</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>
+                VARCHAR(8) NOT NULL DEFAULT 'sip'
+              </serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>scheme</varname> field contains the scheme part
+              of the URI the attribute is assigned to. Recognized schemes
+              are <literal>sip</literal>, <literal>sips</literal>, and
+              <literal>tel</literal>. The field is used as part of the key
+              for lookups into the table.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.uri_attrs.type">
+          <term><varname>type</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>INT NOT NULL DEFAULT 0</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>type</varname> field defines the type of the
+              attribute. If it is set to 2, the attribute is of type string.
+              Otherwise, it is of type integer.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.uri_attrs.value">
+          <term><varname>value</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>value</varname> field contans the value of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </refsect2>
+
+    <refsect2 xml:id="module.avp_db.database.extra_attrs">
+      <title>The Tables for Extra Attributes</title>
+      <para>
+        This section describes the schema for the extra attribute tables.
+        Unlike most other tables, these tables don’t have a default name.
+        Instead, the name is defined by the <parameter>table</parameter> key
+        in the <serdoc:modparam module="avp_db">attr_group</serdoc:modparam>
+        module parameter.
+      </para>
+
+      <variablelist>
+        <varlistentry xml:id="table.extra_attrs.flags">
+          <term><varname>flags</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>
+                INT UNSIGNED NOT NULL DEFAULT 0
+              </serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>flags</varname> field is a bitfield with flags
+              regarding the usage of the database entry. Only the
+              <literal>DB_LOAD_SER</literal> flag in bit 0 is being used.
+              The bit must be set in the <varname>flags</varname> field
+              for SER to load the attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry  xml:id="table.extra_attrs.key">
+          <term><varname>key</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(255) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>key</varname> field contains the key for lookups
+              into the extra attribute table.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.extra_attrs.name">
+          <term><varname>name</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(32) NOT NULL</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>name</varname> field contains the name of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.extra_attrs.type">
+          <term><varname>type</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>INT NOT NULL DEFAULT 0</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>type</varname> field defines the type of the
+              attribute. If it is set to 2, the attribute is of type string.
+              Otherwise, it is of type integer.
+            </para>
+          </listitem>
+        </varlistentry>
+        <varlistentry xml:id="table.extra_attrs.value">
+          <term><varname>value</varname></term>
+          <listitem>
+            <serdoc:fieldinfo>
+              <serdoc:fieldsql>VARCHAR(255)</serdoc:fieldsql>
+            </serdoc:fieldinfo>
+            <para>
+              The <varname>value</varname> field contans the value of the
+              attribute.
+            </para>
+          </listitem>
+        </varlistentry>
+      </variablelist>
+    </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>avp</serdoc:module></member>
+      <member><serdoc:module>avp_radius</serdoc:module></member>
+    </simplelist>
+  </refsect1>
+
+</refentry>
+
+<!-- vim:sw=2 sta et sts=2 ai tw=76
+  -->

+ 29 - 0
modules_s/avp_db/doc/Makefile

@@ -0,0 +1,29 @@
+#
+# The list of documents to build (without extensions)
+#
+DOCUMENTS = avp_db
+
+#
+# The root directory containing Makefile.doc
+#
+ROOT_DIR=../../..
+
+#
+# Validate docbook documents before generating output
+# (may be slow)
+#
+#VALIDATE=1
+
+#
+# You can override the stylesheet used to generate
+# xhtml documents here
+#
+#XHTML_XSL=$(ROOT_DIR)/doc/stylesheets/xhtml.xsl
+
+#
+# You can override the stylesheet used to generate
+# plain text documents here
+#
+#TXT_XSL=$(XHTML_XSL)
+
+include $(ROOT_DIR)/Makefile.doc

+ 71 - 0
modules_s/avp_db/doc/avp_db.xml

@@ -0,0 +1,71 @@
+<?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_db" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<title>avp_db Module</title>
+	<authorgroup>
+	    <author>
+		<firstname>Jiri</firstname>
+		<surname>Kuthan</surname>
+		<affiliation><orgname>FhG FOKUS</orgname></affiliation>
+		<email>[email protected]</email>
+	    </author>
+	</authorgroup>
+	<copyright>
+	    <year>2004</year>
+	    <year>2005</year>
+	    <holder>FhG FOKUS</holder>
+	</copyright>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>Avp_db Module</title>
+    
+    <section id="avp_db.overview">
+	<title>Overview</title>
+	<para>
+	    This module contains several functions that can be used to
+	    manipulate the contents of AVPs (Attribute-Value pairs). The AVPs
+	    are variables attached to the SIP message being processed. Each
+	    variable has its name and value. AVPs can be used to store
+	    arbitrary data or as a means of inter-module comminication.
+	</para>
+	<para>
+	    You may also want to check the avpops module which is more flexible
+	    and contains more functions. In future SER releases the avp module
+	    will be probably deprecated in favor of avpops module.
+	</para>
+	<para>
+	    Domain module operates in caching mode. Domain module reads the
+	    default values of AVPs into cache memory when the module is
+	    loaded. After that default values is re-read only when module is
+	    given avp_list_reload fifo command. Any changes in
+	    usr_preferences_types table must thus be followed by
+	    avp_list_reload command in order to reflect them in module
+	    behavior.
+	</para>
+    </section>
+    
+    <section id="avp_db.dep">
+	<title>Dependencies</title>
+	<para>
+	    A database module, such as mysql, postgres, or dbtext.
+	</para>
+    </section>
+
+    <xi:include href="params.xml"/>
+    <xi:include href="functions.xml"/>
+    <!-- <xi:include href="fifo.xml"/> -->
+
+	<section><title>Example extra attributes usage</title>
+	<para><programlisting><include xmlns="http://www.w3.org/2001/XInclude"
+href="avp_dialogs.cfg" parse="text"/></programlisting></para>
+	</section>
+</section>

+ 328 - 0
modules_s/avp_db/doc/avp_dialogs.cfg

@@ -0,0 +1,328 @@
+debug=3
+memdbg=5
+server_signature=0
+sip_warning=0
+check_via=yes;
+dns=no;
+rev_dns=no;
+
+children=4;
+tcp_children=4;
+tcp_max_connections=2048;
+port=5060
+
+loadmodule "/home/kubartv/SER/lib/ser/modules/sl.so";
+#loadmodule "/home/kubartv/SER/lib/ser/modules/maxfwd.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/tm.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/rr.so";
+#loadmodule "/home/kubartv/SER/lib/ser/modules/xmlrpc.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/mysql.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/domain.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/uri_db.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/avp.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/avp_db.so";
+
+loadmodule "/home/kubartv/SER/lib/ser/modules/usrloc.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/registrar.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/xlog.so";
+loadmodule "/home/kubartv/SER/lib/ser/modules/eval.so";
+
+loadmodule "/home/kubartv/SER/lib/ser/modules/gflags.so"
+
+#modparam("maxfwd", "max_limit", 70);
+
+modparam("usrloc", "db_mode", 1);
+modparam("usrloc|avp_db", "db_url", "mysql://ser:[email protected]/ser")
+
+modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_column=dlg_id");
+
+modparam("gflags", "load_global_attrs", 1);
+
+avpflags dialog_flag;
+
+route["create_dialog"] {
+	# sets attributes needed by dialog (stored when processing reply)
+	$dlg_caller = @contact;
+	$dlg_caller_cseq = @cseq.num;
+	$dlg_status = "new";
+	$dlg_init_method = @cseq.method;
+	$dir = "caller2callee";
+
+	t_on_reply("dialog_creation_reply");
+	return 1;
+}
+
+onreply_route["dialog_creation_reply"] {
+	xlog("L_ERR", "dialog creation reply (%rs, %@cseq.method) [%@from.tag, %@to.tag]\n");
+
+	$res = @msg.response.code;
+	xlog("L_ERR", " ... response: %$res\n");
+	if (([email protected]) || (@to.tag=="")) {
+		# don't create dialog from response without to tag
+		break;
+	}
+	if ($res < 101) {
+		xlog("L_ERR", " ... I won't create dialog from 100 response.\n");
+		break;
+	}
+	
+	del_attr("$id"); # xlset_attr works strange when the attribute already exists
+	xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%@to.tag");
+	if ($res > 299) {
+		xlog("L_ERR", " ... dialog terminated\n");
+		remove_extra_attrs("dlg", "$id");
+		break;
+	}
+
+	xlog("L_ERR", " ... creating dialog '%$id'\n");
+	# generate dialog id
+
+	lock_extra_attrs("dlg", "$id");
+
+	# TODO: try to load the dialog (early dialog may exist)?
+
+	# update dialog data
+
+	if (($dlg_status == "new") && ($res < 200)) {
+		# early response may arrive after final one
+		$dlg_callee = @contact;
+		$dlg_status = "early";
+		xlog("L_ERR", " ... creating early dialog\n");
+	}
+	if ($res >= 200) {
+		$dlg_callee = @contact;
+		$dlg_status = "confirmed";
+		$dlg_confirmed_at = @sys.now.local;
+		xlog("L_ERR", " ... confirming dialog\n");
+	}
+
+	route("save_dialog");
+}
+
+route["save_dialog"] {
+	if ($dlg_status == "destroyed") {
+		xlog("L_ERR", " ... destroying dialog %$id\n");
+		
+		# use this if you want to delete destroyed dialogs:
+		# remove_extra_attrs("dlg", "$id");
+
+		# else if you want leave them in DB (with the time of termination)
+		$dlg_destroyed_at = @sys.now.local;
+		
+		# set flag for attributes with name beggining with dlg_
+		setavpflag("$f./^dlg_/", "dialog_flag");
+		save_extra_attrs("dlg", "$id");
+	}
+	else {
+		# set flag for attributes with name beggining with dlg_
+		setavpflag("$f./^dlg_/", "dialog_flag");
+
+		save_extra_attrs("dlg", "$id");
+	}
+	unlock_extra_attrs("dlg", "$id");
+}
+
+route["load_dialog_data"] {
+	lock_extra_attrs("dlg", "$id");
+
+	del_attr("$dlg_init_method"); # used as flag of succesful read of data
+
+	# delete all used dlg attrs (because load_extra_attrs doesn't delete them itself before adding)
+	del_attr("$dlg_init_method");
+	del_attr("$dlg_caller");
+	del_attr("$dlg_callee");
+	del_attr("$dlg_caller_cseq");
+	del_attr("$dlg_callee_cseq");
+	del_attr("$dlg_status");
+
+	load_extra_attrs("dlg", "$id");
+	if (!$dlg_init_method) {
+		# dialog was not loaded
+		unlock_extra_attrs("dlg", "$id");
+		return -1;
+	}
+	return 1;
+}
+
+route["load_dialog"] {
+	# tries to load dialog according tags and callid
+	
+	# try to load dialog
+	del_attr("$id"); # xlset_attr works strange when the attribute already exists
+	xlset_attr("$id", "call-id:%@call_id caller_tag:%@from.tag callee_tag:%@to.tag");
+	if (route("load_dialog_data")) { 
+		$dir = "caller2callee";
+		return 1; 
+	}
+
+	# try to load dialog in other direction
+	del_attr("$id"); # xlset_attr works strange when the attribute already exists
+	xlset_attr("$id", "call-id:%@call_id caller_tag:%@to.tag callee_tag:%@from.tag");
+	if (route("load_dialog_data")) { 
+		$dir = "callee2caller";
+		return 1; 
+	}
+	
+	$id = "error";
+
+	return -1;
+}
+
+route["update_dialog_reply"] {
+	if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
+		# target refresh for INVITE dialogs
+
+		if ($dir == "caller2calle") { # if request from caller
+			$dlg_callee = @contact; # update callee's contact (response!)
+		}
+		else {
+			$dlg_caller = @contact;
+		}
+	}
+	if (@cseq.method=="BYE") {
+		$dlg_status = "destroyed"; # will be removed in save_dialog
+	}
+}
+
+route["update_dialog"] {
+	if ((@cseq.method == "INVITE") || (@cseq.method == "UPDATE")) {
+		# target refresh for INVITE dialogs
+
+		if ($dir == "caller2calle") { # if request from caller
+			$dlg_caller = @contact; # update caller's contact (request!)
+		}
+		else {
+			$dlg_callee = @contact;
+		}
+	}
+
+	if ($dir == "caller2callee") { # if request from caller
+		# TODO: verify CSeq before modifying and return 500 if lower than last one
+		$dlg_caller_cseq = @cseq.num;
+	}
+	else {
+		# TODO: verify CSeq before modifying and return 500 if lower than last one
+		$dlg_callee_cseq = @cseq.num;
+	}
+
+	if (method=="BYE") {
+		$dlg_status = "pre-destroyed"; # to see that BYE already went through
+	}
+	return 1;
+}
+
+route["trace_dialog"] {
+	xlog("L_ERR", " ... dialog '%$id'\n");
+	xlog("L_ERR", "       -> initial method: %$dlg_init_method\n");
+	xlog("L_ERR", "       -> request dir: %$dir\n");
+	xlog("L_ERR", "       -> caller: %$dlg_caller\n");
+	xlog("L_ERR", "       -> caller's CSeq: %$dlg_caller_cseq\n");
+	xlog("L_ERR", "       -> callee: %$dlg_callee\n");
+	xlog("L_ERR", "       -> callee's CSeq: %$dlg_callee_cseq\n");
+	xlog("L_ERR", "       -> status: %$dlg_status\n");
+	return 1;
+}
+
+onreply_route["dialog_reply"] {
+	if ($id) {
+		xlog("L_ERR", "In-dialog reply (%rs, %@cseq.method) [%@to.tag, %@from.tag]\n");
+		if (!route("load_dialog")) {
+			xlog("L_ERR", "Can't load dialog data\n");
+		}
+		else {
+			route("update_dialog_reply");
+			route("trace_dialog");
+			route("save_dialog");
+		}
+	}
+}
+
+route {
+	if (method=="SUBSCRIBE") {
+		# here we support only INVITE/BYE dialogs
+		sl_reply("400", "Unsupported");
+		break;
+	}
+	
+	if (!lookup_domain("$td", "@to.uri.host")) {
+		sl_send_reply("404", "Domain Not Found");
+		break;
+	}
+	
+	if (!lookup_user("To")) {
+		sl_send_reply("404", "Unknown user");
+		break;
+	}
+
+	if (method=="REGISTER") {
+		save("location");
+		break;
+	};
+		
+	xlog("L_ERR", "----> processing request %@cseq.method\n");
+		
+	if (!lookup_domain("$fd", "@from.uri.host")) {
+		sl_send_reply("404", "From domain Not Found");
+		break;
+	}
+
+	if (!lookup_user("From")) {
+		sl_send_reply("404", "Unknown user in From");
+		break;
+	}
+
+	if (method != "REGISTER") record_route();
+
+	# dialog needs transactions
+	if (!t_newtran()) {
+		sl_send_reply("500", "Can not start transaction");
+		drop;
+	}
+
+	# dialog creation/loading
+	if ([email protected] || (@to.tag == "")) {
+		# initial request or non-dialog message
+		if (method=="INVITE") {
+			route("create_dialog");
+			# we don't save the dialog here because AVPs will be set
+			# when reply comes and the dialog will be stored then
+		}
+		else {
+			xlog("L_ERR", "Non-dialog message: %@cseq\n");
+		}
+	}
+	else {
+		# message within dialog
+		if (route("load_dialog")) {
+			route("update_dialog");
+			route("trace_dialog");
+			route("save_dialog");
+			t_on_reply("dialog_reply");
+		}
+		else {	
+			xlog("L_ERR", "Message within unknown dialog: %@cseq, to_tag=%@to.tag from_tag=%@from.tag\n");
+		}
+	}
+
+	if (loose_route()) {
+		route(1);
+		break;
+	}
+
+	if (!lookup("location")) {
+		t_reply("404", "Not Found");
+		break;
+	};
+	route(1);
+}
+
+route[1] 
+{
+	xlog("L_ERR", "<---- request %@cseq.method processed\n");
+	# send it out now; use stateful forwarding as it works reliably
+	# even for UDP2TCP
+	if (!t_relay()) {
+		sl_reply_error();
+	};
+}
+

+ 25 - 0
modules_s/avp_db/doc/fifo.xml

@@ -0,0 +1,25 @@
+<?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_db.fifo" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>
+
+    <title>FIFO Interface</title>
+    
+    <section id="avp_list_reload">
+	<title><function>avp_list_reload</function></title>
+	<para>
+	    Causes avp_db module to re-read the contents of pref_list_table
+	    table into cache memory.
+	</para>
+    </section>
+
+</section>

+ 217 - 0
modules_s/avp_db/doc/functions.xml

@@ -0,0 +1,217 @@
+<?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_db.functions" xmlns:xi="http://www.w3.org/2001/XInclude">
+	<!-- following is not needed in every section - it is needless here? 
+    <sectioninfo>
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>-->
+
+    <title>Functions</title>
+<!--
+    <section id="avp_load">
+	<title><function moreinfo="none">avp_load(type)</function></title>
+	<para>
+	    Load AVPs from the database.
+	</para>
+	<para>Meaning of the parameters is as follows:</para>
+	<itemizedlist>
+	    <listitem>
+		<para><emphasis>type</emphasis> - One of:
+		    <itemizedlist>
+			<listitem>
+			    <para>
+				<emphasis>caller_uuid</emphasis> - Load AVPs
+				for caller identified by UUID. All AVP names
+				will have "caller_" prefix.
+			    </para>
+			</listitem>
+			<listitem>
+			    <para>
+				<emphasis>callee_uuid</emphasis> - Load AVPs
+				for callee identified by UUID. All AVP
+				names will have "callee_" prefix.
+			    </para>
+			</listitem>
+			<listitem>
+			    <para>
+				<emphasis>caller</emphasis> - Load AVPs for
+				caller based on the URI of the caller
+				(From). All AVP names will have "caller_"
+				prefix.
+			    </para>
+			</listitem>
+			<listitem>
+			    <para>
+				<emphasis>callee</emphasis> - Load AVPs for
+				callee based on the URI of the callee
+				(Request-URI). All AVP names will have
+				"callee_" prefix.
+			    </para>
+			</listitem>
+		    </itemizedlist>
+		</para>
+	    </listitem>
+	</itemizedlist>
+    </section>
+	-->
+
+	<section><title>load_attrs (track, id)</title>
+	<para>
+		Loads attributes from the database.
+
+		<variablelist>
+		Track can have following values:
+		<varlistentry>
+			<term>track</term>
+			<listitem><para>
+			<variablelist>
+			
+			<varlistentry>
+				<term>$fu</term>
+				<listitem><para>Load user attributes into from track. In this case
+				the second parameter is UID used to search attributes.
+				</para></listitem>
+			</varlistentry>
+			
+			<varlistentry>
+				<term>$tu</term>
+				<listitem><para>Load user attributes into to track. In this case
+				the second parameter is UID used to search attributes.
+				</para></listitem>
+			</varlistentry>
+
+			<varlistentry>
+				<term>$fr</term>
+				<listitem><para>Load uri attributes into from track. In this case
+				the second parameter is URI used to search attributes.
+				</para></listitem>
+			</varlistentry>
+
+			<varlistentry>
+				<term>$tr</term>
+				<listitem><para>Load uri attributes into to track. In this case
+				the second parameter is URI used to search attributes.
+				</para></listitem>
+			</varlistentry>
+
+			</variablelist>
+			</para></listitem>
+		</varlistentry>
+
+		<varlistentry>
+			<term>id</term>
+			<listitem><para>Identifier used for searching attributes. When
+			searching for user attributes it is UID, when searchnig uri
+			attributes it is URI.
+			</para></listitem>
+		</varlistentry>
+
+		</variablelist>
+	</para>
+	</section>
+
+	<section><title>load_extra_attrs (group_id, id)</title>
+	<para>
+		Loads 'extra attributes' stored by previous call to save_extra_attrs. 
+		<variablelist>
+			<varlistentry>
+				<term>group_id</term>
+				<listitem><para>Identifies attribute group,	see <xref
+				linkend="extra_attr_group"/>.
+				</para></listitem>
+			</varlistentry>
+			<varlistentry>
+				<term>id</term>
+				<listitem><para>Identifies attributes which should be loaded.
+				</para></listitem>
+			</varlistentry>
+		</variablelist>
+	</para>
+	</section>
+	
+	<section><title>save_extra_attrs (group_id, id)</title>
+	<para>
+		Saves 'extra attributes' flagged by group flag under given id. 
+		<variablelist>
+			<varlistentry>
+				<term>group_id</term>
+				<listitem><para>Identifies attribute group,	see <xref
+				linkend="extra_attr_group"/>.
+				</para></listitem>
+			</varlistentry>
+			<varlistentry>
+				<term>id</term>
+				<listitem><para>Identifier stored with flagged attributes.
+				</para></listitem>
+			</varlistentry>
+		</variablelist>
+	</para>
+	</section>
+
+	<section><title>remove_extra_attrs (group_id, id)</title>
+	<para>
+		Removes all extra attributes with given id.
+		<variablelist>
+			<varlistentry>
+				<term>group_id</term>
+				<listitem><para>Identifies attribute group,	see <xref
+				linkend="extra_attr_group"/>.
+				</para></listitem>
+			</varlistentry>
+			<varlistentry>
+				<term>id</term>
+				<listitem><para>Identifies attributes which should be removed.
+				</para></listitem>
+			</varlistentry>
+		</variablelist>
+	</para>
+	</section>
+
+	<section><title>lock_extra_attrs (group_id, id)</title>
+	<para>
+		Locks extra attributes. Currently locks whole attribute group (not only
+		id).
+		<variablelist>
+			<varlistentry>
+				<term>group_id</term>
+				<listitem><para>Identifies attribute group,	see <xref
+				linkend="extra_attr_group"/>.
+				</para></listitem>
+			</varlistentry>
+			<varlistentry>
+				<term>id</term>
+				<listitem><para>Identifies attributes which should be locked.
+				</para></listitem>
+			</varlistentry>
+		</variablelist>
+	</para>
+	</section>
+
+	<section><title>unlock_extra_attrs (group_id, id)</title>
+	<para>
+		Unlocks extra attributes. Currently unlocks whole attribute group (not only
+		id).
+		<variablelist>
+			<varlistentry>
+				<term>group_id</term>
+				<listitem><para>Identifies attribute group,	see <xref
+				linkend="extra_attr_group"/>.
+				</para></listitem>
+			</varlistentry>
+			<varlistentry>
+				<term>id</term>
+				<listitem><para>Identifies attributes which should be unlocked.
+				</para></listitem>
+			</varlistentry>
+		</variablelist>
+	</para>
+	</section>
+
+</section>

+ 232 - 0
modules_s/avp_db/doc/params.xml

@@ -0,0 +1,232 @@
+<?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_db.parameters" xmlns:xi="http://www.w3.org/2001/XInclude">
+	<!-- following is not needed in every section - it is needless here? 
+	<revhistory>
+	    <revision>
+		<revnumber>$Revision$</revnumber>
+		<date>$Date$</date>
+	    </revision>
+	</revhistory>
+    </sectioninfo>-->
+
+    <title>Parameters</title>
+
+    <section id="avp_db.db_url">
+	<title><varname>db_url</varname> (string)</title>
+	<para>
+	    The URL of the database to be used.
+	</para>
+	<para>
+	    Default value is "mysql://ser:heslo@localhost/ser".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>user_attrs_table</varname> (string)</title>
+	<para>
+	    Name of the table with user attributes.
+	</para>
+	<para>
+	    Default value is "user_attrs".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>uri_attrs_table</varname> (string)</title>
+	<para>
+	    Name of the table with uri attributes.
+	</para>
+	<para>
+	    Default value is "uri_attrs".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>uid_column</varname> (string)</title>
+	<para>
+	    Name of the column that stores UID in the user attributes table.
+	</para>
+	<para>
+	    Default value is "uid".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>username_column</varname> (string)</title>
+	<para>
+	    Name of the column containing the username of the subscriber in uri
+		attributes table.
+	</para>
+	<para>
+	    Default value is "username".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>did_column</varname> (string)</title>
+	<para>
+		Name of the column in uri attributes table containing the ID of domain
+		that the subscriber belongs to.
+	</para>
+	<para>
+	    Default value is "did".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>name</varname> (string)</title>
+	<para>
+	    The name of the column containing attribute names.
+	</para>
+	<para>
+	    Default value is "name".
+	</para>
+    </section>
+    
+    <section>
+	<title><varname>value_column</varname> (string)</title>
+	<para>
+	    The name of the column containing attribute values.
+	</para>
+	<para>
+	    Default value is "value".
+	</para>
+    </section>
+    
+	<section>
+	<title><varname>type_column</varname> (string)</title>
+	<para>
+	    The name of the column containing attribute value type.
+	</para>
+	<para>
+	    Default value is "type".
+	</para>
+    </section>
+
+    <section>
+	<title><varname>flags_column</varname> (string)</title>
+	<para>
+	    The name of the column containing attribute flags.
+	</para>
+	<para>
+	    Default value is "flags".
+	</para>
+    </section>
+    
+	<section>
+	<title><varname>scheme_column</varname> (string)</title>
+	<para>
+		The name of the column containing subscriber's scheme in uri attributes.
+	</para>
+	<para>
+	    Default value is "scheme".
+	</para>
+    </section>
+
+    <section id="extra_attr_group">
+	<title><varname>attr_group</varname> (string)</title>
+	<para>
+		'Extra attribute' group definition. It can be repeated to define more
+		attribute groups.
+	</para>
+	<para>
+		The group definition contains one or more assignments in the form
+		key=value. Possible keys are:
+		<variablelist>
+		
+		<varlistentry>
+			<term>id</term>
+			<listitem><para>Attribute group identifier. Must be set.
+			</para></listitem>
+		</varlistentry>
+
+		<varlistentry>
+			<term>table</term>
+			<listitem><para>Table name used for storing attributes from this
+			attribute group. Must be set.
+			</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>flag</term>
+			<listitem><para>Attribute flag name used to mark attributes in this
+			group. Must be set.</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>key_column</term>
+			<listitem><para>Column name holding key. Default value is
+			<quote>id</quote>.</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>name_column</term>
+			<listitem><para>Column name used for storing attribute name. Default
+			value is <quote>name</quote>.</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>value_column</term>			
+			<listitem><para>Column name used for storing attribute
+			value. Default value is <quote>value</quote>.</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>type_column</term>
+			<listitem><para>Column name used for storing attribute type. Default
+			value is <quote>type</quote>.</para></listitem>
+		</varlistentry>
+		<varlistentry>
+			<term>flags_column</term>			
+			<listitem><para>Column name used for storing attribute
+			flags. Default value is <quote>flags</quote>.</para></listitem>
+		</varlistentry>
+		</variablelist>
+	</para>
+	<para>
+	    None defined by default.
+	</para>
+	<para><example><title>attribute group definition</title>
+	<programlisting>modparam("avp_db", "attr_group", "id=dlg,flag=dialog_flag,table=dlg_attrs,key_column=dlg_id");</programlisting>
+	<para>Table used for these attributes:
+	<programlisting>mysql> describe dlg_attrs;  
++--------+------------------+------+-----+---------+-------+
+| Field  | Type             | Null | Key | Default | Extra |
++--------+------------------+------+-----+---------+-------+
+| dlg_id | varchar(256)     | NO   | MUL |         |       | 
+| name   | varchar(32)      | NO   |     |         |       | 
+| value  | varchar(255)     | YES  |     | NULL    |       | 
+| type   | int(11)          | NO   |     | 0       |       | 
+| flags  | int(10) unsigned | NO   |     | 0       |       | 
++--------+------------------+------+-----+---------+-------+
+5 rows in set (0.00 sec)
+	</programlisting>
+	</para>
+	<para>Setting flags from code (all attrs beginning with <quote>dlg_</quote>):
+	<programlisting>avpflags dialog_flag;
+...
+route {
+	...
+	setavpflag("$f./^dlg_/", "dialog_flag");
+	...
+}
+</programlisting>
+	</para>
+	</example>
+	</para>
+    </section>
+
+    <section id="auto_unlock_extra_attrs">
+	<title><varname>auto_unlock_extra_attrs</varname> (string)</title>
+	<para>
+		Determines the action when any of the 'extra attributes' lock is detected when
+		routing script execution was finished.
+
+		When the value of this parameter is zero (default) BUG level message is logged, 
+		but the lock is kept, so another process trying to obtain the lock might get stuck.
+
+		If the value is nonzero, DEBUG level message is sent to the log and all the locks are released.
+	</para>
+	<para>
+		Default value is 0.
+	</para>
+    </section>
+</section>

+ 540 - 0
modules_s/avp_db/extra_attrs.c

@@ -0,0 +1,540 @@
+#include "extra_attrs.h"
+#include "avp_db.h"
+#include "../../usr_avp.h"
+#include "../../sr_module.h"
+#include "../../ut.h"
+#include "../../mem/mem.h"
+#include "../../mem/shm_mem.h"
+#include "../../lock_ops.h"
+#include "../../script_cb.h"
+#include "../../hashes.h"
+
+#define set_str_val(f,s)	(f).v.lstr=(s); \
+	(f).flags = 0;
+
+#define set_str_val_ex(f,s)	if ((s).len) { (f).v.lstr=(s); (f).flags = 0; }  \
+	else (f).flags|=DB_NULL; 
+
+#define set_int_val(f,t)	(f).v.int4=t;\
+	(f).flags=0; 
+
+#define get_str_val(rvi,dst)	do{if(!(rvi.flags&DB_NULL)){dst=rvi.v.lstr;} else dst.len = 0;}while(0)
+#define get_int_val(rvi,dst)	do{if(!(rvi.flags&DB_NULL)){dst=rvi.v.int4;} else dst = 0;}while(0)
+
+typedef struct _registered_table_t {
+	char *id;
+	char *table_name;
+	
+	/* column names */
+	char *key_column;
+	char *name_column;
+	char *type_column;
+	char *value_column;
+	char *flags_column;
+
+	char *flag_name;
+
+	/* pregenerated queries */
+	db_cmd_t *query;
+	db_cmd_t *remove;
+	db_cmd_t *add;
+
+	avp_flags_t flag;
+
+	int group_mutex_idx;
+
+	struct _registered_table_t *next;
+
+	char buf[1]; /* buffer for strings allocated with the structure */
+} registered_table_t;
+
+static registered_table_t *tables = NULL;
+
+registered_table_t *find_registered_table(const char *id)
+{
+	registered_table_t *t = tables;
+	while (t) {
+		if (strcmp(t->id, id) == 0) return t;
+		t = t->next;
+	}
+	return NULL;
+}
+
+char *get_token(char *s, str *name, str *value)
+{
+	enum { reading_name, reading_value } state = reading_name;
+	/* returns 'token' which has the form name[=value][,]
+	 * replaces separators ,= by binary 0 to allow char* strings */
+
+	name->s = s;
+	name->len = 0;
+	value->s = NULL;
+	value->len = 0;
+
+	while (*s) {
+		switch (state) {
+			case reading_name: 
+				switch (*s) {
+					case '=':
+					case ':': 
+						state = reading_value;
+						value->s = s + 1;
+						*s = 0; /* replace separator */
+						break;
+					case ',': 
+						*s = 0; /* replace separator */
+						return s + 1;
+					default: name->len++;
+				}
+				break;
+			case reading_value:
+				if (*s == ',') {
+					*s = 0; /* replace separator */
+					return s + 1;
+				}
+				else value->len++;
+				break;
+		}
+		s++;
+	}
+	return NULL; /* everything read */
+}
+
+static int cmp_s(str *a, str *b)
+{
+	int i;
+	/* Warning: none string can be NULL! */
+	if (a->len != b->len) return -1;
+	if (!a->len) return 0; /* equal - empty */
+	for (i = 0; i < a->len; i++) 
+		if (a->s[i] != b->s[i]) return 1;
+	return 0;
+}
+
+static inline void cpy(char *dst, str *s)
+{
+	memcpy(dst, s->s, s->len);
+	dst[s->len] = 0;
+}
+
+/* adds new 'extra attribute group' (it adds new table for it) */
+int declare_attr_group(modparam_t type, char* _param)
+{
+	registered_table_t *rt;
+	str name, value;
+	str param;
+	char *p;
+	
+	static str table = STR_STATIC_INIT("table");
+	static str flag = STR_STATIC_INIT("flag");
+	static str id = STR_STATIC_INIT("id");
+	static str key_column = STR_STATIC_INIT("key_column");
+	static str name_column = STR_STATIC_INIT("name_column");
+	static str value_column = STR_STATIC_INIT("value_column");
+	static str type_column = STR_STATIC_INIT("type_column");
+	static str flags_column = STR_STATIC_INIT("flags_column");
+	
+	if (!(type & PARAM_STR)) {
+		ERR("Invalid parameter type\n");
+		return -1;
+	}
+	if (!_param) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+	param = *((str*)_param);
+	ERR("group def: %.*s\n", param.len, param.s);
+	
+	rt = pkg_malloc(param.len + sizeof(*rt) + 1);
+	if (!rt) {
+		ERR("can't allocate PKG memory\n");
+		return -1;
+	}
+	memset(rt, 0, sizeof(*rt));
+	cpy(rt->buf, &param);
+
+	/* default column names */
+	rt->key_column = "id";
+	rt->name_column = "name";
+	rt->type_column = "type";
+	rt->value_column = "value";
+	rt->flags_column = "flags";
+
+	/* parse the string */
+	p = rt->buf;
+	do {
+		p = get_token(p, &name, &value);
+		if (cmp_s(&name, &table) == 0) rt->table_name = value.s;
+		else if (cmp_s(&name, &flag) == 0) rt->flag_name = value.s;
+		else if (cmp_s(&name, &id) == 0) rt->id = value.s;
+		else if (cmp_s(&name, &key_column) == 0) rt->key_column = value.s;
+		else if (cmp_s(&name, &name_column) == 0) rt->name_column = value.s;
+		else if (cmp_s(&name, &type_column) == 0) rt->type_column = value.s;
+		else if (cmp_s(&name, &value_column) == 0) rt->value_column = value.s;
+		else if (cmp_s(&name, &flags_column) == 0) rt->flags_column = value.s;
+	} while (p);
+	
+	if ((!rt->id) || (!rt->flag_name)) {
+		ERR("at least attribute group ID and flags must ve given\n");
+		return -1;
+	}
+	/* insert new element into registered tables */
+
+	rt->flag = register_avpflag(rt->flag_name);
+	if (!rt->flag) {
+		ERR("can't register AVP flag: %s\n", rt->flag_name);
+		pkg_free(rt);
+		return -1;
+	}
+	
+	/* append to the beggining - it doesn't depend on the order */
+	rt->next = tables;
+	tables = rt;
+
+	return 0;
+}
+
+/** Initialize all queries needed by 'extra attributes' for given table. 
+ * Variable default_res holds columns which can used by read_attrs. */
+static int init_queries(db_ctx_t *ctx, registered_table_t *t)
+{
+	db_fld_t match[] = {
+		{ .name = t->key_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = NULL }
+	};
+	db_fld_t query_res[] = {
+		/* Warning: be careful here - the query must have the same result
+		 * as query for user/uri AVPs to be readable by read_attrs */
+		{ .name = t->name_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = t->type_column, .type = DB_INT, .op = DB_EQ },
+		{ .name = t->value_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = t->flags_column, .type = DB_BITMAP, .op = DB_EQ },
+		{ .name = NULL }
+	};
+	db_fld_t add_values[] = {
+		{ .name = t->key_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = t->name_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = t->type_column, .type = DB_INT, .op = DB_EQ },
+		{ .name = t->value_column, .type = DB_STR, .op = DB_EQ },
+		{ .name = t->flags_column, .type = DB_BITMAP, .op = DB_EQ },
+		{ .name = NULL }
+	};
+
+	t->query = db_cmd(DB_GET, ctx, t->table_name, query_res, match, NULL);
+	t->remove = db_cmd(DB_DEL, ctx, t->table_name, NULL, match, NULL);
+	t->add = db_cmd(DB_PUT, ctx, t->table_name, NULL, NULL, add_values);
+
+	if (t->query && t->remove && t->add) return 0;
+	else return -1; /* not all queries were initialized */
+}
+
+int init_extra_avp_queries(db_ctx_t *ctx)
+{
+	registered_table_t *t = tables;
+	while (t) {
+		if (init_queries(ctx, t) < 0)  return -1;
+		t = t->next;
+	}
+	return 0;
+}
+
+static void get_avp_value_ex(avp_t *avp, str *dst, int *type) {
+	avp_value_t val;
+
+	/* Warning! it uses static buffer from int2str !!! */
+
+	get_avp_val(avp, &val);
+	if (avp->flags & AVP_VAL_STR) {
+		*dst = val.s;
+		*type = AVP_VAL_STR;
+	}
+	else { /* probably (!) number */
+		dst->s = int2str(val.n, &dst->len);
+		*type = 0;
+	}
+}
+
+/** Saves attribute into DB with given ID.  The ID must not be NULL. The
+ * use_table must be called outside of this function (from interface
+ * functions) */
+static inline int save_avp(registered_table_t *t, avp_t *avp, str *id) /* id MUST NOT be NULL */
+{
+	str *s, v;
+	int type;
+	static str empty = STR_STATIC_INIT("");
+	
+	set_str_val(t->add->vals[0], *id);
+	
+	s = get_avp_name(avp);
+	if (!s) s = &empty;
+	set_str_val(t->add->vals[1], *s);
+	
+	get_avp_value_ex(avp, &v, &type);
+	set_int_val(t->add->vals[2], type);
+	set_str_val(t->add->vals[3], v);
+	set_int_val(t->add->vals[4], avp->flags & (AVP_CLASS_ALL | AVP_TRACK_ALL | AVP_NAME_STR | AVP_VAL_STR));
+	
+	if (db_exec(NULL, t->add) < 0) {
+		ERR("Can't insert record into DB\n");
+		return -1;
+	}
+	return 0;
+}
+
+/** Loads all attributes with given ID.  The ID must not be NULL. */
+static int read_avps(db_res_t *res, avp_flags_t flag) /* id must not be NULL */
+{
+	db_rec_t *row;
+
+	row = db_first(res);
+	while (row) {
+		int flags = 0;
+		int type = 0;
+		str value = STR_NULL;
+		avp_value_t val;
+		avp_name_t name;
+		
+		get_str_val(row->fld[0], name.s);
+		get_int_val(row->fld[1], type);
+		get_str_val(row->fld[2], value);
+		get_int_val(row->fld[3], flags);
+
+		if (flags & SRDB_LOAD_SER) {
+			if (type == AVP_VAL_STR) val.s = value;
+			else str2int(&value, (unsigned int *)&val.n); /* FIXME */
+
+			flags |= flag;
+
+			/* FIXME: avps probably should be removed before they are added,
+			 * but this should be done in add_avp and not here! */
+			add_avp(flags, name, val);
+		}
+		row = db_next(res);
+	}
+	return 0;
+}
+
+/** Removes all attributes with given ID.  The ID must not be NULL.  */
+static inline int remove_all_avps(registered_table_t *t, str *id)
+{
+	set_str_val(t->remove->match[0], *id);
+	if (db_exec(NULL, t->remove) < 0) {
+		ERR("can't remove attrs\n");
+		return -1;
+	}
+	return 0;
+}
+
+/* ----- interface functions ----- */
+
+int load_extra_attrs(struct sip_msg* msg, char* _table, char* _id)
+{
+	registered_table_t *t;
+	db_res_t *res = NULL;
+	str id;
+
+	t = (registered_table_t *)_table;
+	if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+	
+	set_str_val(t->query->match[0], id);
+	if (db_exec(&res, t->query) < 0) {
+		ERR("DB query failed\n");
+		return -1;
+	}
+	if (res) {
+		read_avps(res, t->flag);
+		db_res_free(res);
+	}
+
+	return 1;
+}
+
+int remove_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
+{
+	str id;
+	registered_table_t *t;
+
+	t = (registered_table_t *)_table;
+
+	if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+	remove_all_avps(t, &id);
+
+	return 1;
+}
+
+int save_extra_attrs(struct sip_msg* msg, char* _table, char *_id)
+{
+	str id;
+	int i;
+	struct usr_avp *avp;
+	static unsigned short lists[] = {
+		AVP_CLASS_USER | AVP_TRACK_FROM, 
+		AVP_CLASS_USER | AVP_TRACK_TO, 
+		AVP_CLASS_URI | AVP_TRACK_FROM, 
+		AVP_CLASS_URI | AVP_TRACK_TO, 
+		0
+	};
+	registered_table_t *t;
+
+	t = (registered_table_t *)_table;
+
+	if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	/* delete all attrs under given id */
+	remove_all_avps(t, &id);
+
+	/* save all attrs flagged with flag under id */	
+	for (i = 0; lists[i]; i++) {
+		for (avp = get_avp_list(lists[i]); avp; avp = avp->next) {
+			if ((avp->flags & t->flag) != 0) save_avp(t, avp, &id);
+		}
+	}
+	return 1;
+}
+
+int extra_attrs_fixup(void** param, int param_no)
+{
+	registered_table_t *t;
+
+	switch (param_no) {
+		case 1: /* try to find registered table, error if not found */
+				t = find_registered_table(*param);
+				if (!t) {
+					ERR("can't find attribute group with id: %s\n", (char*)*param);
+					return -1;
+				}
+				*param = (void*)t;
+				break;
+		case 2: return fixup_var_str_2(param, param_no);
+	}
+	return 0;
+}
+
+/******* locking *******/ 
+
+#define LOCK_CNT	32
+
+gen_lock_t *locks = NULL; /* set of mutexes allocated in shared memory */
+int lock_counters[LOCK_CNT]; /* set of counters (each proces has its own counters) */
+
+static int avpdb_post_script_cb(struct sip_msg *msg, void *param) {
+	int i;
+
+	for (i=0; i<LOCK_CNT; i++) {
+		if (lock_counters[i] > 0) {
+			if (auto_unlock) {
+				DEBUG("post script auto unlock extra attrs <%d>\n", i);
+				lock_release(&locks[i]);
+				lock_counters[i]=0;
+			} else {
+				BUG("script writer didn't unlock extra attrs !!!\n");
+				return 1;
+			}
+		}
+	}
+	return 1;
+}
+
+int init_extra_avp_locks()
+{
+	int i;
+
+	/* zero all 'lock counters' */
+	memset(lock_counters, 0, sizeof(lock_counters));
+
+	locks = shm_malloc(sizeof(gen_lock_t) * LOCK_CNT);
+	if (!locks) {
+		ERR("can't allocate mutexes\n");
+		return -1;
+	}
+	for (i = 0; i < LOCK_CNT; i++) {
+		lock_init(&locks[i]);
+	}
+
+	/* initializes mutexes for extra AVPs */
+	registered_table_t *t = tables;
+	i = 0;
+	while (t) {
+		t->group_mutex_idx = get_hash1_raw(t->table_name, strlen(t->table_name)) % LOCK_CNT;
+		t = t->next;
+	}
+
+	register_script_cb(avpdb_post_script_cb, REQ_TYPE_CB | RPL_TYPE_CB| POST_SCRIPT_CB, 0);
+
+	return 0;
+}
+
+static inline int find_mutex(registered_table_t *t, str *id)
+{
+	/* hash(table_name) + hash(id) */
+	return ((t->group_mutex_idx + get_hash1_raw(id->s, id->len)) % LOCK_CNT);
+}
+
+int lock_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
+{
+	str id;
+	registered_table_t *t;
+	int mutex_idx;
+
+	t = (registered_table_t *)_table;
+	if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	/* find right mutex according to id/table */
+	mutex_idx = find_mutex(t, &id);
+
+	if (lock_counters[mutex_idx] > 0) {
+		/* mutex is already locked by this process */
+		lock_counters[mutex_idx]++;
+	}
+	else {
+		/* the mutex was not locked => lock it and set counter */
+		lock_get(&locks[mutex_idx]);
+		lock_counters[mutex_idx] = 1;
+	}
+
+	return 1;
+}
+
+int unlock_extra_attrs(struct sip_msg* msg, char *_table, char* _id)
+{
+	str id;
+	registered_table_t *t;
+	int mutex_idx;
+
+	t = (registered_table_t *)_table;
+	if ((!t) || (get_str_fparam(&id, msg, (fparam_t*)_id) < 0)) {
+		ERR("invalid parameter value\n");
+		return -1;
+	}
+
+	/* find right mutex according to id/table */
+	mutex_idx = find_mutex(t, &id);
+
+	if (lock_counters[mutex_idx] > 1) {
+		/* mutex is locked more times by this process */
+		lock_counters[mutex_idx]--;
+	}
+	else if (lock_counters[mutex_idx] == 1) {
+		/* the mutex is locked once => unlock it and reset counter */
+		lock_release(&locks[mutex_idx]);
+		lock_counters[mutex_idx] = 0;
+	}
+	else {
+		BUG("trying to unlock without lock group=\"%s\" id=\"%.*s\"\n", t->id, id.len, id.s);
+	}
+
+	return 1;
+}
+

+ 23 - 0
modules_s/avp_db/extra_attrs.h

@@ -0,0 +1,23 @@
+#ifndef __EXTRA_ATTRS_H
+#define __EXTRA_ATTRS_H
+
+
+#include "../../parser/msg_parser.h"
+#include "../../lib/srdb2/db.h"
+#include "../../sr_module.h"
+
+int declare_attr_group(modparam_t type, char* param);
+
+int init_extra_avp_queries(db_ctx_t *ctx);
+int init_extra_avp_locks();
+
+int load_extra_attrs(struct sip_msg* msg, char *_table, char* _id);
+int save_extra_attrs(struct sip_msg* msg, char* _table, char *_id);
+int remove_extra_attrs(struct sip_msg* msg, char *_table, char* _id);
+
+int extra_attrs_fixup(void** param, int param_no);
+
+int lock_extra_attrs(struct sip_msg* msg, char *_table, char* _id);
+int unlock_extra_attrs(struct sip_msg* msg, char *_table, char* _id);
+
+#endif

+ 18 - 0
modules_s/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

+ 115 - 0
modules_s/avp_radius/README

@@ -0,0 +1,115 @@
+
+avp_radius Module
+
+Juha Heinanen
+
+  Copyright @ 2004 Juha Heinanen
+     _________________________________________________________
+
+   Table of Contents
+   1. User's Guide
+
+        1.1. Overview
+        1.2. Dependencies
+        1.3. Exported Parameters
+
+              1.3.1. radius_config (string)
+              1.3.2. caller_service_type (string)
+              1.3.3. callee_service_type (string)
+
+        1.4. Exported Functions
+
+              1.4.1. avp_load_radius()
+
+   List of Examples
+   1-1. radius_config parameter usage
+   1-2. caller_service_type usage
+   1-3. callee_service_type usage
+   1-4. avp_load_radius usage
+     _________________________________________________________
+
+Chapter 1. User's Guide
+
+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. Dependencies
+
+  None.
+
+1.3. Exported 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-1. radius_config parameter usage
+modparam("avp_radius", "radius_config", "/etc/radiusclient.conf")
+     _________________________________________________________
+
+1.3.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 1-2. caller_service_type usage
+modparam("avp_radius", "caller_service_type", 18)
+     _________________________________________________________
+
+1.3.2. callee_service_type (integer)
+
+   This is the value of the Service-Type radius attribute to be
+   used, when callee's attributes are loaded.
+
+   Default value is dictionary value of "SIP-Callee-AVPs" Service-Type.
+
+   Example 1-3. callee_service_type usage
+modparam("avp_radius", "callee_service_type", 19)
+     _________________________________________________________
+
+1.4. Exported Functions
+
+1.4.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 1-4. avp_load_radius usage
+avp_load_radius("callee");
+     _________________________________________________________

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно