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