1 /* ====================================================================
2 * The Kannel Software License, Version 1.0
3 *
4 * Copyright (c) 2001-2014 Kannel Group
5 * Copyright (c) 1998-2001 WapIT Ltd.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Kannel Group (http://www.kannel.org/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Kannel" and "Kannel Group" must not be used to
28 * endorse or promote products derived from this software without
29 * prior written permission. For written permission, please
30 * contact org@kannel.org.
31 *
32 * 5. Products derived from this software may not be called "Kannel",
33 * nor may "Kannel" appear in their name, without prior written
34 * permission of the Kannel Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS
40 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
41 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
42 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
43 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
44 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
45 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
46 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
48 *
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Kannel Group. For more information on
51 * the Kannel Group, please see <http://www.kannel.org/>.
52 *
53 * Portions of this software are based upon software originally written at
54 * WapIT Ltd., Helsinki, Finland for the Kannel project.
55 */
56
57 /*
58 * SMSC Connection wrapper
59 *
60 * Interface to old SMS center implementations
61 *
62 * Kalle Marjola 2000
63 */
64
65 #include "gwlib/gwlib.h"
66 #include "smscconn.h"
67 #include "smscconn_p.h"
68 #include "bb_smscconn_cb.h"
69
70 #include "smsc.h"
71 #include "smsc_p.h"
72
73
74 typedef struct smsc_wrapper {
75 SMSCenter *smsc;
76 List *outgoing_queue;
77 List *stopped; /* list-trick for suspend/isolate */
78 long receiver_thread;
79 long sender_thread;
80 Mutex *reconnect_mutex;
81 } SmscWrapper;
82
83
smscwrapper_destroy(SmscWrapper * wrap)84 static void smscwrapper_destroy(SmscWrapper *wrap)
85 {
86 if (wrap == NULL)
87 return;
88 gwlist_destroy(wrap->outgoing_queue, NULL);
89 gwlist_destroy(wrap->stopped, NULL);
90 mutex_destroy(wrap->reconnect_mutex);
91 if (wrap->smsc != NULL)
92 smsc_close(wrap->smsc);
93 gw_free(wrap);
94 }
95
96
reconnect(SMSCConn * conn)97 static int reconnect(SMSCConn *conn)
98 {
99 SmscWrapper *wrap = conn->data;
100 Msg *msg;
101 int ret;
102 int wait = 1;
103
104 /* disable double-reconnect
105 * NOTE: it is still possible that we do double-connect if
106 * first thread gets through this if-statement and then
107 * execution switches to another thread.. this can be avoided
108 * via double-mutex system, but I do not feel it is worth it,
109 * maybe later --rpr
110 */
111 if (conn->status == SMSCCONN_RECONNECTING) {
112 mutex_lock(wrap->reconnect_mutex); /* wait here */
113 mutex_unlock(wrap->reconnect_mutex);
114 return 0;
115 }
116 mutex_lock(wrap->reconnect_mutex);
117
118 debug("bb.sms", 0, "smsc_wrapper <%s>: reconnect started",
119 octstr_get_cstr(conn->name));
120
121 while((msg = gwlist_extract_first(wrap->outgoing_queue))!=NULL) {
122 bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_TEMPORARILY, NULL);
123 }
124 conn->status = SMSCCONN_RECONNECTING;
125
126
127 while(conn->why_killed == SMSCCONN_ALIVE) {
128 ret = smsc_reopen(wrap->smsc);
129 if (ret == 0) {
130 info(0, "Re-open of %s succeeded.", octstr_get_cstr(conn->name));
131 mutex_lock(conn->flow_mutex);
132 conn->status = SMSCCONN_ACTIVE;
133 conn->connect_time = time(NULL);
134 mutex_unlock(conn->flow_mutex);
135 bb_smscconn_connected(conn);
136 break;
137 }
138 else if (ret == -2) {
139 error(0, "Re-open of %s failed permanently",
140 octstr_get_cstr(conn->name));
141 mutex_lock(conn->flow_mutex);
142 conn->status = SMSCCONN_DISCONNECTED;
143 mutex_unlock(wrap->reconnect_mutex);
144 mutex_unlock(conn->flow_mutex);
145 return -1; /* permanent failure */
146 }
147 else {
148 error(0, "Re-open to <%s> failed, retrying after %d minutes...",
149 octstr_get_cstr(conn->name), wait);
150 gwthread_sleep(wait*60.0);
151
152 wait = wait > 10 ? 10 : wait * 2 + 1;
153 }
154 }
155 mutex_unlock(wrap->reconnect_mutex);
156 return 0;
157 }
158
159
sms_receive(SMSCConn * conn)160 static Msg *sms_receive(SMSCConn *conn)
161 {
162 SmscWrapper *wrap = conn->data;
163 int ret;
164 Msg *newmsg = NULL;
165
166 if (smscenter_pending_smsmessage(wrap->smsc) == 1) {
167
168 ret = smscenter_receive_msg(wrap->smsc, &newmsg);
169 if (ret == 1) {
170
171 /* if any smsc_id available, use it */
172 newmsg->sms.smsc_id = octstr_duplicate(conn->id);
173
174 return newmsg;
175 } else if (ret == 0) { /* "NEVER" happens */
176 warning(0, "SMSC %s: Pending message returned '1', "
177 "but nothing to receive!", octstr_get_cstr(conn->name));
178 msg_destroy(newmsg);
179 return NULL;
180 } else {
181 msg_destroy(newmsg);
182 if (reconnect(conn) == -1)
183 smscconn_shutdown(conn, 0);
184 return NULL;
185 }
186 }
187 return NULL;
188 }
189
190
wrapper_receiver(void * arg)191 static void wrapper_receiver(void *arg)
192 {
193 Msg *msg;
194 SMSCConn *conn = arg;
195 SmscWrapper *wrap = conn->data;
196 /* SmscWrapper *wrap = conn->data; ** non-used */
197 double sleep = 0.0001;
198
199 /* Make sure we log into our own log-file if defined */
200 log_thread_to(conn->log_idx);
201
202 /* remove messages from SMSC until we are killed */
203 while(conn->why_killed == SMSCCONN_ALIVE) {
204
205 gwlist_consume(wrap->stopped); /* block here if suspended/isolated */
206
207 msg = sms_receive(conn);
208 if (msg) {
209 debug("bb.sms", 0, "smscconn (%s): new message received",
210 octstr_get_cstr(conn->name));
211 sleep = 0.0001;
212 bb_smscconn_receive(conn, msg);
213 }
214 else {
215 /* note that this implementations means that we sleep even
216 * when we fail connection.. but time is very short, anyway
217 */
218 gwthread_sleep(sleep);
219 /* gradually sleep longer and longer times until something starts to
220 * happen - this of course reduces response time, but that's better than
221 * extensive CPU usage when it is not used
222 */
223 sleep *= 2;
224 if (sleep >= 2.0)
225 sleep = 1.999999;
226 }
227 }
228 conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
229
230 /* this thread is joined at sender */
231 }
232
233
234
sms_send(SMSCConn * conn,Msg * msg)235 static int sms_send(SMSCConn *conn, Msg *msg)
236 {
237 SmscWrapper *wrap = conn->data;
238 int ret;
239
240 debug("bb.sms", 0, "smscconn_sender (%s): sending message",
241 octstr_get_cstr(conn->name));
242
243 ret = smscenter_submit_msg(wrap->smsc, msg);
244 if (ret == -1) {
245 bb_smscconn_send_failed(conn, msg,
246 SMSCCONN_FAILED_REJECTED, octstr_create("REJECTED"));
247
248 if (reconnect(conn) == -1)
249 smscconn_shutdown(conn, 0);
250 return -1;
251 } else {
252 bb_smscconn_sent(conn, msg, NULL);
253 return 0;
254 }
255 }
256
257
wrapper_sender(void * arg)258 static void wrapper_sender(void *arg)
259 {
260 Msg *msg;
261 SMSCConn *conn = arg;
262 SmscWrapper *wrap = conn->data;
263
264 /* Make sure we log into our own log-file if defined */
265 log_thread_to(conn->log_idx);
266
267 /* send messages to SMSC until our outgoing_list is empty and
268 * no producer anymore (we are set to shutdown) */
269 while(conn->status != SMSCCONN_DEAD) {
270
271 if ((msg = gwlist_consume(wrap->outgoing_queue)) == NULL)
272 break;
273
274 if (octstr_search_char(msg->sms.receiver, ' ', 0) != -1) {
275 /*
276 * multi-send: this should be implemented in corresponding
277 * SMSC protocol, but while we are waiting for that...
278 */
279 int i;
280 Msg *newmsg;
281 /* split from spaces: in future, split with something more sensible,
282 * this is dangerous... (as space is url-encoded as '+')
283 */
284 List *nlist = octstr_split_words(msg->sms.receiver);
285
286 debug("bb.sms", 0, "Handling multi-receiver message");
287
288 for(i=0; i < gwlist_len(nlist); i++) {
289
290 newmsg = msg_duplicate(msg);
291 octstr_destroy(newmsg->sms.receiver);
292
293 newmsg->sms.receiver = gwlist_get(nlist, i);
294 sms_send(conn, newmsg);
295 }
296 gwlist_destroy(nlist, NULL);
297 msg_destroy(msg);
298 }
299 else
300 sms_send(conn,msg);
301
302 }
303 /* cleanup, we are now dying */
304
305 debug("bb.sms", 0, "SMSCConn %s sender died, waiting for receiver",
306 octstr_get_cstr(conn->name));
307
308 conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
309
310 if (conn->is_stopped) {
311 gwlist_remove_producer(wrap->stopped);
312 conn->is_stopped = 0;
313 }
314
315 gwthread_wakeup(wrap->receiver_thread);
316 gwthread_join(wrap->receiver_thread);
317
318 /* call 'failed' to all messages still in queue */
319
320 mutex_lock(conn->flow_mutex);
321
322 conn->status = SMSCCONN_DEAD;
323
324 while((msg = gwlist_extract_first(wrap->outgoing_queue))!=NULL) {
325 bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_SHUTDOWN, NULL);
326 }
327 smscwrapper_destroy(wrap);
328 conn->data = NULL;
329
330 mutex_unlock(conn->flow_mutex);
331
332 bb_smscconn_killed();
333 }
334
335
336
wrapper_add_msg(SMSCConn * conn,Msg * sms)337 static int wrapper_add_msg(SMSCConn *conn, Msg *sms)
338 {
339 SmscWrapper *wrap = conn->data;
340 Msg *copy;
341
342 copy = msg_duplicate(sms);
343 gwlist_produce(wrap->outgoing_queue, copy);
344
345 return 0;
346 }
347
348
wrapper_shutdown(SMSCConn * conn,int finish_sending)349 static int wrapper_shutdown(SMSCConn *conn, int finish_sending)
350 {
351 SmscWrapper *wrap = conn->data;
352
353 debug("bb.sms", 0, "Shutting down SMSCConn %s, %s",
354 octstr_get_cstr(conn->name), finish_sending ? "slow" : "instant");
355
356 if (finish_sending == 0) {
357 Msg *msg;
358 while((msg = gwlist_extract_first(wrap->outgoing_queue))!=NULL) {
359 bb_smscconn_send_failed(conn, msg, SMSCCONN_FAILED_SHUTDOWN, NULL);
360 }
361 }
362 gwlist_remove_producer(wrap->outgoing_queue);
363 gwthread_wakeup(wrap->sender_thread);
364 gwthread_wakeup(wrap->receiver_thread);
365 return 0;
366 }
367
wrapper_stop(SMSCConn * conn)368 static void wrapper_stop(SMSCConn *conn)
369 {
370 SmscWrapper *wrap = conn->data;
371
372 debug("smscconn", 0, "Stopping wrapper");
373 gwlist_add_producer(wrap->stopped);
374
375 }
376
wrapper_start(SMSCConn * conn)377 static void wrapper_start(SMSCConn *conn)
378 {
379 SmscWrapper *wrap = conn->data;
380
381 debug("smscconn", 0, "Starting wrapper");
382 gwlist_remove_producer(wrap->stopped);
383 }
384
385
wrapper_queued(SMSCConn * conn)386 static long wrapper_queued(SMSCConn *conn)
387 {
388 SmscWrapper *wrap = conn->data;
389 long ret = gwlist_len(wrap->outgoing_queue);
390
391 /* use internal queue as load, maybe something else later */
392
393 conn->load = ret;
394 return ret;
395 }
396
smsc_wrapper_create(SMSCConn * conn,CfgGroup * cfg)397 int smsc_wrapper_create(SMSCConn *conn, CfgGroup *cfg)
398 {
399 /* 1. Call smsc_open()
400 * 2. create sender/receiver threads
401 * 3. fill up the conn
402 *
403 * XXX open() SHOULD be done in distinct thread, not here!
404 */
405
406 SmscWrapper *wrap;
407
408 wrap = gw_malloc(sizeof(SmscWrapper));
409 wrap->smsc = NULL;
410 conn->data = wrap;
411 conn->send_msg = wrapper_add_msg;
412
413
414 wrap->outgoing_queue = gwlist_create();
415 wrap->stopped = gwlist_create();
416 wrap->reconnect_mutex = mutex_create();
417 gwlist_add_producer(wrap->outgoing_queue);
418
419 if ((wrap->smsc = smsc_open(cfg)) == NULL)
420 goto error;
421
422 conn->name = octstr_create(smsc_name(wrap->smsc));
423 conn->status = SMSCCONN_ACTIVE;
424 conn->connect_time = time(NULL);
425
426 if (conn->is_stopped)
427 gwlist_add_producer(wrap->stopped);
428
429
430 /* XXX here we could fail things... specially if the second one
431 * fails.. so fix this ASAP
432 *
433 * moreover, open should be in sender/receiver, so that we can continue
434 * while trying to open... maybe move this, or just wait for new
435 * implementations of various SMSC protocols
436 */
437
438 if ((wrap->receiver_thread = gwthread_create(wrapper_receiver, conn))==-1)
439 goto error;
440
441 if ((wrap->sender_thread = gwthread_create(wrapper_sender, conn))==-1)
442 goto error;
443
444 conn->shutdown = wrapper_shutdown;
445 conn->queued = wrapper_queued;
446 conn->stop_conn = wrapper_stop;
447 conn->start_conn = wrapper_start;
448
449 return 0;
450
451 error:
452 error(0, "Failed to create Smsc wrapper");
453 conn->data = NULL;
454 smscwrapper_destroy(wrap);
455 conn->why_killed = SMSCCONN_KILLED_CANNOT_CONNECT;
456 conn->status = SMSCCONN_DEAD;
457 return -1;
458 }
459
460
461
462