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