1 /*
2 * Copyright (C) 2002-2003 Fhg Fokus
3 * Copyright (C) 2008 Juha Heinanen (USE_MYSQL parts)
4 *
5 * This file is part of SEMS, a free SIP media server.
6 *
7 * SEMS is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * For a license to use the sems software under conditions
13 * other than those described here, or to purchase support for this
14 * software, please contact iptel.org by e-mail at the following addresses:
15 * info@iptel.org
16 *
17 * SEMS is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 */
26
27 #include "EarlyAnnounce.h"
28 #include "AmConfig.h"
29 #include "AmUtils.h"
30
31 #include "sems.h"
32 #include "log.h"
33
34 #define MOD_NAME "early_announce"
35
36 #ifdef USE_MYSQL
37 #define DEFAULT_AUDIO_TABLE "default_audio"
38 #define DOMAIN_AUDIO_TABLE "domain_audio"
39 #define USER_AUDIO_TABLE "user_audio"
40 #endif
41
42 EXPORT_SESSION_FACTORY(EarlyAnnounceFactory,MOD_NAME);
43
44 #ifdef USE_MYSQL
45 string EarlyAnnounceFactory::AnnounceApplication;
46 string EarlyAnnounceFactory::AnnounceMessage;
47 string EarlyAnnounceFactory::DefaultLanguage;
48 #else
49 string EarlyAnnounceFactory::AnnouncePath;
50 string EarlyAnnounceFactory::AnnounceFile;
51 #endif
52
53 EarlyAnnounceFactory::ContB2B EarlyAnnounceFactory::ContinueB2B =
54 EarlyAnnounceFactory::Never;
55
EarlyAnnounceFactory(const string & _app_name)56 EarlyAnnounceFactory::EarlyAnnounceFactory(const string& _app_name)
57 : AmSessionFactory(_app_name)
58 {
59 }
60
61 #ifdef USE_MYSQL
62
63 static sql::Driver *driver;
64 static sql::Connection *connection;
65
get_announce_msg(string application,string message,string user,string domain,string language,string * audio_file)66 int get_announce_msg(string application, string message, string user,
67 string domain, string language, string *audio_file)
68 {
69 string query_string;
70
71 if (!user.empty()) {
72 *audio_file = string("/tmp/") + application + "_" +
73 message + "_" + domain + "_" + user + ".wav";
74 query_string = "select audio from " + string(USER_AUDIO_TABLE) +
75 " where application='" + application + "' and message='" +
76 message + "' and userid='" + user + "' and domain='" +
77 domain + "'";
78 } else if (!domain.empty()) {
79 *audio_file = string("/tmp/") + application + "_" +
80 message + "_" + domain + "_" + language + ".wav";
81 query_string = "select audio from " + string(DOMAIN_AUDIO_TABLE) +
82 " where application='" + application + "' and message='" +
83 message + "' and domain='" + domain + "' and language='" +
84 language + "'";
85 } else {
86 *audio_file = string("/tmp/") + application + "_" +
87 message + "_" + language + ".wav";
88 query_string = "select audio from " + string(DEFAULT_AUDIO_TABLE) +
89 " where application='" + application + "' and message='" +
90 message + "' and language='" + language + "'";
91 }
92
93 try {
94
95 DBG("Query string <%s>\n", query_string.c_str());
96
97 sql::Statement *stmt;
98 sql::ResultSet *result;
99
100 stmt = connection->createStatement();
101 result = stmt->executeQuery(query_string);
102 if (result->next()) {
103 FILE *file;
104 file = fopen((*audio_file).c_str(), "wb");
105 string s = result->getString("audio");
106 fwrite(s.data(), 1, s.length(), file);
107 fclose(file);
108 return 1;
109 } else {
110 *audio_file = "";
111 return 1;
112 }
113
114 delete stmt;
115 delete result;
116
117 }
118
119 catch (sql::SQLException &er) {
120 ERROR("MySQL query error: %s\n", er.what());
121 *audio_file = "";
122 return 0;
123 }
124 }
125
126 #endif
127
onLoad()128 int EarlyAnnounceFactory::onLoad()
129 {
130 AmConfigReader cfg;
131 if(cfg.loadFile(AmConfig::ModConfigPath + string(MOD_NAME ".conf")))
132 return -1;
133
134 // get application specific global parameters
135 configureModule(cfg);
136
137 if (cfg.hasParameter("continue_b2b")) {
138 if (cfg.getParameter("continue_b2b") == "yes") {
139 ContinueB2B = Always;
140 DBG("early_announce in b2bua mode.\n");
141 }
142 else if (cfg.getParameter("continue_b2b") == "app-param") {
143 ContinueB2B = AppParam;
144 DBG("early_announce in b2bua/final reply mode "
145 "(depends on app-param).\n");
146 } else {
147 DBG("early_announce sends final reply.\n");
148 }
149 }
150
151 #ifdef USE_MYSQL
152
153 /* Get default audio from MySQL */
154
155 string mysql_server, mysql_user, mysql_passwd, mysql_db, mysql_ca_cert;
156 bool reconnect_state = true;
157 sql::ConnectOptionsMap connection_properties;
158
159 mysql_server = cfg.getParameter("mysql_server");
160 if (mysql_server.empty()) {
161 mysql_server = "localhost";
162 }
163
164 mysql_user = cfg.getParameter("mysql_user");
165 if (mysql_user.empty()) {
166 ERROR("early_announce.conf parameter 'mysql_user' is missing.\n");
167 return -1;
168 }
169
170 mysql_passwd = cfg.getParameter("mysql_passwd");
171 if (mysql_passwd.empty()) {
172 ERROR("early_announce.conf parameter 'mysql_passwd' is missing.\n");
173 return -1;
174 }
175
176 mysql_db = cfg.getParameter("mysql_db");
177 if (mysql_db.empty()) {
178 mysql_db = "sems";
179 }
180
181 mysql_ca_cert = cfg.getParameter("mysql_ca_cert");
182
183 AnnounceApplication = cfg.getParameter("application");
184 if (AnnounceApplication.empty()) {
185 AnnounceApplication = MOD_NAME;
186 }
187
188 AnnounceMessage = cfg.getParameter("message");
189 if (AnnounceMessage.empty()) {
190 AnnounceMessage = "greeting_msg";
191 }
192
193 DefaultLanguage = cfg.getParameter("default_language");
194 if (DefaultLanguage.empty()) {
195 DefaultLanguage = "en";
196 }
197
198 try {
199
200 connection_properties["hostName"] =
201 sql::ConnectPropertyVal(mysql_server);
202 connection_properties["userName"] =
203 sql::ConnectPropertyVal(mysql_user);
204 connection_properties["password"] =
205 sql::ConnectPropertyVal(mysql_passwd);
206
207 if (!mysql_ca_cert.empty()) {
208 connection_properties["sslCa"] =
209 sql::ConnectPropertyVal(sql::SQLString(mysql_ca_cert));
210 connection_properties["sslCAPath"] =
211 sql::ConnectPropertyVal(sql::SQLString(""));
212 connection_properties["sslCipher"] =
213 sql::ConnectPropertyVal(sql::SQLString("DHE-RSA-AES256-SHA"));
214 connection_properties["sslEnforce"] =
215 sql::ConnectPropertyVal(true);
216 }
217
218 driver = get_driver_instance();
219 connection = driver->connect(connection_properties);
220 connection->setClientOption("OPT_RECONNECT", &reconnect_state);
221 connection->setSchema(mysql_db);
222
223 }
224
225 catch (sql::SQLException &er) {
226 ERROR("MySQL connection error: %s\n", er.what());
227 return -1;
228 }
229
230 string announce_file;
231 if (!get_announce_msg(AnnounceApplication, AnnounceMessage, "", "",
232 DefaultLanguage, &announce_file)) {
233 return -1;
234 }
235 if (announce_file.empty()) {
236 ERROR("default announce for " MOD_NAME " module does not exist.\n");
237 return -1;
238 }
239
240 #else
241
242 /* Get default audio from file system */
243
244 AnnouncePath = cfg.getParameter("announce_path",ANNOUNCE_PATH);
245 if( !AnnouncePath.empty()
246 && AnnouncePath[AnnouncePath.length()-1] != '/' )
247 AnnouncePath += "/";
248
249 AnnounceFile = cfg.getParameter("default_announce",ANNOUNCE_FILE);
250
251 string announce_file = AnnouncePath + AnnounceFile;
252 if(!file_exists(announce_file)){
253 ERROR("default file for " MOD_NAME " module does not exist ('%s').\n",
254 announce_file.c_str());
255 return -1;
256 }
257
258 #endif
259
260 return 0;
261 }
262
263
onInvite(const AmSipRequest & req)264 void EarlyAnnounceDialog::onInvite(const AmSipRequest& req)
265 {
266 AmMimeBody sdp_body;
267 sdp_body.addPart(SIP_APPLICATION_SDP);
268
269 if(dlg->reply(req,183,"Session Progress",
270 &sdp_body) != 0){
271 throw AmSession::Exception(500,"could not reply");
272 } else {
273 invite_req = req;
274 }
275 }
276
277
onInvite(const AmSipRequest & req,const string & app_name,const map<string,string> & app_params)278 AmSession* EarlyAnnounceFactory::onInvite(const AmSipRequest& req, const string& app_name,
279 const map<string,string>& app_params)
280 {
281
282 #ifdef USE_MYSQL
283
284 string iptel_app_param = getHeader(req.hdrs, PARAM_HDR, true);
285 string language = get_header_keyvalue(iptel_app_param,"Language");
286 string announce_file = "";
287
288 if (language.empty()) language = DefaultLanguage;
289
290 get_announce_msg(AnnounceApplication, AnnounceMessage, req.user,
291 req.domain, "", &announce_file);
292 if (!announce_file.empty()) goto end;
293 get_announce_msg(AnnounceApplication, AnnounceMessage, "", req.domain,
294 language, &announce_file);
295 if (!announce_file.empty()) goto end;
296 get_announce_msg(AnnounceApplication, AnnounceMessage, "", "", language,
297 &announce_file);
298
299 #else
300
301 string announce_path = AnnouncePath;
302 string announce_file = announce_path + req.domain
303 + "/" + req.user + ".wav";
304
305 DBG("trying '%s'\n",announce_file.c_str());
306 if(file_exists(announce_file))
307 goto end;
308
309 announce_file = announce_path + req.user + ".wav";
310 DBG("trying '%s'\n",announce_file.c_str());
311 if(file_exists(announce_file))
312 goto end;
313
314 announce_file = AnnouncePath + AnnounceFile;
315
316 #endif
317
318 end:
319 return new EarlyAnnounceDialog(announce_file);
320 }
321
EarlyAnnounceDialog(const string & filename)322 EarlyAnnounceDialog::EarlyAnnounceDialog(const string& filename)
323 : filename(filename)
324 {
325 set_sip_relay_only(false);
326 }
327
~EarlyAnnounceDialog()328 EarlyAnnounceDialog::~EarlyAnnounceDialog()
329 {
330 }
331
onEarlySessionStart()332 void EarlyAnnounceDialog::onEarlySessionStart()
333 {
334 // we can drop all received packets
335 // this disables DTMF detection as well
336 setReceiving(false);
337
338 DBG("EarlyAnnounceDialog::onEarlySessionStart\n");
339
340 if(wav_file.open(filename,AmAudioFile::Read))
341 throw string("EarlyAnnounceDialog::onEarlySessionStart: Cannot open file");
342
343 setOutput(&wav_file);
344
345 AmB2BCallerSession::onEarlySessionStart();
346 }
347
onBye(const AmSipRequest & req)348 void EarlyAnnounceDialog::onBye(const AmSipRequest& req)
349 {
350 DBG("onBye: stopSession\n");
351 setStopped();
352 }
353
onCancel(const AmSipRequest & req)354 void EarlyAnnounceDialog::onCancel(const AmSipRequest& req)
355 {
356 dlg->reply(invite_req,487,"Call terminated");
357 AmB2BCallerSession::terminateOtherLeg();
358 setStopped();
359 }
360
process(AmEvent * event)361 void EarlyAnnounceDialog::process(AmEvent* event)
362 {
363
364 AmAudioEvent* audio_event = dynamic_cast<AmAudioEvent*>(event);
365 if(audio_event &&
366 (audio_event->event_id == AmAudioEvent::cleared)) {
367 DBG("AmAudioEvent::cleared\n");
368
369 bool continue_b2b = false;
370 if (EarlyAnnounceFactory::ContinueB2B ==
371 EarlyAnnounceFactory::Always) {
372 continue_b2b = true;
373 } else if (EarlyAnnounceFactory::ContinueB2B ==
374 EarlyAnnounceFactory::AppParam) {
375 string iptel_app_param = getHeader(invite_req.hdrs, PARAM_HDR, true);
376 if (iptel_app_param.length()) {
377 continue_b2b = get_header_keyvalue(iptel_app_param,"B2B")=="yes";
378 } else {
379 continue_b2b = getHeader(invite_req.hdrs,"P-B2B", true)=="yes";
380 }
381 }
382 DBG("determined: continue_b2b = %s\n", continue_b2b?"true":"false");
383
384 if (!continue_b2b) {
385 unsigned int code_i = 404;
386 string reason = "Not Found";
387
388 string iptel_app_param = getHeader(invite_req.hdrs, PARAM_HDR, true);
389 if (iptel_app_param.length()) {
390 string code = get_header_keyvalue(iptel_app_param,"Final-Reply-Code");
391 if (code.length() && str2i(code, code_i)) {
392 ERROR("while parsing Final-Reply-Code parameter\n");
393 }
394 reason = get_header_keyvalue(iptel_app_param,"Final-Reply-Reason");
395 if (!reason.length())
396 reason = "Not Found";
397 } else {
398 string code = getHeader(invite_req.hdrs,"P-Final-Reply-Code", true);
399 if (code.length() && str2i(code, code_i)) {
400 ERROR("while parsing P-Final-Reply-Code\n");
401 }
402 string h_reason = getHeader(invite_req.hdrs,"P-Final-Reply-Reason", true);
403 if (h_reason.length()) {
404 INFO("Use of P-Final-Reply-Code/P-Final-Reply-Reason is deprecated. ");
405 INFO("Use '%s: Final-Reply-Code=<code>;"
406 "Final-Reply-Reason=<rs>' instead.\n",PARAM_HDR);
407 reason = h_reason;
408 }
409 }
410
411 DBG("Replying with code %d %s\n", code_i, reason.c_str());
412 dlg->reply(invite_req, code_i, reason);
413
414 setStopped();
415 } else {
416 set_sip_relay_only(true);
417 recvd_req.insert(std::make_pair(invite_req.cseq,invite_req));
418
419 relayEvent(new B2BSipRequestEvent(invite_req,true));
420 }
421
422 return;
423 }
424
425 AmB2BCallerSession::process(event);
426 }
427