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 * Implementation of a SM/ASI SMSC module.
59 *
60 * Stipe Tolj <stolj@wapme.de>
61 *
62 * This module connects to a CriticalPath InVoke SMS Center which
63 * uses the SM/ASI protocol.
64 * The module is heavily based on the SMPP module design.
65 *
66 * TODO:
67 * 1. alt_dcs is not used. Instead, msg->sms.mclass is used as the SMASI
68 * Class.
69 * 2. Numbers are not handled correctly, I guess. SMASI allows only(?)
70 * international numbers without leading double zero. How to ensure
71 * this?
72 * 3. Handling of npi and ton correct?
73 * 4. SubmitMulti PDUs not supported.
74 * 5. Replace PDUs not supported.
75 * 6. Status PDUs not supported.
76 * 7. Cancel PDUs not supported.
77 * 8. UserRes PDUs not supported.
78 * 9. Smsc PDUs not supported.
79 * 10. EnquireLink PDUs not supported.
80 */
81
82 #include "gwlib/gwlib.h"
83 #include "msg.h"
84 #include "smsc_p.h"
85 #include "smasi_pdu.h"
86 #include "smscconn_p.h"
87 #include "bb_smscconn_cb.h"
88 #include "sms.h"
89 #include "dlr.h"
90
91 #define DEBUG 1
92
93 #ifndef DEBUG
dump_pdu(const char * msg,Octstr * id,SMASI_PDU * pdu)94 static void dump_pdu(const char *msg, Octstr *id, SMASI_PDU *pdu) { }
95 #else
dump_pdu(const char * msg,Octstr * id,SMASI_PDU * pdu)96 static void dump_pdu(const char *msg, Octstr *id, SMASI_PDU *pdu)
97 {
98 debug("bb.sms.smasi", 0, "SMASI[%s]: %s", octstr_get_cstr(id), msg);
99 smasi_pdu_dump(pdu);
100 }
101 #endif
102
103
104 /************************************************************************/
105 /* DEFAULT SETTINGS */
106 /************************************************************************/
107
108 #define SMASI_DEFAULT_PORT 21500
109 #define SMASI_DEFAULT_PRIORITY 0
110 #define MAX_PENDING_SUBMITS 10
111 #define SMASI_THROTTLING_SLEEP_TIME 15
112 #define SMASI_ENQUIRE_LINK_INTERVAL 30.0
113
114
115 /************************************************************************/
116 /* OVERRIDE SETTINGS */
117 /************************************************************************/
118
119 /* Set these to -1 if no override desired. Values carried in message will
120 * be used then. Or the defaults - if message has no values.
121 *
122 * Otherwise these values will be forced!
123 */
124
125 #define SMASI_OVERRIDE_SOURCE_TON 1
126 #define SMASI_OVERRIDE_SOURCE_NPI -1
127 #define SMASI_OVERRIDE_DEST_TON -1
128 #define SMASI_OVERRIDE_DEST_NPI -1
129
130
131 /************************************************************************/
132 /* SMASI STRUCTURE AND RELATED FUNCTIONS */
133 /************************************************************************/
134
135 typedef struct {
136 SMSCConn * conn; /* connection to the bearerbox */
137 int thread_handle; /* handle for the SMASI thread */
138 List *msgs_to_send;
139 Dict *sent_msgs; /* hash table for send, but yet not confirmed */
140 List *received_msgs; /* list of received, but yet not processed */
141 Counter *message_id_counter; /* sequence number */
142 Octstr *host; /* host or IP of the SMASI server */
143 long port; /* port to connect to */
144 Octstr *username;
145 Octstr * password;
146 Octstr * my_number;
147 long source_addr_ton;
148 long source_addr_npi;
149 long dest_addr_ton;
150 long dest_addr_npi;
151 long priority;
152 time_t throttling_err_time;
153 int quitting;
154 long enquire_link_interval;
155 int logged_off;
156 } SMASI;
157
158
smasi_create(SMSCConn * conn)159 static SMASI *smasi_create(SMSCConn *conn)
160 {
161
162 SMASI *smasi = gw_malloc(sizeof(SMASI));
163
164 smasi->conn = conn;
165
166 smasi->thread_handle = -1;
167 smasi->msgs_to_send = gwlist_create();
168 smasi->sent_msgs = dict_create(16, NULL);
169 smasi->received_msgs = gwlist_create();
170 smasi->message_id_counter = counter_create();
171 smasi->host = NULL;
172 smasi->username = NULL;
173 smasi->password = NULL;
174 smasi->source_addr_ton = -1;
175 smasi->source_addr_npi = -1;
176 smasi->dest_addr_ton = -1;
177 smasi->dest_addr_npi = -1;
178 smasi->my_number = NULL;
179 smasi->port = 21500;
180 smasi->quitting = 0;
181 smasi->logged_off = 0;
182 smasi->priority = 0;
183 smasi->throttling_err_time = 0;
184 smasi->enquire_link_interval = 30;
185
186 gwlist_add_producer(smasi->msgs_to_send);
187
188 return smasi;
189 }
190
191
smasi_destroy(SMASI * smasi)192 static void smasi_destroy(SMASI *smasi)
193 {
194 if (smasi == NULL) return;
195
196 gwlist_destroy(smasi->msgs_to_send, msg_destroy_item);
197 dict_destroy(smasi->sent_msgs);
198 gwlist_destroy(smasi->received_msgs, msg_destroy_item);
199 counter_destroy(smasi->message_id_counter);
200 octstr_destroy(smasi->host);
201 octstr_destroy(smasi->username);
202 octstr_destroy(smasi->password);
203 gw_free(smasi);
204 }
205
206
207
208 /************************************************************************/
209 /* DATA ENCODING */
210 /************************************************************************/
211
212 /* These values will be initialized on module startup. They contain the
213 * ASCII representation of the chars that need to be escaped in the message
214 * body before transmission. Example: "," (comma) will be represented by
215 * the octet string ":2c".
216 */
217
218 static Octstr *colon = NULL;
219 static Octstr *assign = NULL;
220 static Octstr *comma = NULL;
221 static Octstr *cr = NULL;
222 static Octstr *lf = NULL;
223
224
225 /*
226 * Escapes outgoing message body data by replacing occurrences of "special"
227 * chars inside the octet string.
228 */
escape_data(Octstr * data)229 static void escape_data(Octstr *data)
230 {
231 long pos = 0;
232
233 /* This one uses a different approach than the encode and decode
234 * functions. Because it is assumed, that only a fraction of the
235 * contained chars have to be escaped.
236 */
237 while (pos < octstr_len(data)) {
238 Octstr * escaped = NULL;
239 int check = octstr_get_char(data, pos);
240
241 if (check == ':') escaped = colon;
242 else if (check == '=') escaped = assign;
243 else if (check == ',') escaped = comma;
244 else if (check == '\n') escaped = cr;
245 else if (check == '\r') escaped = lf;
246
247 if (escaped != NULL) {
248 /* If the current char has to be escaped, delete the char from
249 * the source string, replace it with the escape sequence, and
250 * advance position until after the inserted sequence.
251 */
252 octstr_delete(data, pos, 1);
253 octstr_insert(data, escaped, pos);
254 pos += octstr_len(escaped);
255 } else {
256 /* If not escaped, simply skip the current char. */
257 pos++;
258 }
259 }
260 }
261
262
263 /*
264 * Unescapes incoming message body data by replacing occurrences of escaped
265 * chars with their original character representation.
266 */
unescape_data(Octstr * data)267 static void unescape_data(Octstr *data)
268 {
269 long pos = 0;
270
271 /* Again, an inplace transformation is used. Because, again, it is
272 * assumed that only a fraction of chars has to be unescaped.
273 */
274 while (pos < octstr_len(data)) {
275 int check = octstr_get_char(data, pos);
276
277 if (check == ':') {
278 char byte = 0;
279 int msb = octstr_get_char(data, pos + 1);
280 int lsb = octstr_get_char(data, pos + 2);
281
282 if (msb == '0') msb = 0;
283 else if (msb >= '1' && msb <= '9') msb -= '1' + 1;
284 else msb -= 'a' + 10;
285
286 if (lsb == '0') lsb = 0;
287 else if (lsb >= '1' && lsb <= '9') lsb -= '1' + 1;
288 else lsb -= 'a' + 10;
289
290 byte = msb << 4 | lsb;
291
292 /* Do inplace unescaping. */
293 octstr_delete(data, pos, 3);
294 octstr_insert_data(data, pos, &byte, 1);
295 }
296 pos++;
297 }
298 }
299
300
301 /*
302 * Will replace a binary data octet string (inplace) with a SMASI conform
303 * ASCII representation of the data.
304 */
encode_binary_data(Octstr * data)305 static void encode_binary_data(Octstr *data)
306 {
307 Octstr *result = octstr_create("");
308 long pos = 0;
309
310 while (pos < octstr_len(data)) {
311 int encode = octstr_get_char(data, pos);
312 int msb = (encode & 0xf0) >> 4;
313 int lsb = (encode & 0x0f) >> 0;
314
315 if (msb == 0) msb = '0';
316 else if (msb < 10) msb = '1' + msb - 1;
317 else msb = 'a' + msb - 10;
318
319 if (lsb == 0) lsb = '0';
320 else if (lsb < 10) lsb = '1' + lsb - 1;
321 else lsb = 'a' + lsb - 10;
322
323 octstr_append_char(result, ':');
324 octstr_append_char(result, msb);
325 octstr_append_char(result, lsb);
326
327 pos++;
328 }
329 /* Replace binary data octet string with ASCII representation. */
330 octstr_delete(data, 0, octstr_len(data));
331 octstr_append(data, result);
332 octstr_destroy(result);
333 }
334
335
336 /*
337 * Re-escape SMASI ASCII representation of binary data with the
338 * original binary data octet string.
339 * XXX this may be done by the internal parser routines too.
340 */
decode_binary_data(Octstr * data)341 static void decode_binary_data(Octstr *data)
342 {
343 long pos = 0;
344
345 while (pos < octstr_len(data)) {
346 int check = octstr_get_char(data, pos);
347
348 if (check == ':') {
349 Octstr *byte;
350 int msb = octstr_get_char(data, pos + 1);
351 int lsb = octstr_get_char(data, pos + 2);
352
353 if (msb != -1 && lsb != -1) {
354 byte = octstr_create("");
355 octstr_append_char(byte, msb);
356 octstr_append_char(byte, lsb);
357
358 if (octstr_hex_to_binary(byte) != -1) {
359 /* Do inplace unescaping. */
360 octstr_delete(data, pos, 3);
361 octstr_insert(data, byte, pos);
362 } else {
363 error(0, "Malformed binary encoded data.");
364 }
365
366 octstr_destroy(byte);
367 }
368 }
369 pos++;
370 }
371 }
372
373
374 /************************************************************************/
375 /* MESSAGE PROCESSING */
376 /************************************************************************/
377
get_ton_npi_value(int override,int message)378 static Octstr *get_ton_npi_value(int override, int message)
379 {
380 if (override != -1) {
381 debug("bb.sms.smasi", 0, "SMASI: Manually forced ton or npi to `%d'",
382 override);
383 return (octstr_format("%ld", override));
384 } else {
385 return (octstr_format("%ld", message));
386 }
387 }
388
389
390 /*
391 * Gets the value to be used as source_addr_ton. Will use override values
392 * if configured. Will use values from message otherwise. Or fall back to
393 * defaults if nothing given.
394 */
get_source_addr_ton(SMASI * smasi,Msg * msg)395 static Octstr *get_source_addr_ton(SMASI *smasi, Msg *msg)
396 {
397 return get_ton_npi_value(smasi->source_addr_ton,
398 GSM_ADDR_TON_INTERNATIONAL);
399 }
400
401
402 /*
403 * Gets the value to be used as source_addr_npi. Will use override values
404 * if configured. Will use values from message otherwise. Or fall back to
405 * defaults if nothing given.
406 */
get_source_addr_npi(SMASI * smasi,Msg * msg)407 static Octstr *get_source_addr_npi(SMASI *smasi, Msg *msg)
408 {
409 return get_ton_npi_value(smasi->source_addr_npi,
410 GSM_ADDR_NPI_E164);
411 }
412
413
414 /*
415 * Gets the value to be used as dest_addr_ton. Will use override values
416 * if configured. Will use values from message otherwise. Or fall back to
417 * defaults if nothing given.
418 */
get_dest_addr_ton(SMASI * smasi,Msg * msg)419 static Octstr *get_dest_addr_ton(SMASI *smasi, Msg *msg)
420 {
421 return get_ton_npi_value(smasi->dest_addr_ton,
422 GSM_ADDR_TON_INTERNATIONAL);
423 }
424
425
426 /*
427 * Gets the value to be used as dest_addr_npi. Will use override values
428 * if configured. Will use values from message otherwise. Or fall back to
429 * defaults if nothing given.
430 */
get_dest_addr_npi(SMASI * smasi,Msg * msg)431 static Octstr *get_dest_addr_npi(SMASI *smasi, Msg *msg)
432 {
433 return get_ton_npi_value(smasi->dest_addr_npi,
434 GSM_ADDR_NPI_E164);
435 }
436
437
438 /*
439 * Determine the originator (sender number) type based on the number. Will
440 * change the originator number if necessary.
441 */
get_originator_type(SMASI * smasi,Octstr * originator)442 static Octstr *get_originator_type(SMASI *smasi, Octstr *originator)
443 {
444 /* International or alphanumeric sender? */
445 if (octstr_get_char(originator, 0) == '+') {
446 if (!octstr_check_range(originator, 1, 256, gw_isdigit)) {
447 return octstr_format("%ld", GSM_ADDR_TON_ALPHANUMERIC);
448 } else {
449 /* Numeric sender address with + in front: The + has to be
450 * removed from this international number.
451 */
452 octstr_delete(originator, 0, 1);
453 return octstr_format("%ld", GSM_ADDR_TON_INTERNATIONAL);
454 }
455 } else if (!octstr_check_range(originator, 0, 256, gw_isdigit)) {
456 return octstr_format("%ld", GSM_ADDR_TON_ALPHANUMERIC);
457 }
458
459 /* Return the default value. */
460 return octstr_format("%ld", GSM_ADDR_TON_INTERNATIONAL);
461 }
462
463
464 /*
465 * Creates a SubmitReq PDU from an outgoing message.
466 */
msg_to_pdu(SMASI * smasi,Msg * msg)467 static SMASI_PDU *msg_to_pdu(SMASI *smasi, Msg *msg)
468 {
469 SMASI_PDU *pdu = smasi_pdu_create(SubmitReq);
470
471 pdu->u.SubmitReq.Destination = octstr_duplicate(msg->sms.receiver);
472 pdu->u.SubmitReq.Body = octstr_duplicate(msg->sms.msgdata);
473 pdu->u.SubmitReq.Originator = octstr_duplicate(msg->sms.sender);
474
475 pdu->u.SubmitReq.OriginatorType =
476 get_originator_type(smasi, pdu->u.SubmitReq.Originator);
477
478 pdu->u.SubmitReq.Sequence =
479 octstr_format("%ld", counter_increase(smasi->message_id_counter));
480
481
482 /* If its a international number starting with +, lets remove the +. */
483 if (octstr_get_char(pdu->u.SubmitReq.Destination, 0) == '+')
484 octstr_delete(pdu->u.SubmitReq.Destination, 0, 1);
485
486 /* Do ton and npi override - if configured. Use values from message
487 * otherwise.
488 */
489 pdu->u.SubmitReq.OriginatorType = get_source_addr_ton(smasi, msg);
490 pdu->u.SubmitReq.OriginatorPlan = get_source_addr_npi(smasi, msg);
491 pdu->u.SubmitReq.DestinationType = get_dest_addr_ton(smasi, msg);
492 pdu->u.SubmitReq.DestinationPlan = get_dest_addr_npi(smasi, msg);
493
494 /* Set priority. */
495 if (smasi->priority >= 0 && smasi->priority <= 3) {
496 pdu->u.SubmitReq.MqPriority = octstr_format("%ld", smasi->priority);
497 } else {
498 pdu->u.SubmitReq.MqPriority = octstr_format("%ld", 0);
499 }
500
501 /* Set encoding. */
502 if (msg->sms.coding != DC_UNDEF) {
503 if (msg->sms.coding == DC_7BIT)
504 pdu->u.SubmitReq.MsEncoding = octstr_create("7bit");
505 else if (msg->sms.coding == DC_8BIT)
506 pdu->u.SubmitReq.MsEncoding = octstr_create("8bit");
507 else if (msg->sms.coding == DC_UCS2)
508 pdu->u.SubmitReq.MsEncoding = octstr_create("16bit");
509
510 /* Everything else will default to 7bit. */
511 }
512
513 /* Set messaging class - if within defined parameter range. */
514 if (msg->sms.mclass != MC_UNDEF)
515 pdu->u.SubmitReq.Class = octstr_format("%ld", msg->sms.mclass);
516
517 /* Set Protocol ID. */
518 pdu->u.SubmitReq.ProtocolID = octstr_format("%ld",
519 (msg->sms.pid == SMS_PARAM_UNDEFINED ? 0 : msg->sms.pid));
520
521 /* Check if SMS is binary. */
522 if (msg->sms.udhdata && octstr_len(msg->sms.udhdata) > 0) {
523
524 pdu->u.SubmitReq.UserDataHeader =
525 octstr_duplicate(msg->sms.udhdata);
526
527 pdu->u.SubmitReq.BodyEncoding =
528 octstr_create("Data");
529
530 if (pdu->u.SubmitReq.MsEncoding)
531 octstr_destroy(pdu->u.SubmitReq.MsEncoding);
532
533 pdu->u.SubmitReq.MsEncoding =
534 octstr_create("transparent");
535
536 /* Encode data. */
537 encode_binary_data(pdu->u.SubmitReq.UserDataHeader);
538 encode_binary_data(pdu->u.SubmitReq.Body);
539 } else {
540
541 /* Otherwise do data escaping. */
542 escape_data(pdu->u.SubmitReq.Body);
543 }
544
545 return pdu;
546 }
547
548
549 /*
550 * Create a message structure from an incoming DeliverReq PDU.
551 */
pdu_to_msg(SMASI_PDU * pdu)552 static Msg *pdu_to_msg(SMASI_PDU *pdu)
553 {
554 Msg *msg = NULL;
555
556 gw_assert(pdu->type == DeliverReq);
557 gw_assert(pdu->u.DeliverReq.Originator);
558 gw_assert(pdu->u.DeliverReq.Destination);
559 gw_assert(pdu->u.DeliverReq.Body);
560
561 msg = msg_create(sms);
562
563 msg->sms.sender = octstr_duplicate(pdu->u.DeliverReq.Originator);
564 msg->sms.receiver = octstr_duplicate(pdu->u.DeliverReq.Destination);
565 msg->sms.msgdata = octstr_duplicate(pdu->u.DeliverReq.Body);
566
567 /* Read priority. */
568 if (pdu->u.DeliverReq.ProtocolId)
569 if (octstr_parse_long(&msg->sms.pid,
570 pdu->u.DeliverReq.ProtocolId, 0, 10) == -1)
571 msg->sms.pid = SMS_PARAM_UNDEFINED;
572
573 /* Read Coding. */
574 if (pdu->u.DeliverReq.MsEncoding) {
575
576 /* Use specified coding. */
577 if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "7bit") == 0)
578 msg->sms.coding = DC_7BIT;
579 else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "8bit") == 0)
580 msg->sms.coding = DC_8BIT;
581 else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "UCS2") == 0)
582 msg->sms.coding = DC_UCS2;
583 else if (octstr_str_compare(pdu->u.DeliverReq.MsEncoding, "transparent") == 0)
584 msg->sms.coding = DC_8BIT;
585 } else {
586
587 /* Determine specified coding according to udhdata presence. */
588 if (pdu->u.DeliverReq.UserDataHeader)
589 msg->sms.coding = DC_8BIT;
590 else
591 msg->sms.coding = DC_7BIT;
592 }
593
594 /* Unescape (non-binary) or decode (binary) data. */
595 if (msg->sms.coding == DC_8BIT) {
596
597 decode_binary_data(msg->sms.msgdata);
598 if (pdu->u.DeliverReq.UserDataHeader &&
599 octstr_len(pdu->u.DeliverReq.UserDataHeader) > 0) {
600 msg->sms.udhdata = octstr_duplicate(pdu->u.DeliverReq.UserDataHeader);
601 decode_binary_data(msg->sms.udhdata);
602 }
603
604 } else {
605 unescape_data(msg->sms.msgdata);
606 }
607
608 /* Read message class. */
609 if (pdu->u.DeliverReq.Class &&
610 octstr_parse_long(&msg->sms.mclass,
611 pdu->u.DeliverReq.Class, 0, 10) == -1)
612 msg->sms.mclass = MC_UNDEF; /* Set to unspecified. */
613
614 /* Read protocol ID. */
615 if (pdu->u.DeliverReq.ProtocolId &&
616 octstr_parse_long(&msg->sms.pid,
617 pdu->u.DeliverReq.ProtocolId, 0, 10) == -1)
618 msg->sms.pid = SMS_PARAM_UNDEFINED;
619
620 return msg;
621 }
622
623
624 /************************************************************************/
625 /* PDU HANDLING */
626 /************************************************************************/
627
send_logoff(SMASI * smasi,Connection * conn)628 static void send_logoff(SMASI *smasi, Connection *conn)
629 {
630 SMASI_PDU *pdu = NULL;
631 Octstr *os = NULL;
632
633 counter_increase(smasi->message_id_counter);
634
635 pdu = smasi_pdu_create(LogoffReq);
636 pdu->u.LogoffReq.Reason = octstr_create("Client shutting down");
637 dump_pdu("Sending !LogoffReq:", smasi->conn->id, pdu);
638
639 os = smasi_pdu_pack(pdu);
640 conn_write(conn, os);
641 octstr_destroy(os);
642 smasi_pdu_destroy(pdu);
643 }
644
645
send_enquire_link(SMASI * smasi,Connection * conn,long * last_sent)646 static void send_enquire_link(SMASI *smasi, Connection *conn, long *last_sent)
647 {
648 SMASI_PDU *pdu = NULL;
649 Octstr *os = NULL;
650
651 if (date_universal_now() - *last_sent < smasi->enquire_link_interval)
652 return;
653 *last_sent = date_universal_now();
654
655 pdu = smasi_pdu_create(EnquireLinkReq);
656 dump_pdu("Sending EnquireLinkReq:", smasi->conn->id, pdu);
657 os = smasi_pdu_pack(pdu);
658 if (os)
659 conn_write(conn, os); /* Write errors checked by caller. */
660 octstr_destroy(os);
661 smasi_pdu_destroy(pdu);
662 }
663
664
send_pdu(Connection * conn,Octstr * id,SMASI_PDU * pdu)665 static int send_pdu(Connection *conn, Octstr *id, SMASI_PDU *pdu)
666 {
667 Octstr * os = NULL;
668 int ret = 0;
669
670 dump_pdu("Sending PDU:", id, pdu);
671 os = smasi_pdu_pack(pdu);
672 if (os) ret = conn_write(conn, os);
673 else ret = -1;
674
675 octstr_destroy(os);
676 return ret;
677 }
678
679
680 /*
681 * Try to read a SMASI PDU from a connection. Return -1 for error (caller
682 * should close the connection), 0 for no PDU ready yet, or 1 for PDU read
683 * and unpacked. Return a pointer to the PDU in `*pdu'.
684 */
read_pdu(SMASI * smasi,Connection * conn,SMASI_PDU ** pdu)685 static int read_pdu(SMASI *smasi, Connection *conn, SMASI_PDU **pdu)
686 {
687 Octstr *os;
688
689 os = smasi_pdu_read(conn);
690 if (os == NULL) {
691 if (conn_eof(conn) || conn_error(conn))
692 return -1;
693 return 0;
694 }
695
696 *pdu = smasi_pdu_unpack(os);
697 if (*pdu == NULL) {
698 error(0, "SMASI[%s]: PDU unpacking failed.",
699 octstr_get_cstr(smasi->conn->id));
700 debug("bb.sms.smasi", 0, "SMASI[%s]: Failed PDU follows.",
701 octstr_get_cstr(smasi->conn->id));
702 octstr_dump(os, 0);
703 octstr_destroy(os);
704 return -1;
705 }
706 octstr_destroy(os);
707 return 1;
708 }
709
710
handle_pdu(SMASI * smasi,Connection * conn,SMASI_PDU * pdu,long * pending_submits)711 static void handle_pdu(SMASI *smasi, Connection *conn,
712 SMASI_PDU *pdu, long *pending_submits)
713 {
714 SMASI_PDU *resp = NULL;
715 Msg *msg = NULL;
716 long reason;
717
718 switch (pdu->type) {
719
720 case DeliverReq:
721 msg = pdu_to_msg(pdu);
722
723 msg_dump(msg, 0);
724
725 if (smasi->my_number && octstr_len(smasi->my_number)) {
726 octstr_destroy(msg->sms.receiver);
727 msg->sms.receiver = octstr_duplicate(smasi->my_number);
728 }
729
730 time(&msg->sms.time);
731 msg->sms.smsc_id = octstr_duplicate(smasi->conn->id);
732 bb_smscconn_receive(smasi->conn, msg);
733 resp = smasi_pdu_create(DeliverConf);
734
735 if (pdu->u.DeliverReq.Sequence)
736 resp->u.DeliverConf.Sequence =
737 octstr_duplicate(pdu->u.DeliverReq.Sequence);
738
739 if (pdu->u.DeliverReq.MsgReference)
740 resp->u.DeliverConf.MsgReference =
741 octstr_duplicate(pdu->u.DeliverReq.MsgReference);
742 break;
743
744 case SubmitConf:
745 if (pdu->u.SubmitConf.Sequence) {
746 msg = dict_remove(smasi->sent_msgs,
747 pdu->u.SubmitConf.Sequence);
748 } else {
749 msg = NULL;
750 }
751
752 if (msg == NULL) {
753 warning(0, "SMASI[%s]: SMSC sent SubmitConf for unknown message.",
754 octstr_get_cstr(smasi->conn->id));
755 } else {
756 debug("bb.sms.smasi",0,
757 "SMSC[%s]: SMSC confirmed msg seq <%s> ref <%s>",
758 octstr_get_cstr(smasi->conn->id),
759 octstr_get_cstr(pdu->u.SubmitConf.Sequence),
760 octstr_get_cstr(pdu->u.SubmitConf.MsgReference));
761
762 bb_smscconn_sent(smasi->conn, msg, NULL);
763
764 --(*pending_submits);
765 }
766 break;
767
768 case SubmitRej:
769 if (pdu->u.SubmitRej.Sequence) {
770 msg = dict_remove(smasi->sent_msgs,
771 pdu->u.SubmitRej.Sequence);
772 } else {
773 msg = NULL;
774 }
775
776 error(0, "SMASI[%s]: SMSC returned error code %s for "
777 "message ref <%s>", octstr_get_cstr(smasi->conn->id),
778 octstr_get_cstr(pdu->u.SubmitRej.RejectCode),
779 octstr_get_cstr(pdu->u.SubmitRej.MsgReference));
780
781 if (msg == NULL) {
782 warning(0, "SMASI[%s]: SMSC sent SubmitRej for unknown message.",
783 octstr_get_cstr(smasi->conn->id));
784 } else {
785 reason = SMSCCONN_FAILED_REJECTED;
786 bb_smscconn_send_failed(smasi->conn, msg, reason,
787 octstr_create("REJECTED"));
788 --(*pending_submits);
789 }
790 break;
791
792 case LogonConf:
793 *pending_submits = 0;
794
795 smasi->conn->status = SMSCCONN_ACTIVE;
796 smasi->conn->connect_time = time(NULL);
797
798 bb_smscconn_connected(smasi->conn);
799
800 info(0, "SMASI[%s]: connection to SMSC established.",
801 octstr_get_cstr(smasi->conn->id));
802 break;
803
804 case LogonRej:
805 if (octstr_len(pdu->u.LogonRej.Reason) > 0) {
806 error(0, "SMASI[%s]: SMSC rejected login with reason <%s>",
807 octstr_get_cstr(smasi->conn->id),
808 octstr_get_cstr(pdu->u.LogonRej.Reason));
809 } else {
810 error(0, "SMASI[%s]: SMSC rejected login without reason",
811 octstr_get_cstr(smasi->conn->id));
812 }
813 break;
814
815 case LogoffConf:
816 info(0, "SMASI[%s]: SMSC confirmed logoff.",
817 octstr_get_cstr(smasi->conn->id));
818 smasi->logged_off = 1;
819 break;
820
821 default:
822 warning(0, "SMASI[%s]: Unknown PDU type <%s>, ignored.",
823 octstr_get_cstr(smasi->conn->id), pdu->type_name);
824 break;
825 }
826
827 if (resp != NULL) {
828 send_pdu(conn, smasi->conn->id, resp);
829 smasi_pdu_destroy(resp);
830 }
831 }
832
833
834 /************************************************************************/
835 /* SMASI CONNECTION HANDLING */
836 /************************************************************************/
837
838 /*
839 * Open transmission connection to SMS center. Return NULL for error,
840 * open connection for OK. Caller must set smasi->conn->status correctly
841 * before calling this.
842 */
open_connection(SMASI * smasi)843 static Connection *open_connection(SMASI *smasi)
844 {
845 Connection *conn = conn_open_tcp_with_port(smasi->host, smasi->port, 0, smasi->conn->our_host);
846
847 if (conn == NULL) {
848 error(0, "SMASI[%s]: Couldn't connect to server.",
849 octstr_get_cstr(smasi->conn->id));
850 return NULL;
851 } else {
852 SMASI_PDU *logon = smasi_pdu_create(LogonReq);
853
854 logon->u.LogonReq.Name = octstr_duplicate(smasi->username);
855 logon->u.LogonReq.Password = octstr_duplicate(smasi->password);
856
857 counter_increase(smasi->message_id_counter);
858
859 send_pdu(conn, smasi->conn->id, logon);
860
861 smasi_pdu_destroy(logon);
862 }
863
864 return conn;
865 }
866
867
send_messages(SMASI * smasi,Connection * conn,long * pending_submits)868 static void send_messages(SMASI *smasi, Connection *conn,
869 long *pending_submits)
870 {
871 double delay = 0;
872
873 if (*pending_submits == -1) return;
874
875 if (smasi->conn->throughput > 0) {
876 delay = 1.0 / smasi->conn->throughput;
877 }
878
879 while (*pending_submits < MAX_PENDING_SUBMITS) {
880 SMASI_PDU *pdu = NULL;
881 /* Get next message, quit if none to be sent. */
882 Msg *msg = gwlist_extract_first(smasi->msgs_to_send);
883
884 if (msg == NULL) break;
885
886 /* Send PDU, record it as waiting for ack from SMSC. */
887 pdu = msg_to_pdu(smasi, msg);
888
889 if (pdu->u.SubmitReq.Sequence)
890 dict_put(smasi->sent_msgs, pdu->u.SubmitReq.Sequence, msg);
891
892 send_pdu(conn, smasi->conn->id, pdu);
893
894 smasi_pdu_destroy(pdu);
895
896 /* obey throughput speed limit, if any */
897 if (smasi->conn->throughput > 0)
898 gwthread_sleep(delay);
899
900 ++(*pending_submits);
901 }
902 }
903
904
905 /*
906 * This is the main function for the background thread for doing I/O on
907 * one SMASI connection (the one for transmitting or receiving messages).
908 * It makes the initial connection to the SMASI server and re-connects
909 * if there are I/O errors or other errors that require it.
910 */
smasi_thread(void * arg)911 static void smasi_thread(void *arg)
912 {
913 long pending_submits;
914 SMASI_PDU *pdu;
915 SMASI *smasi;
916 int logoff_already_sent = 0;
917 int ret;
918 Connection *conn;
919 long last_enquire_sent;
920 double timeout;
921
922 smasi = arg;
923
924 /* Make sure we log into our own log-file if defined */
925 log_thread_to(smasi->conn->log_idx);
926
927 while (!smasi->quitting) {
928
929 conn = open_connection(smasi);
930 if (conn == NULL) {
931 error(0, "SMASI[%s]: Could not connect to SMSC center " \
932 "(retrying in %ld seconds).",
933 octstr_get_cstr(smasi->conn->id), smasi->conn->reconnect_delay);
934
935 gwthread_sleep(smasi->conn->reconnect_delay);
936 smasi->conn->status = SMSCCONN_RECONNECTING;
937 continue;
938 }
939
940 last_enquire_sent = date_universal_now();
941 pending_submits = -1;
942
943 for (;;) {
944 timeout = last_enquire_sent + smasi->enquire_link_interval
945 - date_universal_now();
946
947 /* wait for activity */
948 if (conn_wait(conn, timeout) == -1) {
949 error(0, "SMASI[%s]: I/O error or other error. Re-connecting.",
950 octstr_get_cstr(smasi->conn->id));
951 break;
952 }
953
954 /* Send logoff request if module is shutting down. */
955 if (smasi->quitting && !logoff_already_sent) {
956 send_logoff(smasi, conn);
957 logoff_already_sent = 1;
958 }
959
960 /* send an enquire link */
961 send_enquire_link(smasi, conn, &last_enquire_sent);
962
963 /* Receive incoming PDUs. */
964 while ((ret = read_pdu(smasi, conn, &pdu)) == 1) {
965 /* Deal with the PDU we just got */
966 dump_pdu("Got PDU:", smasi->conn->id, pdu);
967
968 /* Process the received PDU. */
969 handle_pdu(smasi, conn, pdu, &pending_submits);
970
971 smasi_pdu_destroy(pdu);
972
973 /* Bail out if logoff confirmed. */
974 if (smasi->logged_off) break;
975
976 /* Make sure we send even if we read a lot. */
977 if ((!smasi->throttling_err_time ||
978 ((time(NULL) - smasi->throttling_err_time) >
979 SMASI_THROTTLING_SLEEP_TIME
980 && !(smasi->throttling_err_time = 0))))
981 send_messages(smasi, conn, &pending_submits);
982 }
983
984 /* Check if connection broken. */
985 if (ret == -1) {
986 error(0, "SMASI[%s]: I/O error or other error. Re-connecting.",
987 octstr_get_cstr(smasi->conn->id));
988 break;
989 }
990
991 /* Bail out if logoff confirmed. */
992 if (smasi->logged_off) break;
993
994 if ((!smasi->throttling_err_time ||
995 ((time(NULL) - smasi->throttling_err_time) >
996 SMASI_THROTTLING_SLEEP_TIME
997 && !(smasi->throttling_err_time = 0))))
998 send_messages(smasi, conn, &pending_submits);
999
1000 }
1001
1002 conn_destroy(conn);
1003 conn = NULL;
1004 }
1005 }
1006
1007
1008 /************************************************************************/
1009 /* SMSCCONN INTERFACE */
1010 /************************************************************************/
1011
queued_cb(SMSCConn * conn)1012 static long queued_cb(SMSCConn *conn)
1013 {
1014 SMASI *smasi = conn->data;
1015
1016 conn->load = (smasi ? (conn->status != SMSCCONN_DEAD ?
1017 gwlist_len(smasi->msgs_to_send) : 0) : 0);
1018
1019 return conn->load;
1020 }
1021
1022
send_msg_cb(SMSCConn * conn,Msg * msg)1023 static int send_msg_cb(SMSCConn *conn, Msg *msg)
1024 {
1025 SMASI *smasi = conn->data;
1026
1027 gwlist_produce(smasi->msgs_to_send, msg_duplicate(msg));
1028 gwthread_wakeup(smasi->thread_handle);
1029
1030 return 0;
1031 }
1032
1033
shutdown_cb(SMSCConn * conn,int finish_sending)1034 static int shutdown_cb(SMSCConn *conn, int finish_sending)
1035 {
1036 SMASI *smasi = NULL;
1037
1038 debug("bb.sms.smasi", 0, "Shutting down SMSCConn %s (%s)",
1039 octstr_get_cstr(conn->name), finish_sending ? "slow" : "instant");
1040
1041 conn->why_killed = SMSCCONN_KILLED_SHUTDOWN;
1042
1043 smasi = conn->data;
1044 smasi->quitting = 1;
1045 gwthread_wakeup(smasi->thread_handle);
1046 gwthread_join(smasi->thread_handle);
1047 smasi_destroy(smasi);
1048
1049 debug("bb.sms.smasi", 0, "SMSCConn %s shut down.",
1050 octstr_get_cstr(conn->name));
1051 conn->status = SMSCCONN_DEAD;
1052 bb_smscconn_killed();
1053
1054 /* Clean up. */
1055 octstr_destroy(colon);
1056 octstr_destroy(assign);
1057 octstr_destroy(comma);
1058 octstr_destroy(cr);
1059 octstr_destroy(lf);
1060
1061 return 0;
1062 }
1063
1064
1065 /*
1066 * Configures the SMASI structure according to the configuration.
1067 *
1068 * @return 0 on complete success. -1 if failed due to missing or invalid
1069 * configuration entry.
1070 */
init_configuration(SMASI * smasi,CfgGroup * config)1071 static int init_configuration(SMASI *smasi, CfgGroup *config)
1072 {
1073 /* Read mandatory entries. */
1074 smasi->host = cfg_get(config, octstr_imm("host"));
1075 smasi->username = cfg_get(config, octstr_imm("smsc-username"));
1076 smasi->password = cfg_get(config, octstr_imm("smsc-password"));
1077
1078 /* Check configuration. */
1079 if (smasi->host == NULL) {
1080 error(0,"SMASI: Configuration file doesn't specify host");
1081 return -1;
1082 }
1083 if (smasi->username == NULL) {
1084 error(0, "SMASI: Configuration file doesn't specify username.");
1085 return -1;
1086 }
1087 if (smasi->password == NULL) {
1088 error(0, "SMASI: Configuration file doesn't specify password.");
1089 return -1;
1090 }
1091
1092 /* Read optional entries. Set default values if not set. */
1093 smasi->my_number = cfg_get(config, octstr_imm("my-number"));
1094 if (cfg_get_integer(&smasi->port, config, octstr_imm("port")) == -1)
1095 smasi->port = SMASI_DEFAULT_PORT;
1096 if (cfg_get_integer(&smasi->source_addr_ton, config,
1097 octstr_imm("source-addr-ton")) == -1)
1098 smasi->source_addr_ton = SMASI_OVERRIDE_SOURCE_TON;
1099 if (cfg_get_integer(&smasi->source_addr_npi, config,
1100 octstr_imm("source-addr-npi")) == -1)
1101 smasi->source_addr_npi = SMASI_OVERRIDE_SOURCE_NPI;
1102 if (cfg_get_integer(&smasi->dest_addr_ton, config,
1103 octstr_imm("dest-addr-ton")) == -1)
1104 smasi->dest_addr_ton = SMASI_OVERRIDE_DEST_TON;
1105 if (cfg_get_integer(&smasi->dest_addr_npi, config,
1106 octstr_imm("dest-addr-npi")) == -1)
1107 smasi->dest_addr_npi = SMASI_OVERRIDE_DEST_NPI;
1108 if (cfg_get_integer(&smasi->priority, config,
1109 octstr_imm("priority")) == -1)
1110 smasi->priority = SMASI_DEFAULT_PRIORITY;
1111 if (cfg_get_integer(&smasi->enquire_link_interval, config,
1112 octstr_imm("enquire-link-interval")) == -1)
1113 smasi->enquire_link_interval = SMASI_ENQUIRE_LINK_INTERVAL;
1114
1115 /* Configure SMSC connection. */
1116 smasi->conn->data = smasi;
1117 smasi->conn->name = octstr_format("SMASI:%S:%d:%S",
1118 smasi->host, smasi->port, smasi->username);
1119
1120 smasi->conn->id = cfg_get(config, octstr_imm("smsc-id"));
1121
1122 if (smasi->conn->id == NULL)
1123 smasi->conn->id = octstr_duplicate(smasi->conn->name);
1124
1125 return 0;
1126 }
1127
1128
smsc_smasi_create(SMSCConn * conn,CfgGroup * config)1129 int smsc_smasi_create(SMSCConn *conn, CfgGroup *config)
1130 {
1131 SMASI *smasi = NULL;
1132
1133 /* Initialize data encoding subsystem. */
1134 colon = octstr_create(":3a");
1135 assign = octstr_create(":3d");
1136 comma = octstr_create(":2c");
1137 cr = octstr_create(":0a");
1138 lf = octstr_create(":0d");
1139
1140 /* Create main SMASI structure and initialize it with configuration
1141 * settings.
1142 */
1143 smasi = smasi_create(conn);
1144
1145 if (init_configuration(smasi, config) != 0)
1146 panic(0, "SMASI SMSC module configuration invalid.");
1147
1148 conn->status = SMSCCONN_CONNECTING;
1149
1150 /* Port is always set to a configured value or defaults to 21500.
1151 * Therefore, threads are always started.
1152 */
1153 smasi->thread_handle = gwthread_create(smasi_thread, smasi);
1154
1155 if (smasi->thread_handle == -1) {
1156 error(0, "SMASI[%s]: Couldn't start SMASI thread.",
1157 octstr_get_cstr(smasi->conn->id));
1158 smasi_destroy(conn->data);
1159 return -1;
1160 }
1161
1162 /* Setup control function pointers. */
1163 conn->shutdown = shutdown_cb;
1164 conn->queued = queued_cb;
1165 conn->send_msg = send_msg_cb;
1166
1167 return 0;
1168 }
1169
1170