Browse code

merged from 'master'.

Raphael Coeffic authored on 04/01/2011 11:36:19
Showing 65 changed files
... ...
@@ -44,6 +44,7 @@ ENDIF(SIP_FOUND)
44 44
 ENDIF(PYTHONLIBS_FOUND)
45 45
 ADD_SUBDIRECTORY(reg_agent)
46 46
 ADD_SUBDIRECTORY(registrar_client)
47
+ADD_SUBDIRECTORY(sbc)
47 48
 ADD_SUBDIRECTORY(sst_b2b)
48 49
 ADD_SUBDIRECTORY(sw_prepaid_sip)
49 50
 #IF(PYTHONLIBS_FOUND)
... ...
@@ -149,10 +149,6 @@ void CallTimerDialog::onInvite(const AmSipRequest& req)
149 149
   removeHeader(invite_req.hdrs, "P-App-Param");
150 150
   removeHeader(invite_req.hdrs, "P-App-Name");
151 151
 
152
-  //dlg.updateStatus(invite_req);
153
-  recvd_req.insert(std::make_pair(invite_req.cseq,invite_req));
154
-  
155
-  set_sip_relay_only(true);
156 152
   connectCallee(invite_req.to, invite_req.r_uri, true);
157 153
 }
158 154
 
... ...
@@ -167,16 +167,17 @@ CONST_ACTION_2P(DLGConnectCalleeRelayedAction,',', false);
167 167
 EXEC_ACTION_START(DLGConnectCalleeRelayedAction) {  
168 168
   string remote_party = resolveVars(par1, sess, sc_sess, event_params);
169 169
   string remote_uri = resolveVars(par2, sess, sc_sess, event_params);
170
-  if (sc_sess->last_req.get()) {
171
-    sc_sess->B2BaddReceivedRequest(*sc_sess->last_req.get());
172
-  } else {
173
-    WARN("internal error: initial INVITE request missing.\n");
174
-  }
175
-  AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
176
-  if (b2b_sess) 
177
-    b2b_sess->set_sip_relay_only(true);
178
-  else 
179
-    ERROR("getting B2B session.\n");
170
+
171
+  // if (sc_sess->last_req.get()) {
172
+  //   sc_sess->B2BaddReceivedRequest(*sc_sess->last_req.get());
173
+  // } else {
174
+  //   WARN("internal error: initial INVITE request missing.\n");
175
+  // }
176
+  // AmB2BSession* b2b_sess = dynamic_cast<AmB2BSession*>(sess);
177
+  // if (b2b_sess) 
178
+  //   b2b_sess->set_sip_relay_only(true);
179
+  // else 
180
+  //   ERROR("getting B2B session.\n");
180 181
 
181 182
   sc_sess->B2BconnectCallee(remote_party, remote_uri, true);
182 183
 } EXEC_ACTION_END;
... ...
@@ -39,6 +39,8 @@ CCAcc::~CCAcc() { }
39 39
 
40 40
 void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret)
41 41
 {
42
+  DBG("cc_acc: %s(%s)\n", method.c_str(), AmArg::print(args).c_str());
43
+
42 44
     if(method == "getCredit"){
43 45
       assertArgCStr(args.get(0));
44 46
       ret.push(getCredit(args.get(0).asCStr()));
... ...
@@ -57,11 +59,14 @@ void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret)
57 59
       assertArgInt(args.get(1));
58 60
       ret.push(setCredit(args.get(0).asCStr(),
59 61
 			 args.get(1).asInt()));	
62
+    } else if(method == "connectCall"){
63
+      // call is connected
60 64
     } else if(method == "_list"){
61 65
       ret.push("getCredit");
62 66
       ret.push("subtractCredit");
63 67
       ret.push("setCredit");
64 68
       ret.push("addCredit");
69
+      ret.push("connectCall");
65 70
     }
66 71
     else
67 72
 	throw AmDynInvoke::NotImplemented(method);
... ...
@@ -51,9 +51,12 @@ void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret)
51 51
      } else if(method == "subtractCredit"){
52 52
        ret.push(subtractCredit(args.get(0).asCStr(),
53 53
 			      args.get(1).asInt()));	
54
+     } else if(method == "connectCall"){
55
+       //
54 56
      } else if(method == "_list"){
55 57
        ret.push("getCredit");
56 58
        ret.push("subtractCredit");
59
+       ret.push("connectCall");
57 60
      }
58 61
      else
59 62
 	throw AmDynInvoke::NotImplemented(method);
60 63
new file mode 100644
... ...
@@ -0,0 +1,9 @@
1
+set (sbc_SRCS
2
+SBC.cpp
3
+HeaderFilter.cpp
4
+ParamReplacer.cpp
5
+SDPFilter.cpp
6
+)
7
+
8
+SET(sems_module_name sbc)
9
+INCLUDE(${CMAKE_SOURCE_DIR}/cmake/module.rules.txt)
0 10
new file mode 100644
... ...
@@ -0,0 +1,178 @@
1
+/*
2
+ * Copyright (C) 2010 Stefan Sayer
3
+ *
4
+ * This file is part of SEMS, a free SIP media server.
5
+ *
6
+ * SEMS is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * For a license to use the SEMS software under conditions
12
+ * other than those described here, or to purchase support for this
13
+ * software, please contact iptel.org by e-mail at the following addresses:
14
+ *    info@iptel.org
15
+ *
16
+ * SEMS is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
+ */
25
+
26
+#include "HeaderFilter.h"
27
+#include "sip/parse_common.h"
28
+#include "log.h"
29
+const char* FilterType2String(FilterType ft) {
30
+    switch(ft) {
31
+    case Transparent: return "transparent";
32
+    case Whitelist: return "whitelist";
33
+    case Blacklist: return "blacklist";
34
+    default: return "unknown";
35
+    };
36
+}
37
+
38
+int skip_header(const std::string& hdr, size_t start_pos, 
39
+		 size_t& name_end, size_t& val_begin,
40
+		 size_t& val_end, size_t& hdr_end) {
41
+    // adapted from sip/parse_header.cpp
42
+
43
+    name_end = val_begin = val_end = start_pos;
44
+    hdr_end = hdr.length();
45
+
46
+    //
47
+    // Header states
48
+    //
49
+    enum {
50
+	H_NAME=0,
51
+	H_HCOLON,
52
+	H_VALUE_SWS,
53
+	H_VALUE,
54
+    };
55
+
56
+    int st = H_NAME;
57
+    int saved_st = 0;
58
+
59
+    
60
+    size_t p = start_pos;
61
+    for(;p<hdr.length() && st != ST_LF && st != ST_CRLF;p++){
62
+
63
+	switch(st){
64
+
65
+	case H_NAME:
66
+	    switch(hdr[p]){
67
+
68
+	    case_CR_LF;
69
+
70
+	    case HCOLON:
71
+		st = H_VALUE_SWS;
72
+		name_end = p;
73
+		break;
74
+
75
+	    case SP:
76
+	    case HTAB:
77
+		st = H_HCOLON;
78
+		name_end = p;
79
+		break;
80
+	    }
81
+	    break;
82
+
83
+	case H_VALUE_SWS:
84
+	    switch(hdr[p]){
85
+
86
+		case_CR_LF;
87
+
88
+	    case SP:
89
+	    case HTAB:
90
+		break;
91
+
92
+	    default:
93
+		st = H_VALUE;
94
+		val_begin = p;
95
+		break;
96
+		
97
+	    };
98
+	    break;
99
+
100
+	case H_VALUE:
101
+	    switch(hdr[p]){
102
+		case_CR_LF;
103
+	    };
104
+	    if (st==ST_CR || st==ST_LF)
105
+		val_end = p;
106
+	    break;
107
+
108
+	case H_HCOLON:
109
+	    switch(hdr[p]){
110
+	    case HCOLON:
111
+		st = H_VALUE_SWS;
112
+		val_begin = p;
113
+		break;
114
+
115
+	    case SP:
116
+	    case HTAB:
117
+		break;
118
+
119
+	    default:
120
+		DBG("Missing ':' after header name\n");
121
+		return MALFORMED_SIP_MSG;
122
+	    }
123
+	    break;
124
+
125
+	case_ST_CR(hdr[p]);
126
+
127
+	    st = saved_st;
128
+	    hdr_end = p;
129
+	    break;
130
+	}
131
+    }
132
+    
133
+    hdr_end = p;
134
+    if (p==hdr.length() && st==H_VALUE) {
135
+	val_end = p;	
136
+    }
137
+    
138
+    return 0;
139
+}
140
+
141
+int inplaceHeaderFilter(string& hdrs, const set<string>& headerfilter_list, FilterType f_type) {
142
+    if (!hdrs.length() || f_type == Transparent)
143
+	return 0;
144
+
145
+    int res = 0;
146
+    size_t start_pos = 0;
147
+    while (start_pos<hdrs.length()) {
148
+	size_t name_end, val_begin, val_end, hdr_end;
149
+	if ((res = skip_header(hdrs, start_pos, name_end, val_begin,
150
+			       val_end, hdr_end)) != 0) {
151
+	    return res;
152
+	}
153
+	string hdr_name = hdrs.substr(start_pos, name_end-start_pos);
154
+	bool erase = false;
155
+	if (f_type == Whitelist) {
156
+	    erase = headerfilter_list.find(hdr_name)==headerfilter_list.end();
157
+	} else if (f_type == Blacklist) {
158
+	    erase = headerfilter_list.find(hdr_name)!=headerfilter_list.end();
159
+	}
160
+	if (erase) {
161
+	    DBG("erasing header '%s'\n", hdr_name.c_str());
162
+	    hdrs.erase(start_pos, hdr_end-start_pos);
163
+	} else {
164
+	    start_pos = hdr_end;
165
+	}
166
+    }
167
+
168
+    // todo: multi-line header support
169
+
170
+    return res;
171
+}
172
+
173
+/** EMACS **
174
+ * Local variables:
175
+ * mode: c++
176
+ * c-basic-offset: 4
177
+ * End:
178
+ */
0 179
new file mode 100644
... ...
@@ -0,0 +1,41 @@
1
+/*
2
+ * Copyright (C) 2010 Stefan Sayer
3
+ *
4
+ * This file is part of SEMS, a free SIP media server.
5
+ *
6
+ * SEMS is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * For a license to use the SEMS software under conditions
12
+ * other than those described here, or to purchase support for this
13
+ * software, please contact iptel.org by e-mail at the following addresses:
14
+ *    info@iptel.org
15
+ *
16
+ * SEMS is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
+ */
25
+#ifndef _HeaderFilter_h_
26
+#define _HeaderFilter_h_
27
+
28
+#include <string>
29
+using std::string;
30
+
31
+#include <set>
32
+using std::set;
33
+
34
+enum FilterType { Transparent=0, Whitelist, Blacklist };
35
+const char* FilterType2String(FilterType ft);
36
+int skip_header(const std::string& hdr, size_t start_pos, 
37
+		 size_t& name_end, size_t& val_begin, size_t& val_end, size_t& hdr_end);
38
+int inplaceHeaderFilter(string& hdrs, const set<string>& headerfilter_list, 
39
+			FilterType f_type);
40
+
41
+#endif
0 42
new file mode 100644
... ...
@@ -0,0 +1,7 @@
1
+plug_in_name = sbc
2
+
3
+module_ldflags =
4
+module_cflags  = -DMOD_NAME=\"$(plug_in_name)\"
5
+
6
+COREPATH ?= ../../core
7
+include $(COREPATH)/plug-in/Makefile.app_module
0 8
new file mode 100644
... ...
@@ -0,0 +1,240 @@
1
+/*
2
+ * Copyright (C) 2010 Stefan Sayer
3
+ *
4
+ * This file is part of SEMS, a free SIP media server.
5
+ *
6
+ * SEMS is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * For a license to use the SEMS software under conditions
12
+ * other than those described here, or to purchase support for this
13
+ * software, please contact iptel.org by e-mail at the following addresses:
14
+ *    info@iptel.org
15
+ *
16
+ * SEMS is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
+ */
25
+
26
+#include "ParamReplacer.h"
27
+
28
+#include "log.h"
29
+#include "AmSipHeaders.h"
30
+#include "AmUtils.h"
31
+
32
+void replaceParsedParam(const string& s, size_t p,
33
+			AmUriParser& parsed, string& res) {
34
+  switch (s[p+1]) {
35
+  case 'u': { // URI
36
+    res+=parsed.uri_user+"@"+parsed.uri_host;
37
+    if (!parsed.uri_port.empty())
38
+      res+=":"+parsed.uri_port;
39
+  } break;
40
+  case 'U': res+=parsed.uri_user; break; // User
41
+  case 'd': { // domain
42
+    res+=parsed.uri_host;
43
+    if (!parsed.uri_port.empty())
44
+      res+=":"+parsed.uri_port;
45
+  } break;
46
+  case 'h': res+=parsed.uri_host; break; // host
47
+  case 'p': res+=parsed.uri_port; break; // port
48
+  case 'H': res+=parsed.uri_headers; break; // Headers
49
+  case 'P': res+=parsed.uri_param; break; // Params
50
+  default: WARN("unknown replace pattern $%c%c\n",
51
+		s[p], s[p+1]); break;
52
+  };
53
+}
54
+
55
+string replaceParameters(const string& s,
56
+			 const char* r_type,
57
+			 const AmSipRequest& req,
58
+			 const string& app_param,
59
+			 AmUriParser& ruri_parser, AmUriParser& from_parser,
60
+			 AmUriParser& to_parser) {
61
+  string res;
62
+  bool is_replaced = false;
63
+  size_t p = 0;
64
+  char last_char=' ';
65
+  
66
+  while (p<s.length()) {
67
+    size_t skip_chars = 1;
68
+    if (last_char=='\\') {
69
+      res += s[p];
70
+      is_replaced = true;
71
+    } else if (s[p]=='\\') {
72
+      if (p==s.length()-1)
73
+      res += s[p];
74
+    } else if ((s[p]=='$') && (s.length() >= p+1)) {
75
+      is_replaced = true;
76
+      p++;
77
+      switch (s[p]) {
78
+      case 'f': { // from
79
+	if ((s.length() == p+1) || (s[p+1] == '.')) {
80
+	  res += req.from;
81
+	  break;
82
+	}
83
+	  
84
+	if (from_parser.uri.empty()) {
85
+	  from_parser.uri = req.from;
86
+	  if (!from_parser.parse_uri()) {
87
+	    WARN("Error parsing From URI '%s'\n", req.from.c_str());
88
+	    break;
89
+	  }
90
+	}
91
+
92
+	replaceParsedParam(s, p, from_parser, res);
93
+
94
+      }; break;
95
+
96
+      case 't': { // to
97
+	if ((s.length() == p+1) || (s[p+1] == '.')) {
98
+	  res += req.to;
99
+	  break;
100
+	}
101
+
102
+	if (to_parser.uri.empty()) {
103
+	  to_parser.uri = req.to;
104
+	  if (!to_parser.parse_uri()) {
105
+	    WARN("Error parsing To URI '%s'\n", req.to.c_str());
106
+	    break;
107
+	  }
108
+	}
109
+
110
+	replaceParsedParam(s, p, to_parser, res);
111
+
112
+      }; break;
113
+
114
+      case 'r': { // r-uri
115
+	if ((s.length() == p+1) || (s[p+1] == '.')) {
116
+	  res += req.r_uri;
117
+	  break;
118
+	}
119
+	
120
+	if (ruri_parser.uri.empty()) {
121
+	  ruri_parser.uri = req.r_uri;
122
+	  if (!ruri_parser.parse_uri()) {
123
+	    WARN("Error parsing R-URI '%s'\n", req.r_uri.c_str());
124
+	    break;
125
+	  }
126
+	}
127
+	replaceParsedParam(s, p, ruri_parser, res);
128
+      }; break;
129
+
130
+#define case_HDR(pv_char, pv_name, hdr_name)				\
131
+	case pv_char: {							\
132
+	  AmUriParser uri_parser;					\
133
+	  uri_parser.uri = getHeader(req.hdrs, hdr_name);		\
134
+	  if ((s.length() == p+1) || (s[p+1] == '.')) {			\
135
+	    res += uri_parser.uri;					\
136
+	    break;							\
137
+	  }								\
138
+									\
139
+	  if (!uri_parser.parse_uri()) {				\
140
+	    WARN("Error parsing " pv_name " URI '%s'\n", uri_parser.uri.c_str()); \
141
+	    break;							\
142
+	  }								\
143
+	  if (s[p+1] == 'i') {						\
144
+	    res+=uri_parser.uri_user+"@"+uri_parser.uri_host;		\
145
+	    if (!uri_parser.uri_port.empty())				\
146
+	      res+=":"+uri_parser.uri_port;				\
147
+	  } else {							\
148
+	    replaceParsedParam(s, p, uri_parser, res);			\
149
+	  }								\
150
+	}; break;							
151
+
152
+	case_HDR('a', "PAI", SIP_HDR_P_ASSERTED_IDENTITY);  // P-Asserted-Identity
153
+	case_HDR('p', "PPI", SIP_HDR_P_PREFERRED_IDENTITY); // P-Preferred-Identity
154
+
155
+      case 'P': { // app-params
156
+	if (s[p+1] != '(') {
157
+	  WARN("Error parsing P param replacement (missing '(')\n");
158
+	  break;
159
+	}
160
+	if (s.length()<p+3) {
161
+	  WARN("Error parsing P param replacement (short string)\n");
162
+	  break;
163
+	}
164
+
165
+	size_t skip_p = p+2;
166
+	for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
167
+	if (skip_p==s.length()) {
168
+	  WARN("Error parsing P param replacement (unclosed brackets)\n");
169
+	  break;
170
+	}
171
+	string param_name = s.substr(p+2, skip_p-p-2);
172
+	// DBG("param_name = '%s' (skip-p - p = %d)\n", param_name.c_str(), skip_p-p);
173
+	res += get_header_keyvalue(app_param, param_name);
174
+	skip_chars = skip_p-p;
175
+      } break;
176
+
177
+      case 'H': { // header
178
+	size_t name_offset = 2;
179
+	if (s[p+1] != '(') {
180
+	  if (s[p+2] != '(') {
181
+	    WARN("Error parsing H header replacement (missing '(')\n");
182
+	    break;
183
+	  }
184
+	  name_offset = 3;
185
+	}
186
+	if (s.length()<name_offset+1) {
187
+	  WARN("Error parsing H header replacement (short string)\n");
188
+	  break;
189
+	}
190
+
191
+	size_t skip_p = p+name_offset;
192
+	for (;skip_p<s.length() && s[skip_p] != ')';skip_p++) { }
193
+	if (skip_p==s.length()) {
194
+	  WARN("Error parsing H header replacement (unclosed brackets)\n");
195
+	  break;
196
+	}
197
+	string hdr_name = s.substr(p+name_offset, skip_p-p-name_offset);
198
+	// DBG("param_name = '%s' (skip-p - p = %d)\n", param_name.c_str(), skip_p-p);
199
+	if (name_offset == 2) {
200
+	  // full header
201
+	  res += getHeader(req.hdrs, hdr_name);
202
+	} else {
203
+	  // parse URI and use component
204
+	  AmUriParser uri_parser;
205
+	  uri_parser.uri = getHeader(req.hdrs, hdr_name);
206
+	  if ((s[p+1] == '.')) {
207
+	    res += uri_parser.uri;
208
+	    break;
209
+	  }
210
+
211
+	  if (!uri_parser.parse_uri()) {
212
+	    WARN("Error parsing header %s URI '%s'\n",
213
+		 hdr_name.c_str(), uri_parser.uri.c_str());
214
+	    break;
215
+	  }
216
+	  replaceParsedParam(s, p, uri_parser, res);
217
+	}
218
+	skip_chars = skip_p-p;
219
+      } break;
220
+
221
+      default: {
222
+	WARN("unknown replace pattern $%c%c\n",
223
+	     s[p], s[p+1]);
224
+      }; break;
225
+      };
226
+
227
+      p+=skip_chars; // skip $.X      
228
+    } else {
229
+      res += s[p];
230
+    }
231
+
232
+    last_char = s[p];
233
+    p++;
234
+  }
235
+
236
+  if (is_replaced) {
237
+    DBG("%s pattern replace: '%s' -> '%s'\n", r_type, s.c_str(), res.c_str());
238
+  }
239
+  return res;
240
+}
0 241
new file mode 100644
... ...
@@ -0,0 +1,43 @@
1
+/*
2
+ * Copyright (C) 2010 Stefan Sayer
3
+ *
4
+ * This file is part of SEMS, a free SIP media server.
5
+ *
6
+ * SEMS is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * For a license to use the SEMS software under conditions
12
+ * other than those described here, or to purchase support for this
13
+ * software, please contact iptel.org by e-mail at the following addresses:
14
+ *    info@iptel.org
15
+ *
16
+ * SEMS is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
+ */
25
+
26
+#ifndef _ParamReplacer_h_
27
+#define _ParamReplacer_h_
28
+
29
+#include <string>
30
+using std::string;
31
+
32
+#include "AmSipMsg.h"
33
+#include "AmUriParser.h"
34
+
35
+// $xy parameters replacement
36
+string replaceParameters(const string& s,
37
+			 const char* r_type,
38
+			 const AmSipRequest& req,
39
+			 const string& app_param,
40
+			 AmUriParser& ruri_parser,
41
+			 AmUriParser& from_parser,
42
+			 AmUriParser& to_parser);
43
+#endif
0 44
new file mode 100644
... ...
@@ -0,0 +1,901 @@
1
+/*
2
+ * Copyright (C) 2010 Stefan Sayer
3
+ *
4
+ * This file is part of SEMS, a free SIP media server.
5
+ *
6
+ * SEMS is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * For a license to use the SEMS software under conditions
12
+ * other than those described here, or to purchase support for this
13
+ * software, please contact iptel.org by e-mail at the following addresses:
14
+ *    info@iptel.org
15
+ *
16
+ * SEMS is distributed in the hope that it will be useful,
17
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
+ * GNU General Public License for more details.
20
+ *
21
+ * You should have received a copy of the GNU General Public License
22
+ * along with this program; if not, write to the Free Software
23
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
+ */
25
+
26
+/* 
27
+SBC - feature-wishlist
28
+
29
+- SDP filter (reconstructed SDP)
30
+- accounting (MySQL DB, cassandra DB)
31
+- RTP forwarding mode (bridging)
32
+- RTP transcoding mode (bridging)
33
+- overload handling (parallel call to target thresholds)
34
+- call distribution
35
+- select profile on monitoring in-mem DB record
36
+- fallback profile
37
+- add headers
38
+- online profile reload
39
+ */
40
+#include "SBC.h"
41
+
42
+#include "log.h"
43
+#include "AmUtils.h"
44
+#include "AmAudio.h"
45
+#include "AmPlugIn.h"
46
+#include "AmMediaProcessor.h"
47
+#include "AmConfigReader.h"
48
+#include "AmSessionContainer.h"
49
+#include "AmSipHeaders.h"
50
+
51
+#include "HeaderFilter.h"
52
+#include "ParamReplacer.h"
53
+#include "SDPFilter.h"
54
+
55
+#include <algorithm>
56
+using std::map;
57
+
58
+string SBCFactory::user;
59
+string SBCFactory::domain;
60
+string SBCFactory::pwd;
61
+AmConfigReader SBCFactory::cfg;
62
+AmSessionEventHandlerFactory* SBCFactory::session_timer_fact = NULL;
63
+
64
+EXPORT_SESSION_FACTORY(SBCFactory,MOD_NAME);
65
+
66
+
67
+bool SBCCallProfile::readFromConfiguration(const string& name,
68
+					   const string profile_file_name) {
69
+  if (cfg.loadFile(profile_file_name)) {
70
+    ERROR("reading SBC call profile from '%s'\n", profile_file_name.c_str());
71
+    return false;
72
+  }
73
+
74
+  ruri = cfg.getParameter("RURI");
75
+  from = cfg.getParameter("From");
76
+  to = cfg.getParameter("To");
77
+
78
+  force_outbound_proxy = cfg.getParameter("force_outbound_proxy") == "yes";
79
+  outbound_proxy = cfg.getParameter("outbound_proxy");
80
+
81
+  next_hop_ip = cfg.getParameter("next_hop_ip");
82
+  next_hop_port = cfg.getParameter("next_hop_port");
83
+
84
+  string hf_type = cfg.getParameter("header_filter", "transparent");
85
+  if (hf_type=="transparent")
86
+    headerfilter = Transparent;
87
+  else if (hf_type=="whitelist")
88
+    headerfilter = Whitelist;
89
+  else if (hf_type=="blacklist")
90
+    headerfilter = Blacklist;
91
+  else {
92
+    ERROR("invalid header_filter mode '%s'\n", hf_type.c_str());
93
+    return false;
94
+  }
95
+  
96
+  vector<string> elems = explode(cfg.getParameter("header_list"), ",");
97
+  for (vector<string>::iterator it=elems.begin(); it != elems.end(); it++)
98
+    headerfilter_list.insert(*it);
99
+
100
+  string mf_type = cfg.getParameter("message_filter", "transparent");
101
+  if (mf_type=="transparent")
102
+    messagefilter = Transparent;
103
+  else if (mf_type=="whitelist")
104
+    messagefilter = Whitelist;
105
+  else if (hf_type=="blacklist")
106
+    messagefilter = Blacklist;
107
+  else {
108
+    ERROR("invalid message_filter mode '%s'\n", mf_type.c_str());
109
+    return false;
110
+  }
111
+  
112
+  elems = explode(cfg.getParameter("message_list"), ",");
113
+  for (vector<string>::iterator it=elems.begin(); it != elems.end(); it++)
114
+    messagefilter_list.insert(*it);
115
+
116
+  string sdp_filter = cfg.getParameter("sdp_filter");
117
+  if (sdp_filter=="transparent") {
118
+    sdpfilter_enabled = true;
119
+    sdpfilter = Transparent;
120
+  } else if (sdp_filter=="whitelist") {
121
+    sdpfilter_enabled = true;
122
+    sdpfilter = Whitelist;
123
+  } else if (sdp_filter=="blacklist") {
124
+    sdpfilter_enabled = true;
125
+    sdpfilter = Blacklist;
126
+  } else {
127
+    sdpfilter_enabled = false;
128
+  }
129
+  if (sdpfilter_enabled) {
130
+    vector<string> c_elems = explode(cfg.getParameter("sdpfilter_list"), ",");
131
+    for (vector<string>::iterator it=c_elems.begin(); it != c_elems.end(); it++) {
132
+      string c = *it;
133
+      std::transform(c.begin(), c.end(), c.begin(), ::tolower);
134
+      sdpfilter_list.insert(c);
135
+    }
136
+  }
137
+
138
+  sst_enabled = cfg.getParameter("enable_session_timer", "no") == "yes";
139
+  use_global_sst_config = !cfg.hasParameter("session_expires");
140
+  
141
+  auth_enabled = cfg.getParameter("enable_auth", "no") == "yes";
142
+  auth_credentials.user = cfg.getParameter("auth_user");
143
+  auth_credentials.pwd = cfg.getParameter("auth_pwd");
144
+
145
+  call_timer_enabled = cfg.getParameter("enable_call_timer", "no") == "yes";
146
+  call_timer = cfg.getParameter("call_timer");
147
+
148
+  prepaid_enabled = cfg.getParameter("enable_prepaid", "no") == "yes";
149
+  prepaid_accmodule = cfg.getParameter("prepaid_accmodule");
150
+  prepaid_uuid = cfg.getParameter("prepaid_uuid");
151
+  prepaid_acc_dest = cfg.getParameter("prepaid_acc_dest");
152
+
153
+  // check for acc module if configured statically
154
+  if (prepaid_enabled &&
155
+      (prepaid_accmodule.find('$') == string::npos) &&
156
+      (NULL == AmPlugIn::instance()->getFactory4Di(prepaid_accmodule))) {
157
+    ERROR("prepaid accounting module '%s' used in call profile "
158
+	  "'%s' is not loaded\n", prepaid_accmodule.c_str(), name.c_str());
159
+    return false;
160
+  }
161
+
162
+  INFO("SBC: loaded SBC profile '%s':\n", name.c_str());
163
+
164
+  INFO("SBC:      RURI = '%s'\n", ruri.c_str());
165
+  INFO("SBC:      From = '%s'\n", from.c_str());
166
+  INFO("SBC:      To   = '%s'\n", to.c_str());
167
+  INFO("SBC:      force outbound proxy: %s\n", force_outbound_proxy?"yes":"no");
168
+  INFO("SBC:      outbound proxy = '%s'\n", outbound_proxy.c_str());
169
+  if (!next_hop_ip.empty()) {
170
+    INFO("SBC:      next hop = %s%s\n", next_hop_ip.c_str(),
171
+	 next_hop_port.empty()? "" : (":"+next_hop_port).c_str());
172
+  }
173
+
174
+  INFO("SBC:      header filter  is %s, %zd items in list\n",
175
+       FilterType2String(headerfilter), headerfilter_list.size());
176
+  INFO("SBC:      message filter is %s, %zd items in list\n",
177
+       FilterType2String(messagefilter), messagefilter_list.size());
178
+  INFO("SBC:      SDP filter is %sabled, %s, %zd items in list\n",
179
+       sdpfilter_enabled?"en":"dis", FilterType2String(sdpfilter),
180
+       sdpfilter_list.size());
181
+
182
+  INFO("SBC:      SST %sabled\n", sst_enabled?"en":"dis");
183
+  INFO("SBC:      SIP auth %sabled\n", auth_enabled?"en":"dis");
184
+  INFO("SBC:      call timer %sabled\n", call_timer_enabled?"en":"dis");
185
+  if (call_timer_enabled) {
186
+    INFO("SBC:                  %s seconds\n", call_timer.c_str());
187
+  }
188
+  INFO("SBC:      prepaid %sabled\n", prepaid_enabled?"en":"dis");
189
+  if (prepaid_enabled) {
190
+    INFO("SBC:                    acc_module = '%s'\n", prepaid_accmodule.c_str());
191
+    INFO("SBC:                    uuid       = '%s'\n", prepaid_uuid.c_str());
192
+    INFO("SBC:                    acc_dest   = '%s'\n", prepaid_acc_dest.c_str());
193
+  }
194
+
195
+  return true;
196
+}
197
+
198
+SBCFactory::SBCFactory(const string& _app_name)
199
+: AmSessionFactory(_app_name)
200
+{
201
+}
202
+
203
+
204
+int SBCFactory::onLoad()
205
+{
206
+
207
+  if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf"))) {
208
+    ERROR("No configuration for sbc present (%s)\n",
209
+	 (AmConfig::ModConfigPath + string(MOD_NAME ".conf")).c_str()
210
+	 );
211
+    return -1;
212
+  }
213
+
214
+  session_timer_fact = AmPlugIn::instance()->getFactory4Seh("session_timer");
215
+  if(!session_timer_fact) {
216
+    ERROR("could not load session_timer from session_timer plug-in\n");
217
+    return -1;
218
+  }
219
+
220
+  vector<string> profiles_names = explode(cfg.getParameter("profiles"), ",");
221
+  for (vector<string>::iterator it =
222
+	 profiles_names.begin(); it != profiles_names.end(); it++) {
223
+    string profile_file_name = AmConfig::ModConfigPath + *it + ".sbcprofile.conf";
224
+    if (!call_profiles[*it].readFromConfiguration(*it, profile_file_name)) {
225
+      ERROR("configuring SBC call profile from '%s'\n", profile_file_name.c_str());
226
+      return -1;
227
+    }
228
+  }
229
+
230
+  active_profile = cfg.getParameter("active_profile");
231
+  if (active_profile != "$(paramhdr)" &&
232
+      active_profile != "$(ruri.user)" &&
233
+      call_profiles.find(active_profile) == call_profiles.end()) {
234
+    ERROR("call profile active_profile '%s' not loaded!\n", active_profile.c_str());
235
+    return -1;
236
+  }
237
+
238
+  INFO("SBC: active profile: '%s'\n", active_profile.c_str());
239
+  return 0;
240
+}
241
+
242
+
243
+AmSession* SBCFactory::onInvite(const AmSipRequest& req)
244
+{
245
+  string profile = active_profile;
246
+  if (profile == "$(paramhdr)") {
247
+    string app_param = getHeader(req.hdrs, PARAM_HDR, true);
248
+    profile = get_header_keyvalue(app_param,"profile");
249
+  } else if (profile == "$(ruri.user)") {
250
+    profile = req.user;
251
+  }
252
+
253
+  map<string, SBCCallProfile>::iterator it=
254
+    call_profiles.find(profile);
255
+  if (it==call_profiles.end()) {
256
+    ERROR("could not find call profile '%s' (active_profile = %s)\n",
257
+	  profile.c_str(), active_profile.c_str());
258
+    throw AmSession::Exception(500,"Server Internal Error");
259
+  }
260
+
261
+  DBG("using call profile '%s'\n", profile.c_str());
262
+  SBCCallProfile& call_profile = it->second;
263
+  AmConfigReader& sst_cfg = call_profile.use_global_sst_config ?
264
+    cfg : call_profile.cfg; // override with profile config
265
+
266
+  if (call_profile.sst_enabled) {
267
+    DBG("Enabling SIP Session Timers\n");
268
+    if (!session_timer_fact->onInvite(req, sst_cfg))
269
+      return NULL;
270
+  }
271
+
272
+  SBCDialog* b2b_dlg = new SBCDialog(call_profile);
273
+
274
+  if (call_profile.sst_enabled) {
275
+    AmSessionEventHandler* h = session_timer_fact->getHandler(b2b_dlg);
276
+    if(!h) {
277
+      delete b2b_dlg;
278
+      ERROR("could not get a session timer event handler\n");
279
+      throw AmSession::Exception(500,"Server internal error");
280
+    }
281
+
282
+    if (h->configure(sst_cfg)){
283
+      ERROR("Could not configure the session timer: disabling session timers.\n");
284
+      delete h;
285
+    } else {
286
+      b2b_dlg->addHandler(h);
287
+    }
288
+  }
289
+
290
+  return b2b_dlg;
291
+}
292
+
293
+
294
+SBCDialog::SBCDialog(const SBCCallProfile& call_profile) // AmDynInvoke* user_timer)
295
+  : m_state(BB_Init),
296
+    m_user_timer(NULL),prepaid_acc(NULL),
297
+    call_profile(call_profile)
298
+{
299
+  set_sip_relay_only(false);
300
+}
301
+
302
+
303
+SBCDialog::~SBCDialog()
304
+{
305
+}
306
+
307
+void SBCDialog::onInvite(const AmSipRequest& req)
308
+{
309
+  AmUriParser ruri_parser, from_parser, to_parser;
310
+
311
+  DBG("processing initial INVITE\n");
312
+
313
+  if(dlg.reply(req, 100, "Connecting") != 0) {
314
+    throw AmSession::Exception(500,"Failed to reply 100");
315
+  }
316
+
317
+  string app_param = getHeader(req.hdrs, PARAM_HDR, true);
318
+
319
+#define REPLACE_VALS req, app_param,ruri_parser, from_parser, to_parser
320
+
321
+  ruri = call_profile.ruri.empty() ? 
322
+    req.r_uri : replaceParameters(call_profile.ruri, "RURI", REPLACE_VALS);
323
+
324
+  from = call_profile.from.empty() ? 
325
+    req.from : replaceParameters(call_profile.from, "From", REPLACE_VALS);
326
+
327
+  to = call_profile.to.empty() ? 
328
+    req.to : replaceParameters(call_profile.to, "To", REPLACE_VALS);
329
+
330
+  if (!call_profile.outbound_proxy.empty()) {
331
+      call_profile.outbound_proxy =
332
+      replaceParameters(call_profile.outbound_proxy, "outbound_proxy", REPLACE_VALS);
333
+      DBG("set outbound proxy to '%s'\n", call_profile.outbound_proxy.c_str());
334
+  }
335
+
336
+  if (!call_profile.next_hop_ip.empty()) {
337
+    call_profile.next_hop_ip =
338
+      replaceParameters(call_profile.next_hop_ip, "next_hop_ip", REPLACE_VALS);
339
+    DBG("set next hop ip to '%s'\n", call_profile.next_hop_ip.c_str());
340
+
341
+    if (!call_profile.next_hop_port.empty()) {
342
+      call_profile.next_hop_port =
343
+	replaceParameters(call_profile.next_hop_port, "next_hop_port", REPLACE_VALS);
344
+      unsigned int nh_port_i;
345
+      if (str2i(call_profile.next_hop_port, nh_port_i)) {
346
+	ERROR("next hop port '%s' not understood\n", call_profile.next_hop_port.c_str());
347
+	throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
348
+      }
349
+      call_profile.next_hop_port_i = nh_port_i;
350
+      DBG("set next hop port to '%u'\n", call_profile.next_hop_port_i);
351
+    }
352
+  }
353
+
354
+  m_state = BB_Dialing;
355
+
356
+  invite_req = req;
357
+
358
+  removeHeader(invite_req.hdrs,PARAM_HDR);
359
+  removeHeader(invite_req.hdrs,"P-App-Name");
360
+
361
+  if (call_profile.sdpfilter_enabled) {
362
+    b2b_mode = B2BMode_SDPFilter;
363
+  }
364
+
365
+  if (call_profile.sst_enabled) {
366
+    removeHeader(invite_req.hdrs,SIP_HDR_SESSION_EXPIRES);
367
+    removeHeader(invite_req.hdrs,SIP_HDR_MIN_SE);
368
+  }
369
+
370
+  inplaceHeaderFilter(invite_req.hdrs, 
371
+		      call_profile.headerfilter_list, call_profile.headerfilter);
372
+
373
+  if (call_profile.auth_enabled) {
374
+    call_profile.auth_credentials.user = 
375
+      replaceParameters(call_profile.auth_credentials.user, "auth_user", REPLACE_VALS);
376
+    call_profile.auth_credentials.pwd = 
377
+      replaceParameters(call_profile.auth_credentials.pwd, "auth_pwd", REPLACE_VALS);
378
+  }
379
+
380
+  // get timer
381
+  if (call_profile.call_timer_enabled || call_profile.prepaid_enabled) {
382
+    AmDynInvokeFactory* fact =
383
+      AmPlugIn::instance()->getFactory4Di("user_timer");
384
+    if (NULL == fact) {
385
+      ERROR("load session_timer module for call timers\n");
386
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
387
+    }
388
+    m_user_timer = fact->getInstance();
389
+    if(NULL == m_user_timer) {
390
+      ERROR("could not get a timer reference\n");
391
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
392
+    }
393
+  }
394
+
395
+  if (call_profile.call_timer_enabled) {
396
+    call_profile.call_timer =
397
+      replaceParameters(call_profile.call_timer, "call_timer", REPLACE_VALS);
398
+    if (str2i(call_profile.call_timer, call_timer)) {
399
+      ERROR("invalid call_timer value '%s'\n", call_profile.call_timer.c_str());
400
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
401
+    }
402
+
403
+    if (!call_timer) {
404
+      // time=0
405
+      throw AmSession::Exception(503, "Service Unavailable");
406
+    }
407
+  }
408
+
409
+  if (call_profile.prepaid_enabled) {
410
+    call_profile.prepaid_accmodule =
411
+      replaceParameters(call_profile.prepaid_accmodule, "prepaid_accmodule", REPLACE_VALS);
412
+    if (call_profile.prepaid_accmodule.empty()) {
413
+      ERROR("using prepaid but empty prepaid_accmodule!\n");
414
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
415
+    }
416
+
417
+    AmDynInvokeFactory* pp_fact =
418
+      AmPlugIn::instance()->getFactory4Di(call_profile.prepaid_accmodule);
419
+    if (NULL == pp_fact) {
420
+      ERROR("prepaid_accmodule '%s' not loaded\n", call_profile.prepaid_accmodule.c_str());
421
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
422
+    }
423
+    prepaid_acc = pp_fact->getInstance();
424
+    if(NULL == prepaid_acc) {
425
+      ERROR("could not get a prepaid acc reference\n");
426
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
427
+    }
428
+
429
+    call_profile.prepaid_uuid =
430
+      replaceParameters(call_profile.prepaid_uuid, "prepaid_uuid", REPLACE_VALS);
431
+
432
+    call_profile.prepaid_acc_dest =
433
+      replaceParameters(call_profile.prepaid_acc_dest, "prepaid_acc_dest", REPLACE_VALS);
434
+
435
+    prepaid_starttime = time(NULL);
436
+
437
+    AmArg di_args,ret;
438
+    di_args.push(call_profile.prepaid_uuid);
439
+    di_args.push(call_profile.prepaid_acc_dest);
440
+    di_args.push((int)prepaid_starttime);
441
+    di_args.push(getCallID());
442
+    di_args.push(getLocalTag());
443
+    prepaid_acc->invoke("getCredit", di_args, ret);
444
+    prepaid_credit = ret.get(0).asInt();
445
+    if(prepaid_credit < 0) {
446
+      ERROR("Failed to fetch credit from accounting module\n");
447
+      throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR);
448
+    }
449
+    if (prepaid_credit == 0) {
450
+      throw AmSession::Exception(402,"Insufficient Credit");
451
+    }
452
+  }
453
+
454
+#undef REPLACE_VALS
455
+
456
+  DBG("SBC: connecting to '%s'\n",ruri.c_str());
457
+  DBG("     From:  '%s'\n",from.c_str());
458
+  DBG("     To:  '%s'\n",to.c_str());
459
+  connectCallee(to, ruri, true);
460
+}
461
+
462
+void SBCDialog::process(AmEvent* ev)
463
+{
464
+
465
+  AmPluginEvent* plugin_event = dynamic_cast<AmPluginEvent*>(ev);
466
+  if(plugin_event && plugin_event->name == "timer_timeout") {
467
+    int timer_id = plugin_event->data.get(0).asInt();
468
+    if (timer_id == SBC_TIMER_ID_CALL_TIMER &&
469
+	getCalleeStatus() == Connected) {
470
+      DBG("SBC: %us call timer hit - ending call\n", call_timer);
471
+      stopCall();
472
+      ev->processed = true;
473
+      return;
474
+    } else if (timer_id == SBC_TIMER_ID_PREPAID_TIMEOUT) {
475
+      DBG("timer timeout, no more credit\n");
476
+      stopCall();
477
+      ev->processed = true;
478
+      return;
479
+    }
480
+  }
481
+
482
+  AmB2BCallerSession::process(ev);
483
+}
484
+
485
+void SBCDialog::relayEvent(AmEvent* ev) {
486
+  if (call_profile.headerfilter != Transparent) {
487
+    if (ev->event_id == B2BSipRequest) {
488
+      B2BSipRequestEvent* req_ev = dynamic_cast<B2BSipRequestEvent*>(ev);
489
+      assert(req_ev);
490
+      inplaceHeaderFilter(req_ev->req.hdrs, 
491
+			  call_profile.headerfilter_list, call_profile.headerfilter);
492
+    } else if (ev->event_id == B2BSipReply) {
493
+      B2BSipReplyEvent* reply_ev = dynamic_cast<B2BSipReplyEvent*>(ev);
494
+      assert(reply_ev);
495
+      inplaceHeaderFilter(reply_ev->reply.hdrs, 
496
+			  call_profile.headerfilter_list, call_profile.headerfilter);
497
+    }
498
+  }
499
+
500
+  AmB2BCallerSession::relayEvent(ev);
501
+}
502
+
503
+int SBCDialog::filterBody(AmSdp& sdp, bool is_a2b) {
504
+  if (call_profile.sdpfilter_enabled && 
505
+      call_profile.sdpfilter != Transparent) {
506
+    filterSDP(sdp, call_profile.sdpfilter, call_profile.sdpfilter_list);
507
+  }
508
+  return 0;
509
+}
510
+
511
+void SBCDialog::onSipRequest(const AmSipRequest& req) {
512
+  // AmB2BSession does not call AmSession::onSipRequest for 
513
+  // forwarded requests - so lets call event handlers here
514
+  // todo: this is a hack, replace this by calling proper session 
515
+  // event handler in AmB2BSession
516
+  bool fwd = sip_relay_only &&
517
+    (req.method != "BYE") &&
518
+    (req.method != "CANCEL");
519
+  if (fwd) {
520
+      CALL_EVENT_H(onSipRequest,req);
521
+  }
522
+
523
+  if (fwd && call_profile.messagefilter != Transparent) {
524
+    bool is_filtered = (call_profile.messagefilter == Whitelist) ^ 
525
+      (call_profile.messagefilter_list.find(req.method) != 
526
+       call_profile.messagefilter_list.end());
527
+    if (is_filtered) {
528
+      DBG("replying 405 to filtered message '%s'\n", req.method.c_str());
529
+      dlg.reply(req, 405, "Method Not Allowed", "", "", "", SIP_FLAGS_VERBATIM);
530
+      return;
531
+    }
532
+  }
533
+
534
+  AmB2BCallerSession::onSipRequest(req);
535
+}
536
+
537
+void SBCDialog::onSipReply(const AmSipReply& reply, int old_dlg_status,
538
+			      const string& trans_method)
539
+{
540
+  TransMap::iterator t = relayed_req.find(reply.cseq);
541
+  bool fwd = t != relayed_req.end();
542
+
543
+  DBG("onSipReply: %i %s (fwd=%i)\n",reply.code,reply.reason.c_str(),fwd);
544
+  DBG("onSipReply: content-type = %s\n",reply.content_type.c_str());
545
+  if (fwd) {
546
+      CALL_EVENT_H(onSipReply,reply, old_dlg_status, trans_method);
547
+  }
548
+
549
+  AmB2BCallerSession::onSipReply(reply,old_dlg_status, trans_method);
550
+}
551
+
552
+bool SBCDialog::onOtherReply(const AmSipReply& reply)
553
+{
554
+  bool ret = false;
555
+
556
+  if ((m_state == BB_Dialing) && (reply.cseq == invite_req.cseq)) {
557
+    if (reply.code < 200) {
558
+      DBG("Callee is trying... code %d\n", reply.code);
559
+    }
560
+    else if(reply.code < 300) {
561
+      if(getCalleeStatus()  == Connected) {
562
+        m_state = BB_Connected;
563
+
564
+	if ((call_profile.call_timer_enabled || call_profile.prepaid_enabled) &&
565
+	    (NULL == m_user_timer)) {
566
+	  ERROR("internal implementation error: invalid timer reference\n");
567
+	  terminateOtherLeg();
568
+	  terminateLeg();
569
+	  return ret;
570
+	}
571
+
572
+	if (call_profile.call_timer_enabled) {
573
+	  DBG("SBC: starting call timer of %u seconds\n", call_timer);
574
+	  AmArg di_args,ret;
575
+	  di_args.push((int)SBC_TIMER_ID_CALL_TIMER);
576
+	  di_args.push((int)call_timer);           // in seconds
577
+	  di_args.push(getLocalTag().c_str());
578
+	  m_user_timer->invoke("setTimer", di_args, ret);
579
+	}
580
+
581
+	startPrepaidAccounting();
582
+      }
583
+    }
584
+    else if(reply.code == 487 && dlg.getStatus() == AmSipDialog::Pending) {
585
+      DBG("Stopping leg A on 487 from B with 487\n");
586
+      dlg.reply(invite_req, 487, "Request terminated");
587
+      setStopped();
588
+      ret = true;
589
+    }
590
+    else if (reply.code >= 300 && dlg.getStatus() == AmSipDialog::Connected) {
591
+      DBG("Callee final error in connected state with code %d\n",reply.code);
592
+      terminateLeg();
593
+    }
594
+    else {
595
+      DBG("Callee final error with code %d\n",reply.code);
596
+      AmB2BCallerSession::onOtherReply(reply);
597
+    }
598
+  }
599
+  return ret;
600
+}
601
+
602
+
603
+void SBCDialog::onOtherBye(const AmSipRequest& req)
604
+{
605
+  stopPrepaidAccounting();
606
+  AmB2BCallerSession::onOtherBye(req);
607
+}
608
+
609
+
610
+void SBCDialog::onBye(const AmSipRequest& req)
611
+{
612
+  stopCall();
613
+}
614
+
615
+
616
+void SBCDialog::onCancel()
617
+{
618
+  if(dlg.getStatus() == AmSipDialog::Pending) {
619
+    DBG("Wait for leg B to terminate");
620
+  } else {
621
+    DBG("Canceling leg A on CANCEL since dialog is not pending");
622
+    dlg.reply(invite_req, 487, "Request terminated");
623
+    setStopped();
624
+  }
625
+}
626
+
627
+void SBCDialog::stopCall() {
628
+  if (m_state == BB_Connected) {
629
+    stopPrepaidAccounting();
630
+  }
631
+  terminateOtherLeg();
632
+  terminateLeg();
633
+}
634
+
635
+void SBCDialog::startPrepaidAccounting() {
636
+  if (!call_profile.prepaid_enabled)
637
+    return;
638
+
639
+  if (NULL == prepaid_acc) {
640
+    ERROR("Internal error, trying to use prepaid, but no prepaid_acc\n");
641
+    terminateOtherLeg();
642
+    terminateLeg();
643
+    return;
644
+  }
645
+
646
+  gettimeofday(&prepaid_acc_start, NULL);
647
+
648
+  DBG("SBC: starting prepaid timer of %d seconds\n", prepaid_credit);
649
+  {
650
+    AmArg di_args,ret;
651
+    di_args.push((int)SBC_TIMER_ID_PREPAID_TIMEOUT);
652
+    di_args.push((int)prepaid_credit);           // in seconds
653
+    di_args.push(getLocalTag().c_str());
654
+    m_user_timer->invoke("setTimer", di_args, ret);
655
+  }
656
+
657
+  {
658
+    AmArg di_args,ret;
659
+    di_args.push(call_profile.prepaid_uuid);     // prepaid_uuid
660
+    di_args.push(call_profile.prepaid_acc_dest); // accounting destination
661
+    di_args.push((int)prepaid_starttime);        // call start time (INVITE)
662
+    di_args.push((int)prepaid_acc_start.tv_sec); // call connect time
663
+    di_args.push(getCallID());                   // Call-ID
664
+    di_args.push(getLocalTag());                 // ltag
665
+    di_args.push(other_id);                      // other leg ltag
666
+
667
+    prepaid_acc->invoke("connectCall", di_args, ret);
668
+  }
669
+}
670
+
671
+void SBCDialog::stopPrepaidAccounting() {
672
+  if (!call_profile.prepaid_enabled)
673
+    return;
674
+
675
+  if(prepaid_acc_start.tv_sec != 0 || prepaid_acc_start.tv_usec != 0) {
676
+
677
+    if (NULL == prepaid_acc) {
678
+      ERROR("Internal error, trying to subtractCredit, but no prepaid_acc\n");
679
+      return;
680
+    }
681
+
682
+    struct timeval now;
683
+    gettimeofday(&now, NULL);
684
+    timersub(&now, &prepaid_acc_start, &now);
685
+    if(now.tv_usec > 500000)
686
+      now.tv_sec++;
687
+    DBG("Call lasted %ld seconds\n", now.tv_sec);
688
+
689
+    AmArg di_args,ret;
690
+    di_args.push(call_profile.prepaid_uuid);     // prepaid_uuid
691
+    di_args.push((int)now.tv_sec);               // call duration
692
+    di_args.push(call_profile.prepaid_acc_dest); // accounting destination
693
+    di_args.push((int)prepaid_starttime);        // call start time (INVITE)
694
+    di_args.push((int)prepaid_acc_start.tv_sec); // call connect time
695
+    di_args.push((int)time(NULL));               // call end time
696
+    di_args.push(getCallID());                   // Call-ID
697
+    di_args.push(getLocalTag());                 // ltag
698
+    di_args.push(other_id);
699
+
700
+    prepaid_acc->invoke("subtractCredit", di_args, ret);
701
+  }
702
+}
703
+
704
+void SBCDialog::createCalleeSession()
705
+{
706
+  SBCCalleeSession* callee_session = new SBCCalleeSession(this, call_profile);
707
+  
708
+  if (call_profile.auth_enabled) {
709
+    // adding auth handler
710
+    AmSessionEventHandlerFactory* uac_auth_f = 
711
+      AmPlugIn::instance()->getFactory4Seh("uac_auth");
712
+    if (NULL == uac_auth_f)  {
713
+      INFO("uac_auth module not loaded. uac auth NOT enabled.\n");
714
+    } else {
715
+      AmSessionEventHandler* h = uac_auth_f->getHandler(callee_session);
716
+      
717
+      // we cannot use the generic AmSessionEventHandler hooks, 
718
+      // because the hooks don't work in AmB2BSession
719
+      callee_session->setAuthHandler(h);
720
+      DBG("uac auth enabled for callee session.\n");
721
+    }
722
+  }
723
+
724
+  if (call_profile.sst_enabled) {
725
+    AmSessionEventHandler* h = SBCFactory::session_timer_fact->getHandler(callee_session);
726
+    if(!h) {
727
+      ERROR("could not get a session timer event handler\n");
728
+      delete callee_session;
729
+      throw AmSession::Exception(500,"Server internal error");
730
+    }
731
+    AmConfigReader& sst_cfg = call_profile.use_global_sst_config ? 
732
+      SBCFactory::cfg: call_profile.cfg; // override with profile config
733
+
734
+    if(h->configure(sst_cfg)){
735
+      ERROR("Could not configure the session timer: disabling session timers.\n");
736
+      delete h;
737
+    } else {
738
+      callee_session->addHandler(h);
739
+    }
740
+  }
741
+
742
+  AmSipDialog& callee_dlg = callee_session->dlg;
743
+
744
+  callee_dlg.force_outbound_proxy = call_profile.force_outbound_proxy;
745
+  if (!call_profile.outbound_proxy.empty()) {
746
+    callee_dlg.outbound_proxy = call_profile.outbound_proxy;
747
+  }
748
+  
749
+  if (!call_profile.next_hop_ip.empty()) {
750
+    callee_dlg.next_hop_ip = call_profile.next_hop_ip;
751
+    callee_dlg.next_hop_port = call_profile.next_hop_port.empty() ?
752
+      5060 : call_profile.next_hop_port_i;
753
+  }
754
+
755
+  other_id = AmSession::getNewId();
756
+  
757
+  callee_dlg.local_tag    = other_id;
758
+  callee_dlg.callid       = AmSession::getNewId() + "@" + AmConfig::LocalIP;
759
+  
760
+  // this will be overwritten by ConnectLeg event 
761
+  callee_dlg.remote_party = to;
762
+  callee_dlg.remote_uri   = ruri;
763
+
764
+  callee_dlg.local_party  = from; 
765
+  callee_dlg.local_uri    = from; 
766
+  
767
+  DBG("Created B2BUA callee leg, From: %s\n",
768
+      from.c_str());
769
+
770
+  if (AmConfig::LogSessions) {
771
+    INFO("Starting B2B callee session %s app %s\n",
772
+	 callee_session->getLocalTag().c_str(), invite_req.cmd.c_str());
773
+  }
774
+
775
+  MONITORING_LOG5(other_id.c_str(), 
776
+		  "app",  invite_req.cmd.c_str(),
777
+		  "dir",  "out",