pax_global_header00006660000000000000000000000064122371257220014515gustar00rootroot0000000000000052 comment=0977e616256b4671a06cddaa62e0cb354bc6e22f smstools-3.1.15/000077500000000000000000000000001223712572200134675ustar00rootroot00000000000000smstools-3.1.15/LICENSE000077500000000000000000000446741223712572200145160ustar00rootroot00000000000000

GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.  
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

one line to give the program's name and an idea of what it does.
Copyright (C) yyyy  name of author

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'.  This is free software, and you are welcome
to redistribute it under certain conditions; type `show c' 
for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written 
by James Hacker.

signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. smstools-3.1.15/Makefile000077500000000000000000000006271223712572200151370ustar00rootroot00000000000000# Makefile # If you change destination of executables, remember to change # startup script (init.d/sms3) too. BINDIR=/usr/local/bin VERSION=$(shell grep package_version src/version.h | cut -f2) compile: cd src && $(MAKE) -$(MAKEFLAGS) install: compile ./install.sh $(BINDIR) uninstall: ./uninstall.sh $(BINDIR) clean: cd src && $(MAKE) -$(MAKEFLAGS) clean package: compile clean ./package.sh smstools-3.1.15/README000077500000000000000000000002711223712572200143520ustar00rootroot00000000000000Please read the LICENSE before using this software. Version 3.x: The online manual and help is on http://smstools3.kekekasvi.com. A copy of the online manual is in the doc directory. smstools-3.1.15/doc/000077500000000000000000000000001223712572200142345ustar00rootroot00000000000000smstools-3.1.15/doc/at-commands.html000077500000000000000000000230351223712572200173330ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

AT Commands used in SMS Server Tools 3

Last updated: 02.09.2010.
Applies for the version of smsd: 3.2beta0.

 Command  Purpose  Function, file  Standard
 AT   Checking if a modem is alive.   initmodem, modem.c   GSM 07.07 
 AT+CGSN   Retrieving identity (IMEI).   initmodem, modem.c   GSM 07.07 
 AT+CHUP   Ending an incoming call.   handlephonecall_ring, modem.c   GSM 07.07 
 AT+CHUP   Ending a voicecall.   sendsms, smsd.c   GSM 07.07 
 AT+CIMI   Retrieving identity (IMSI).   initmodem, modem.c   GSM 07.07 
 AT+CMEE=1   Enable CME result codes.   initmodem, modem.c   GSM 07.07 
 AT+CMGD=?   Checking for incoming messages.   check_memory, modem.c   GSM 07.05 
 AT+CMGD=number   Deleting a message.   deletesms_list, modem.c   GSM 07.05 
 AT+CMGD=number   Deleting a message.   deletesms, modem.c   GSM 07.05 
 AT+CMGF=0   Selecting PDU mode.   initmodem, modem.c   GSM 07.05 
 AT+CMGL=value   Checking for incoming messages.   check_memory, modem.c   GSM 07.05 
 AT+CMGR=number   Reading stored message.   readsim, modem.c   GSM 07.05 
 AT+CMGS=number   Sending a message.   send_part, smsd.c   GSM 07.05 
 AT+CNMA   Sending an acknowledgement.   detect_routed_message, modem.c   GSM 07.05 
 AT+CPAS   Checking phone activity status.   sendsms, smsd.c   GSM 07.07 
 AT+CPBR=1,number   Reading missed phonecall entries.   readphonecalls, smsd.c   GSM 07.07 
 AT+CPBR=?   Checking for phonecall limits.   readphonecalls, smsd.c   GSM 07.07 
 AT+CPBS="MC"   Selecting a phonebook.   readphonecalls, smsd.c   GSM 07.07 
 AT+CPBW=number   Removing processed phonecall entries.   readphonecalls, smsd.c   GSM 07.07 
 AT+CPIN?   Checking if a PIN is needed.   initmodem, modem.c   GSM 07.07 
 AT+CPIN="value"   Entering a PIN.   initmodem, modem.c   GSM 07.07 
 AT+CPMS?   Checking for incoming messages.   check_memory, modem.c   GSM 07.05 
 AT+CPMS="name"   Selecting a memory.   receivesms, smsd.c   GSM 07.05 
 AT+CREG?   Checking for network registration.   wait_network_registration, modem.c   GSM 07.07 
 AT+CSCA="+number"   Setting SMSC.   initmodem, modem.c   GSM 07.05 
 AT+CSCS?   Query of TE character set.   cmd_to_modem, modem.c   GSM 07.07 
 AT+CSQ   Retrieving signal quality.   initmodem, modem.c   GSM 07.07 
 ATD+number;   Dialing a voicecall (international number format).   sendsms, smsd.c   GSM 07.07 
 ATDnumber;   Dialing a voicecall (national number format).   sendsms, smsd.c   GSM 07.07 
 ATE0   Disabling the echo.   initmodem, modem.c   GSM 07.07 
 ATH   Ending an incoming call.   handlephonecall_ring, modem.c   GSM 07.07 
 ATH   Ending a voicecall.   sendsms, smsd.c   GSM 07.07 
 AT^SPBD="MC"   Remove phonecall entries.   readphonecalls, smsd.c   (Siemens) 
 AT+VTS=string   Sending DTMF tones (using VTS format).   sendsms, smsd.c   GSM 07.07 

Standards:
GSM 07.07 describes the main commands.
GSM 07.05 describes the additional AT commands (SMS).


smstools-3.1.15/doc/blacklist.html000077500000000000000000000104511223712572200170760ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Blacklist and Whitelist

If you need to disable some receivers to get messages you can create a blacklist file.

This is a simple text file that contains the phone numbers of the receivers that you want to disable. You can add comments that start with an # character.

Example:


 491721234567 # Michael Testman 
 44 # Do not allow any number in UK 
 s # Disallow all short numbers 

Important note:
When you send a message, the destination number may be either in international format or in any format starting with an s. When somebody shall not receive messages, then you need to do either

Example:


 491721234567 # Michael Testman normal international format 
 s00491721234567 # Michael Testman alternative international format 
 s01721234567 # Michael Testman local format 
 s1234567 # Michael Testman alternative local format 

Whitelist

If you need to enable only a specified list of receivers, then you can create a whitelist file. Normally you do not have a whitelist - everybody can receive SM except those who are in the blacklist. If a recipient phone number is in both files it will not get a SM. The blacklist is more important.

Example:


 491721234567 # Stefan Frings 
 491721111111 # Test 
 44 # Allow any destination in UK 
 s8 # Allow all short numbers that start with an 8 

With the smsd version >= 3.1 whitelist can also specify a queue to be used.
This overrides automatic number based selection and can be overridden by the queue setting in the message file.

Example:


 # Messages to Stefan and Test are queued normally, 
 # because any queue is not yet defined. 
 491721234567 # Stefan Frings 
 491721111111 # Test 

 # Messages to UK and s8 numbers will use a queue specified: 
 [QUEUE1] 
 44 # Allow any destination in UK 
 s8 # Allow all short numbers that start with an 8 

 # Other short numbers will use another queue: 
 [QUEUE2] 
 s 

 [] # Setting can be cleared with empty brackets. 
 491722222222 # Another Test, queue not defined. 

Note: You can edit these files without restarting the program.

With the smsd version >= 3.1 you can also make a provider sorting using SQL tables, because message headers are read after the checkhandler is processed.


smstools-3.1.15/doc/book.html000077500000000000000000000004011223712572200160520ustar00rootroot00000000000000 Redirecting to:
http://smstools.meinemullemaus.de/book.html smstools-3.1.15/doc/compiling.html000077500000000000000000000046451223712572200171170ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Compiling

Windows users should follow the Step by Step Instruction for Windows.

Other users should follow these steps:

  1. Log in as root

  2. Install the package:
    gcc GNU Compiler Collection
    make GNU Make
    tar GNU Tape Archiver
    Ubuntu users: you can install necessary packages with sudo apt-get install build-essential manpages-dev

  3. Extract the source package into your preferred directory:
    tar -xzf smstools*.tar.gz
    Solaris users: edit src/Makefile as instructed inside it

  4. Use make to compile and install the sources:
    make
    make install

The book describes the installation procedure of the SMS Server Tools much more in detail. It also explains how to install and use other useful programs like Apache webserver, MySQL, PHP script language, cronjobs, shell scripts, sendmail, fetchmail, formail, sed, awk, cut, grep. All these programs are also available for Windows.

The book is about the version 2.x and written, maintained and owned by Stefan Frings.


smstools-3.1.15/doc/configure.html000077500000000000000000002707771223712572200171320ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Configuring

Menu:

All global settings (in alphabetic order):
 adminmessage_device    admin_to    alarmhandler    alarmlevel    autosplit    blacklist    blockafter    blocktime    checked    checkhandler    date_filename    date_filename_format    datetime_format    decode_unicode_text    delaytime    delaytime_mainprocess    devices    errorsleeptime    eventhandler    executable_check    failed    filename_preview    group    hangup_incoming_call    ic_purge_hours    ic_purge_interval    ic_purge_minutes    ic_purge_read    ignore_exec_output    ignore_outgoing_priority    incoming    incoming_utf8    infofile    internal_combine    internal_combine_binary    international_prefixes    keep_filename    keep_messages    language_file    log_charconv    logfile    loglevel    log_read_from_modem    log_single_lines    logtime_format    logtime_ms    logtime_us    log_unmodified    max_continuous_sending    national_prefixes    os_cygwin    outgoing    phonecalls    pidfile    priviledged_numbers    receive_before_send    regular_run    regular_run_interval    report    saved    sent    shell    shell_test    smart_logging    spool_directory_order    stats    stats_interval    stats_no_zeroes    status_include_counters    status_interval    status_signal_quality    store_original_filename    store_received_pdu    store_sent_pdu    suspend    terminal    trim_text    trust_outgoing    umask    user    validity    voicecall_hangup_ath    whitelist  

All modem settings:
 adminmessage_count_clear    adminmessage_limit    admin_to    baudrate    check_memory_method    check_network    cmgl_value    communication_delay    cs_convert    decode_unicode_text    detect_message_routing    detect_unexpected_input    device    device_open_alarm_after    device_open_retries    eventhandler    eventhandler_ussd    hangup_incoming_call    incoming    init    init2    internal_combine    internal_combine_binary    keep_messages    keep_open    logfile    loglevel    loglevel_lac_ci    log_not_registered_after    max_continuous_sending    memory_start    message_count_clear    messageids    message_limit    mode    modem_disabled    ms_purge_hours    ms_purge_minutes    ms_purge_read    needs_wakeup_at    outgoing    pdu_from_file    phonecalls    phonecalls_error_max    phonecalls_purge    pin    pinsleeptime    pre_init    primary_memory    priviledged_numbers    queues    read_timeout    regular_run    regular_run_cmd    regular_run_cmdfile    regular_run_interval    regular_run_logfile    regular_run_loglevel    regular_run_post_run    regular_run_statfile    report    report_device_details    routed_status_report_cnma    rtscts    secondary_memory    secondary_memory_max    send_delay    send_handshake_select    sending_disabled    signal_quality_ber_ignore    smsc    smsc_pdu    socket_connection_alarm_after    socket_connection_errorsleeptime    socket_connection_retries    start    startsleeptime    status_include_counters    status_signal_quality    stop    telnet_login    telnet_login_prompt    telnet_login_prompt_ignore    telnet_password    telnet_password_prompt    trust_spool    unexpected_input_is_trouble    using_routed_status_report    ussd_convert    verify_pdu    voicecall_clcc    voicecall_cpas    voicecall_hangup_ath    voicecall_ignore_modem_response    voicecall_vts_list    voicecall_vts_quotation_marks  


The config file is /etc/smsd.conf. You may specify another file using the option -c. During installation an easy example file will be copied to smstools3/examples directory.

The config file has the following structure:

Configuring the provider-sorting is shown in different document.

In case of yes/no settings, you can use the following keywords:

After version >= 3.1 also "no" values should be typed correctly, "no" is no more a default.

In case of lists, you need to use the comma character to separate items. Example: modem1, modem2, modem3

After version >= 3.1 multiple different values can be defined for each setting. If smsd is started with command line argument -a (ask), settings with multiple choices are prompted and suitable value can be selected for a run. For example:

A question mark tells that more than one value is defined. Possible values are separated using pipe. In this example the smsd will prompt:
When smsd is started without -a, first value is always choosen.

After version >= 3.1.5 only whole line comments are allowed. For example:
# This comment line is valid.
devices = GSM1 # This kind of comment is invalid.

After version >= 3.1.5 default settings for all modems can be defined in the [default] section. If setup has large count of similar modems, almost all settings can be defined once in [default] section and only device depended settings like device are required to define in the modem sections. In the future versions this will replace all modem settings which are now defined in the global part of configuration.

As "default" is now reserved name, it cannot be used as a modem name.

Global part

The global part begins at the top of the config file.

adminmessage_device = name
Default value: first available modem.
Available from version >= 3.1.5. Defines which modem is used to send administrative messages from the mainspooler. This feature uses shared memory and works only if libmm is installed and statistics functionality is enabled.

admin_to = phone number
Default value: not in use.
Available from version >= 3.1. Specifies a destination number for administrative messages created by smsd. Messages are sent without using the filesystem.

alarmhandler = filename
Default value: not in use.
You can specify here an external program that is started whenever an alarm occurs.
After version >= 3.1 a value can be defined as a word, like LOG_INFO, INFO or info.

alarmlevel = number
Default value: LOG_WARNING.
Specifies what levels start an alarmhandler. You can use value between 2 and 7.

autosplit = number
Default value: 3.
Controls if and how the program splits large text messages. The program does not split text messages with UDH.
If splitting is disabled, binary messages requiring more than one part are not sent.
 0 disabled
 1 enabled, no part-number
 2 enabled, text numbers
 3 enabled, concatenated format (not supported by some phones)

blacklist = filename
Default value: not in use.
Name of the blacklist file.

blockafter = number
Default value: 3.
Available from version >= 3.0.9. A modem is blocked after n number of errors while sending messages. A successfull sending will reset this counter.

blocktime = number
Default value: 3600.
A modem is not used so many seconds when it seems to be out of order.

checked = directory
Default value: /var/spool/sms/checked.
Path of the default Queue directory in case you do not use provider-sorting.

checkhandler = filename
Default value: not in use.
External program that checks if a message file is valid. (This script can also be used to convert message file from UTF-8 to ISO format, which is internal format used in smsd. See scripts/checkhandler-utf-8 for sample code.)
After version >= 3.0.9 the smsd converts messages automatically from UTF-8 to ISO, if it is necessary.
If the checkhandler return a non-zero (other than 2) exitcode the message will not be sent.
With the smsd version >= 3.1 the checkhandler can also modify a message file.
Exitcode 2 means that the checkhandler has moved a message to the spooler by itself.

date_filename = number
Default value: 0.
Available from version >= 3.1. Defines if date is included to the filename of incoming message. With value 1 like 2007-09-02.GSM1.xxxxxx and with value 2 like GSM1.2007-09-02.xxxxxx.

date_filename_format = format string
Default value: compatible with previous versions.
Available from version >= 3.1.7. Specifies a format for date string in filenames. See man strftime for usage details.

datetime_format = format string
Default value: compatible with previous versions.
Available from version >= 3.1. Specifies a format for timestamps. See man strftime for usage details.

Before the version 3.1.7 a name for this setting was datetime. The old name can still be used because of backward compatibility.

decode_unicode_text = yes/no
Default value: no.
Available from version >= 3.0. Controls when the incoming Unicode text is decoded internally.

delaytime = number
Default value: 10.
Smsd sleep so many seconds when it has nothing to do.

delaytime_mainprocess = number
Default value: not in use.
Available after >= 3.1.4. If this value is not set, delaytime setting is used in the main process. With this setting outgoing messages can be checked more frequently than incoming messages.

devices = names
Default value: empty.
List of names of your modems, maximum 64 devices. This limit is changeable.

After version >= 3.1.12 this setting can be given in shortened form:
devices = GSM* 101-164

"GSM" defines a prefix, "101" is the number of first modem and "164" is the number of last modem.
The example is extracted to: devices = GSM101,GSM102,GSM103 etc...

errorsleeptime = number
Default value: 10.
A modem sleeps so many seconds when it answers a command with ERROR.

eventhandler = filename
Default value: not in use.
Specifies an external program or script that will execute whenever a message was sent, received or failed.
(If your locale is UTF-8, you can use this script to convert received messages from smsd's internal format (ISO) to UTF-8. See scripts/eventhandler-utf-8 for code sample.)
After version >= 3.0.9 there is incoming_utf8 = yes setting available. Using this setting the external conversion is not required.

executable_check = yes/no
Default value: yes.
Available from version >= 3.1.1. This setting defines if all executables are checked during the startup check. Usually eventhanler, alarmhandler etc. are shell scripts or some other single files which can be executed and therefore checked simply. If using a settings like eventhandler = /usr/local/bin/php -f /usr/local/bin/smsd_eventhandler.php, the check will fail and smsd will not start unless executable_check = no is defined.

failed = directory
Default value: not in use.
Path of the Failed Folder. Delete this line if you do not want to keep failed files.

filename_preview = number
Default value: not in use.
Available from version >= 3.1. Defines how many characters of message text is concatenated to the name of a messsage file. Characters used are a...z, A...Z, 0...9, - and period. All other characters are replaced with _'s.

hangup_incoming_call = yes/no
Default value: no.
Available from version >= 3.1.5. If set to yes and detected unexpected input contains "RING", incoming call is ended. Use modem setting voicecall_hangup_ath to define if "ATH" is used to make hangup instead of "AT+CHUP".

ic_purge_hours = number
Default value: 24.
ic_purge_minutes = number
Default value: 0.
ic_purge_read = yes/no
Default value: yes.
ic_purge_interval = number
Default value: 30.
Available from version >= 3.1.5. These settings defines how concatenation storage is purged when internal_combine is used and messages with missing parts are received. Storage is checked every ic_purge_interval minutes. If there are message parts older than defined with ic_purge_hours and/or ic_purge_minutes settings, message parts are deleted. If ic_purge_read is yes, message is stored to the incoming folder. In this case there will be only one part in the file and a header Incomplete: yes indicates that message is broken. Value 0 for both ic_purge_hours and ic_purge_minutes disables this feature.

ignore_exec_output = yes/no
Default value: no.
Available from version >= 3.1.7. Defines if an unexpected output from eventhanders is ignored.
Usually eventhandlers should not output anything. If there is some output, it usually means that some error has occurred. These errors are often problematic, because that output cannot be seen and error is therefore not notified. Smsd will print any output from the evenhandlers to the log file. This output can also be used for debugging purposes, but usually debugging should be done by running eventhandler manually.

ignore_outgoing_priority = yes/no
Default value: no.
Available from version >= 3.1.5. With this setting Priority: high header is not checked. This speeds up spooling on systems where priorizing is not required and there are lot of files in the spooler directory.

incoming = directory
Default value: /var/spool/sms/incoming.
Path of the Incoming Folder.

incoming_utf8 = yes/no
Default value: no.
Available from version >= 3.0.9. With this setting messages using ISO or GSM alphabet are stored using UTF-8 character set.

internal_combine = yes/no
Default value: yes.
Available from version >= 3.0. Controls when the incoming multipart message is combined internally.

internal_combine_binary = no
Default value: internal_combine.
Available from version >= 3.1.5. Controls when the incoming multipart binary message is not combined internally.

international_prefixes = list of numbers
Default value: not in use.
Available from version >= 3.1.5. See SMS file (Using Type Of Address selection) for details.

keep_filename = yes/no
Default value: yes.
Available from version >= 3.1. If this is set to no, an unique filename is created each time when a message file is moved from directory to another directory. This ensures that any previously sent message file is not overwritten, even if the original filename was the same. Also if an user has created filename with space character, this creates a new name without spaces and therefore spaces can't effect to operation of an eventhandler.

keep_messages = yes/no
Default value: no.
Available from version >= 3.1.5. This is for testing purposes. Smsd runs as usual but messages are not removed from the modem. After all messages are read, smsd will stop. Use this with one modem only.

language_file = filename
Default value: not in use.
Available from version >= 3.1. Message files can be written using localized headers. See the localizing for details.

log_charconv = yes/no
Default value: no.
Available from version >= 3.0.9. With this setting a details of character set conversions (outgoing UTF-8 to ISO conversion and incoming GSM/ISO to UTF-8 conversion) is printed to the log. If smsd is compiled using DEBUGMSG definition, details are also printed to the console. Logging feature can be useful if you have some troubles with characters and like to know what exactly happens inside the smsd.

logfile = filename
Default value: empty.
Name of the log file. Delete this line, if you want to use the syslog for logging. You can use "1" to write to the console (stdout).

This setting can be overridden by the -l (ell) command line argument.

loglevel = number/word
Default value: 4 for logfile, 7 for syslog.
Sets the verbosity of a log file.

This affects also syslog. If you want all messages in syslog, you need to set it to "7" (or higher) here and "*" in the config file of syslog. If you want less messages, you can reduce it here or in the config file of syslog, both will work.

debug 7 All AT-Commands and modem answers and other detailed informations useful for debugging
info 6 Information what is going on at the moment. Not detailled enough for debugging but maybe interesting.
notice 5 Information when a message was received or sent and when something not normal happens but program still works fine (for example wrong destination number in SMS file).
warning 4 Warning when the program has a problem sending a single short message.
error 3 Error message when the program has temporary problem (for example modem answered with ERROR during initialization or a file can not be accessed).
critical 2 Error message when the program has a permament problem (for example sending failed many times or wrong permissions to a queue).
The numbers in this table are taken from GNU/Linux. Probably all operating systems use the same numbers.

After version >= 3.1 a value can be defined as a word, like LOG_INFO, INFO or info.

log_read_from_modem = yes/no
Default value: no.
Available from version >= 3.1.9. In some cases, when resolving troubles with a modem, it's useful to see exact data which is received from the modem. This setting is similar than log_unmodified, but displays the data as a hexadecimal dump.

log_single_lines = yes/no
Default value: yes.
Available from version >= 3.1. Defines if linefeeds are removed from the modem response.

logtime_format = format string
Default value: compatible with previous versions.
Available from version >= 3.1.7. Specifies a format for date string logging. See man strftime for usage details.

After version >= 3.1.14 this setting can have special keywords included: timeus or timems. Keywords are replaced with a current value of microseconds or milliseconds.

logtime_ms = yes/no
Default value: no.
logtime_us = yes/no
Default value: no.
Available from version >= 3.1.14. With these settings the timestamp in the log file can have microseconds or milliseconds shown. These settings can be used when a default format for timestamp is in use. Value is shown after seconds and is delimited with a dot.

If logtime_format is defined, these settings have no effect. Milliseconds or microseconds can be included in customized logtime_format using keywords timeus or timems.

log_unmodified = yes/no
Default value: no.
Available from version >= 3.1.7. In some cases, when resolving troubles with a modem, it's useful to see what kind of line ends were received from the modem. With this setting spaces and line ends are not removed from the string which is logged. This setting overrides the setting log_single_lines.

max_continuous_sending = number
Default value: 300 (5 min).
Available from version >= 3.1.5. This setting is in seconds and defines how long modem can send messages without doing anything else. After max_continuous_sending time is reached, received messages are checked and other tasks are run.

national_prefixes = list of numbers
Default value: not in use.
Available from version >= 3.1.5. See SMS file (Using Type Of Address selection) for details.

os_cygwin = yes/no
Default value: no.
Available from version >= 3.0.10. Defines if smsd is running on Cygwin environment. This is needed if outgoing file permissions should be checked and changed by the smsd.

outgoing = directory
Default value: /var/spool/sms/outgoing.
Path of the Outgoing Queue folder.

phonecalls = directory
Default value: empty, incoming directory is used.
Available from version >= 3.1. Defines where phonecalls data is stored.

priviledged_numbers = list of numbers
Default value: not in use.
Available from version >= 3.1.5. This list can be used with check_memory_method values 31, 41, and 5. Global list is default for each modem. List can be comma separated list of numbers or their parts starting from the left. Maximum 25 priviledged numbers can be defined. When messages are received, messages from priviledged numbers are handled first. First number in the list has highest priority.

receive_before_send = yes/no
Default value: no.
Forces smsd to empty the first SIM card memory before sending SM. This is a workaround for modems that cannot send SM with a full SIM card.

regular_run = filename
Default value: not in use.
regular_run_interval = number
Default value: 300.
Available from version >= 3.1. A regular_run is an external script or program which is run regularly while the smsd is running. A value regular_run_interval describes number of seconds between each run.
See Running for more information and sample usage.

report = directory
Default value: not in use.
Available from version >= 3.1. Path of the Report Folder. Without this setting status report messages are stored to the Incoming Folder.

saved = directory
Default value: not in use.
Available from version >= 3.1. Path of the Saved Folder. If defined, smsd will store concatenation storage's to this directory (otherwise incoming directory is used). At startup check existing concatenation storages are moved from incoming directory to saved directory. Zero sized files are ignored. If both directories has a storage file with data, fatal error is produced and the smsd does not start.

sent = directory
Default value: not in use.
Path of the Sent Folder. Delete this line, if you do not want to keep copies of each sent message file.

shell = filename
Default value: /bin/sh
Available from version >= 3.1.5. Defines which shell is used to run eventhandler and other external scripts.

shell_test = yes/no
Default value: yes
Available from version >= 3.1.14. When executable_check is enabled, testing of the shell can be omitted with this setting.

smart_logging = yes/no.
Default value: no.
Available from version >= 3.1.5. This feature is available when file based logging is used. If loglevel is less than 7 (for example "notice" is a good choise with smart_logging), trouble log (with loglevel 7) about whole communication is written to different file if there has been any errors.

"Whole communication" means sending single SMS, receiving single SMS, and other things what smsd will do after idle time is spent. When communication starts, all possible log lines are collected to internal buffer and only loglevel lines are written to the logfile. If during communication there are any errors, all collected lines are printed to trouble log when communication reaches it's end.

This feature was made because with loglevel 7 logfile grows very much and fast, and debug level is not usually needed when there was no any errors. In case of errors it's important to see whole communication, not just a single line which says that "error happened".

File name is created with the rule: if lenght of logfile setting is more than 4 characters and setting ends with ".log", trouble log filename will end with "_trouble.log". If length is less than 4 or setting does not end with ".log", trouble log filename is logfile appended with ".trouble". In usual cases logfile is /var/log/smsd.log and trouble log filename will be /var/log/smsd_trouble.log, or in some (Debian, Ubuntu, ...) distributions: /var/log/smstools/smsd.log and /var/log/smstools/smsd_trouble.log.

spool_directory_order = yes/no
Default value: no.
Available from version >= 3.1.9. With this setting files are handled in the order they are on disk. This disables priorizing and also the age of files are not checked.

stats = directory
Default value: not in use.
Specifies the directory where smsd stores statistic files. The directoy must exist before you start smsd. If not given, then the program does not write statistic files. After version >= 3.1.1 message counter files are stored to this directory even if smsd is compiled without statistics enabled.

stats_interval = number
Default value: 3600.
Smsd writes statistics files every n seconds. Value 0 disables statistics but counters are still updated if stats directory is defined.

stats_no_zeroes = yes/no
Default value: no.
Smsd does not write statistic files when no message was sent or received (Zero-Counters) if this is set to yes.

status_include_counters = yes/no
Default value: yes.
status_signal_quality = yes/no
Default value: yes.
Available from version >= 3.1.5. These settings define if message counters and explained signal quality is included in the line of status file.

status_interval = number.
Default value: 1.
Available from version >= 3.1.5. If statistics function is enabled and stats directory is defined, smsd writes file named status into this directory. The file contains status of all modems in the first line using Status: header (this is similar than smsd -s outputs to console) and explained status in the next lines using modem's name as a header. Smsd writes status file every status_interval seconds if a status has changed. Value 0 disables this feature.

For example, the output is like:

Status:      09-05-27 20:46:17,  irir------------
SONERA:      09-05-27 20:46:09,  Idle,       123,  0,  321,  ssi: -63 dBm, ber: < 0.2 %
ELISA:       09-05-27 20:46:12,  Receiving,  234,  0,  432,  ssi: -73 dBm, ber: < 0.2 %
DNA:         09-05-27 20:46:06,  Idle,       456,  0,  543,  ssi: -77 dBm, ber: < 0.2 %
SAUNALAHTI:  09-05-27 20:46:14,  Receiving,  678,  0,  654,  ssi: -69 dBm, ber: < 0.2 %
Timestamp value tells when status is created or modem initialization was last started.

Status can be: (s) Sending, (r) Receiving, (i) Idle, (b) Blocked, (t) Trouble, (-) Unknown. Trouble -status means that something abnormal has happened and if smart_logging is in use, trouble log will be written.

Counters are: sent, failed, received. Sent counter is the value from stats/<modemname>.counter file. Smsd does not clear this value. Failed and received counters are from original statistics data and they are cleared each time when stats are stored (stats_interval), or smsd is restarted.

store_original_filename = yes/no
Default value: yes.
Available from version >= 3.1. Together with keep_filename this controls when the original filename is stored to message file when it's moved from outgoing directory to the spool.

store_received_pdu = number
Default value: 1.
Available from version >= 3.0. Controls when the incoming PDU string(s) is stored to message file.
 0 no PDU's are stored
 1 unsupported PDU's are stored
 2 unsupported and PDU's with 8bit binary data or Unicode text are stored
 3 all PDU's are stored
Header is "PDU: " and PDU's of a multipart message are stored from 1 to n order.

store_sent_pdu = number
Default value: 1.
Available from version >= 3.0.9. Controls when the outgoing PDU string(s) is stored to message file.
 0 no PDU's are stored
 1 failed (to send) PDU's are stored
 2 failed and PDU's with 8bit binary data or Unicode text are stored
 3 all PDU's are stored
Header is "PDU: " and PDU's of a multipart message are stored from 1 to n order.

suspend = filename
Default value: not in use.
Available from version >= 3.1.7. With this file, any modem process can be suspended. When a process is suspended, the modem port is closed and modem process does not do anything.

The file is checked before smsd starts sending or receiving. If a name of the device is found from the file, suspend will start. The format of a line in file is: <devicename>: <reason>. Colon is needed, but the <reason> is optional. It is printed to the log. A special line ALL: <reason> can also be used, and this affects for all modem processes.

When a process is suspended, the file is checked every ten seconds, or more frequently if the delaytime value is smaller.

When a process is suspended, it listens a signal SIGUSR2. If this signal is received, modem process will send and receive messages once.

terminal = yes/no
Default value: no.
Available from version >= 3.1. Enables terminal mode like command line argument -t.

trim_text = yes/no
Default value: yes.
Available from version >= 3.1.7. With this setting trailing whitespaces are removed from the outgoing message. Does not effect with messages written using Unicode or GSM alphabet.

After version >= 3.1.9, if the message is going blank, the removal is not done. This is because some people, really, need to send a message which contains only single space character.

trust_outgoing = yes/no
Default value: no.
Available from version >= 3.1.5. This setting can be used to speed up spooling, but only if it is completely sure that files are created by rename and permissions are correct.

umask = value
Default value: empty.
Available from version >= 3.1.7. Effective umask for smsd can be set in the configuration file. Value can be hexadecimal, decimal or octal format.

Next four settings are available from version >= 3.0.2:
user = username
Default value: not in use.
group = groupname
Default value: not in use.

If the smsd is started by the root, these two settings can be used to switch smsd to run as an unpriviledged user.
As of version >= 3.0.9, if user is set but group is unset, that user's normal groups (e.g. from /etc/groups) are used. This means you can allow other users on the system access to write messages to the outgoing spool without giving them direct access to the serial port.

infofile = filename
Default value: /var/run/smsd.working.
pidfile = filename
Default value: /var/run/smsd.pid.

Location of infofile and pidfile can be changed with these settings. This is usually necessary if the smsd is running as an unpriviledged user. If a sms3 script is used to start and stop the smsd, these settings should be defined in the script.
These four settings can be overridden by the command line argument(s):

See Running for more information.

validity = number
Default value: 255.
Available from version >= 3.0. See SMS file for details of possible values.

voicecall_hangup_ath = yes/no
Default value: no.
Available from version >= 3.1.5. Defines if ATH is used to hangup call instead of AT+CHUP.

whitelist = filename
Default value: not in use.
Name of the whitelist file. The black list takes precedence before the white list. See Blacklist and Whitelist for more details and sample usage.


Modem settings

[modem name]
Begin of a modem settings block. The modem name must be the same as in the devices= line in the global part.
NOTE for Cygwin users: Do not use a device name, like COM1, as a modem name. While a message is received, a file starting with this name is created and Windows handles it as a device. This will cause a modem process to be freezed.

After version >= 3.1.12 it is no more mandatory that the modem settings block exists for all modems. When using large number of similar modems, it is often enough that [default] block exists.

adminmessage_limit = number
Default value: not in use.
adminmessage_count_clear = number
Default value: not in use.
Available from version >= 3.1.5. Adminmessage_limit specifies the maximum number of administrative messages to send. After this limit is reached, no more administrative messages will be sent until the smsd is restarted or message counter is cleared by the adminmessage_count_clear setting. The value of this setting is minutes.

admin_to = phone number
Default value: not in use.
Available from version >= 3.1. Specifies a destination number for administrative messages created by smsd. This setting overrides the setting in the global part.

baudrate = number
Default value: 115200.
Specifies the speed of the serial communication in bits per second. Most modems including old devices work well with 115200. If this speed is not supported by the system, 19200 is used. Some very old devices may need lower speed like 9600 baud.

check_memory_method = number
Default value: 1.
Available from version >= 3.1.5. Defines how incoming messages are checked:
 0 CPMS is not supported. Default values are used for used_memory and max_memory.
 1 CPMS is supported and must work. In case of failure incoming messages are not read.
 2 CMGD is used to check messages. Some devices does not support this.

To see if this is supported, check the answer for AT+CMGD=? command. Answers like:
+CMGD: (1,2,3),(0-4)
OK
= is supported, there are messages number 1, 2 and 3 in the memory.

+CMGD: (),(0-4)
OK
= is supported, no messages in the memory.

+CMGD: (1-30)
OK
= is not supported, a modem does not give message numbers even if there is messsages available.

 3 CMGL is used to check messages. Message is deleted after it is read.
 4 CMGL is used to check messages. Messages are deleted after all messages are read.
 31 CMGL is used to check messages. Message is deleted after it is read.
CMGL data is checked and PDU is taken directly from the list.
 41 CMGL is used to check messages. Messages are deleted after all messages are read.
CMGL data is checked and PDU is taken directly from the list.
 5 CMGL is used to check messages. Messages are deleted after all messages are read.
CMGL data is checked and PDU is taken directly from the list.
Multipart message is handled after all of it's parts are available. After multipart message is handled,
only the first part is deleted by the smsd. The modem will delete rest of parts by itself. This is SIMCOM SIM600 compatible.

NOTE: Some devices are incompatible with CMGL method.
NOTE: With values 31, 41 and 5 priviledged_numbers sorting can be used.

check_network = value
Default value: 1.
Available from version >= 3.1, enhanced in version 3.1.5. Defines how network registration is checked:
 0
 no 
Network registration is not checked.
 1
 yes 
Network registration is always checked
 2 Network registration is checked only when preparing to send messages.

If a modem does not support network checking, checking is automatically ignored.
With value 2 incoming messages are processed faster.

cmgl_value = string
Default value: 4.
Available from version >= 3.1.5. If check_memory_method = 3, 4, 31, 41 or 5 is used, correct value for AT+CMGL= command must be defined. This value depends on the modem.

communication_delay = number
Default value: 0.
Available from version >= 3.1.5. Only some very problematic modems may need this setting. Defines minimum time in milliseconds between latest answer from modem and next command which will be sent to modem.

cs_convert = yes/no
Default value: yes.
The program converts normal text messages into GSM character set. You need this to display german umlauts and control characters correctly.

decode_unicode_text = yes/no
Default value: use the global part setting.
Available from version >= 3.0. Specifies an internal Unicode decoding like in the global part.

detect_message_routing = yes/no
Default value: yes.
Available from version >= 3.1.5. By default smsd tries to detect if a modem is in message routing mode. Before sending a command smsd listens if some data with routed message is available. Also, after a command is sent, smsd checks the answer. In both cases, if there is one or more routed message coming, a notification is written to the log and alarmhandler is called. Routed messages are saved and handled later when smsd can do it.

NOTE: This checking is done to avoid errors and loss of messages. Routing mode SHOULD NOT BE USED in normal operation. With routing mode it is NOT quaranteed that all messages are delivered. Some devices are in routing mode by default and this feature helps to detect it. Use init = AT+CNMI=... with suitable values to disable routing mode. Values depend on modem, see the manual of a modem for details. All received messages must be stored to the message store which is usually "SM" (SIM card memory).

detect_unexpected_input = yes/no
Default value: yes.
Available from version >= 3.1.5. Before any command is sent to the modem, smsd checks if there is some unexpected input. For example some modem may send new message identification (CMTI) if settings are incorrect. Any unexpected input will be logged.

device = name of serial port / definition of internet host
Default value: empty.
Specifies the device name of the serial port or internet host to the modem. GNU/Linux example: /dev/ttyS0. Windows example: /dev/com1 or /dev/ttyS0. Solaris example: /dev/cuaa. Network modem example: @10.1.1.1:5000.

After version >= 3.1.7 this setting can define a socket if starting with character @. Format for the internet host is: @<host_or_ip>:<port>. Host definition can be name or IP address.

After version >= 3.1.12 this setting can contain a special word "modemname" (without quotation marks). For example:

When a device is USB0, the setting is extracted as /dev/ttyUSB0.

device_open_alarm_after = number
Default value: 0.
Available from version >= 3.1.7. After defined number of retries, an alarmhandler is called. Smsd still continues trying, if device_open_retries value is bigger.

device_open_retries = number
Default value: 1.
Available from version >= 3.1.7. Defines how many times smsd will retry when cannot open a device. When maximum number of retries is reached, modem process will call alarmhandler and stop. With value -1 smsd will retry forever.

eventhandler = filename
Default value: empty.
Specifies an eventhandler script like in the global part. If you use this variable, then this modem will use its own individual eventhandler instead of the global one.

eventhandler_ussd = filename
Default value: not in use.
Available from version >= 3.1.7. This setting defines an eventhandler to use with USSD messages. It is possible to use the same script or program which is used as eventhandler, but it's not a default because basically those scripts are not compatible without modifications.

After an USSD message is received, and probably ussd_convert is done, eventhandler_ussd is called. Smsd checks what is the current character set of a modem and creates a temporary file which contains the USSD answer. Arguments for the eventhandler_ussd are:
 $1 "USSD" keyword.
 $2 Filename (which contains the answer).
 $3 Devicename.
 $4 Character set.
 $5 Command what was used to get the USSD answer.

Eventhandler_ussd can do whatever is needed with the USSD answer. It can also modify the answer, or delete the file. After eventhandler_ussd returns, smsd will check if the file still exists. If it exists, it's first line is taken as a new answer. Modified answer is then logged and probably printed to the regular_run_statfile.

hangup_incoming_call = yes/no
Default value: no.
Available from version >= 3.1.5. If set to yes and detected unexpected input contains "RING", incoming call is ended. Use modem setting voicecall_hangup_ath to define if "ATH" is used to make hangup instead of "AT+CHUP". This setting overrides global setting.

incoming = no/yes/high or 0/1/2
Default value: no.
Specifies if the program should read incoming SM from this modem. "Yes" or "1" means that smsd receives with less priority. The value "high" or "2" means that smsd receives with high priority. "No" or "0" means that smsd does not receive messages.

init = modem command
Default value: not in use.
Specifies a modem initialisation command. Most modems do not need any init string. See the manual of your modem for more details of modem commands.

init2 = modem command
Default value: not in use.
Specifies a second modem initialisation command. Most users do not need this.

internal_combine = yes/no
Default value: use the global part setting.
Available from version >= 3.0. Specifies an internal multipart message combining like in the global part.

internal_combine_binary = no
Default value: use the global part setting.
Available from version >= 3.1.5. Specifies an internal multipart binary message combining like in the global part.

keep_messages = yes/no
Default value: no.
Available from version >= 3.1.7. Defines if messages are not deleted from the device. Unlike a global setting keep_messages, smsd continues running.

keep_open = yes/no
Default value: yes.
Available from version >= 3.1. If this is changed to no, a modem is closed while it's not used.

logfile = filename
Default value: empty, using a global log file.
Available from version >= 3.1. Defines a log file if a global log is not used.

After version >= 3.1.12 this setting can contain a special word "modemname" (without quotation marks). For example:
logfile = /var/log/smstools/modemname.log

When a device is GSM1, the setting is extracted as /var/log/smstools/GSM1.log.

loglevel = number/word
Default value: same as in the global setting.
Available from version >= 3.1. Sets the verbosity of a log file. See more details in the global part.

loglevel_lac_ci = number/word
Default value: LOG_INFO.
Available from version >= 3.1.14. Sets the verbosity of logging the Location area code and Cell ID and their changes. This requires that AT+CREG? returns location information. It is automatically enabled by pre_init using a command CREG=2. After the Location are code or Cell ID changes, quality of signal is also logged. This feature can be disabled with the setting which is more than current loglevel, for example 8.

log_not_registered_after = number
Default value: 0.
Available from version >= 3.1.14. If it's known that the modem gives "not registered" after a message is sent or received, with this setting number of log messages can be avoided.

max_continuous_sending = number
Default value: 300 (5 min).
Available from version >= 3.1.5. This setting is in seconds and defines how long modem can send messages without doing anything else. After max_continuous_sending time is reached, received messages are checked and other tasks are run. This setting overrides global setting.

memory_start = number
Default value: 1.
Tells the first memory space number for received messages. This is normally 1, Vodafone Mobile Connect Card starts with 0.

messageids = number
Default value: 2.
Available from version >= 3.1.1. Defines how message id's are stored: 1 = first, 2 = last, 3 = all. When all id's are stored, numbers are delimited whith space and there is one space and dot in the end of string.

message_limit = number
Default value: not in use.
message_count_clear = number
Default value: not in use.
Available from version >= 3.1. Message_limit specifies the maximum number of messages to send. After this limit is reached, no more messages will be sent until the smsd is restarted or message counter is cleared by the message_count_clear setting. The value of this setting is minutes.
If admin_to is specified, an administrative message is sent when the limit is reached.

mode = old/new
Default value: new.
Specifies version of modem command set. Almost everybody needs to use this as a "new".

From version >= 3.0.9 this effects mainly to the sending side. In the receiving side the incoming PDU is checked, and if it does not match to the selected mode, another mode is tried automatically.

 old  For Falcom A1 and maybe some other old modems of GSM phase 1 (1990-1995).
In the receiving side this mode does not have SCA information in the begin of PDU.
 new  For nearly all mobile phones and modems.
In the receiving side this mode has SCA information in the begin of PDU.
(SCA stands for Service Centre Address).

modem_disabled = yes/no
Default value: no.
Available from version >= 3.0.9. This is for testing purposes too.
Whole messaging system including eventhandlers etc. can be tested without any working modem existing. Sending of messages is simulated in the similar way than with sending_disabled setting. Incoming messages are taken only from the file, if pdu_from_file is defined. No any communication is made between smsd and modem, but a device setting should still exist because smsd wants to open and close a device. If in you testing environment you do not have a priviledges to the usual modem device, like /dev/ttyS0, you can use a definition like device = /tmp/modemfile. If this file exists and is writable for the process owner, it's enough for smsd.

ms_purge_hours = number
Default value: 6.
ms_purge_minutes = number
Default value: 0.
ms_purge_read = yes/no
Default value: yes.
Available from version >= 3.1.5. These settings are used only with SIM600 (or compatible) modems (check_memory_method 5). If multipart message is received with one or more parts missing, incomplete message is removed from the message storage after time defined. Time is calculated from the timestamp of the first available part. Value 0 for both settings disables this feature. Setting ms_purge_read defines if parts are read before they are deleted. If internal_combine setting is used, parts are stored to the concatenation storage. If missing part(s) are later received, there will be similar timeout before parts are purged. After this the messsage is complete and is stored to the incoming folder. See also global settings ic_purge_*.

needs_wakeup_at = yes/no
Default value: no.
Available from version >= 3.1.7. After being idle, some modems do not answer to the first AT command. For example with BenQ M32, there can be OK answer, but in many times there is not. To avoid error messages, smsd first send AT and read the answer if it's available.

smsc = number
Default value: not in use.
Specifies the SMSC number that this modem should use to send SM. You need this setting only if the default of the SIM card is bad. Write the phone number of the SMSC in international format without the starting "+".

outgoing = yes/no
Default value: yes.
Available from version >= 3.0.9. Specifies if a modem is used to handle and send outgoing messages.

pdu_from_file = filename / directoryname/
Default value: not in use.
Available from version >= 3.0. This is for testing purposes.
You can test you eventhandler and some other things without actually receiving a message from the modem/phone. This is especially important when it's not possible to receive the same message again because the sender cannot be reached.
You may have the original PDU string stored to the incoming messsage file, or you may see it in log file (depending of the loglevel). This PDU string can be stored to the pdu_from_file named file, and when this file exists the smsd will read the PDU from there. Rest processing will be done similarry than with normally received messsages and you can then debug possible problems and see when they are fixed.
This file can contain empty lines and comment lines starting with # character.
Actual data can be stored as one line containing the PDU string, or two lines containing (first) the modem answer and (second) the PDU string.
For example:
#2006-09-13 13:12:10,7, GSM1: <-
+CMGR: 0,,40
0791531811111111240C9153183254769800F1609031314174211854747A0E4ACF416110BD3CA783DAE5F93C7C2EBB14

or simply one line only:
079153181111111106BC0C91531832547698609031314174216090313141842100

NOTE: After this file is processed, it is removed.
After >= 3.0.9 the setting can be a directory.
If this setting ends with a slash and a directory with that name exists, file(s) are read from this directory (and deleted after processing). All files found from the given directory are processed one by one, expect hidden files (name begins with a dot). When this setting points to the directory, no dot's are allowed in any position of a path. Be very careful with this setting while it will delete the content of a whole directory.
After >= 3.0.9: while reading a PDU from file, a first line starting with PDU: and space is taken if any exists.
After >= 3.1.5: this can be used only with check_memory_method values 0 or 1.

phonecalls = yes/no/clip
Default value: no.
Available from version >= 3.1. Specifies if missed phonecalls are reported. After version >= 3.1.7 a value clip, or 2 can be used. This value could be used with modems which do not support reading of phonebook, or phobebook cannot be used because entries cannot be deleted. Phocecall is detected using the +CLIP Calling line identification report from a modem.

When phonecalls = clip is used, a setting hangup_incoming_call is automaticatty set to yes. This is because smsd must hangup a call before it's handled.

phonecalls_purge = yes/no/command
Default value: no.
Available from version >= 3.1.7. Specifies if missed phonecalls are removed using a purge command. Value yes specifies that AT^SPBD="MC" command is used, this works with some Siemens based devices. Other command can be given as a value.

pin = 4 digit number
Default value: not in use.
Specifies the PIN number of the SIM card inside the modem. See also pinsleeptime.
Note after version >= 3.1.1: Even if a PIN is not defined, it's still checked if a PIN is needed. Some phones may give an incorrect answer for the check when a pin is not needed, like SIM PIN2 or SIM PUK2 instead of READY. In this case define pin = ignore to skip the whole PIN handling procedure.

pinsleeptime = number
Default value: 0.
Available from version >= 3.0.9. Specifies how many seconds the program will sleep after a PIN is entered. Some modems do not work without some delay.

phonecalls_error_max = number
Default value: 3.
Available from version >= 3.1.7. Specifies the maximum number of errors before phonecalls are ignored.

pre_init = yes/no
Default value: yes.
Available from version >= 3.0.8. Specifies is an "echo off" and "CMEE=1" commands are sent to the modem before anything else is done.

primary_memory = memory name
Default value: not in use.
secondary_memory = memory name
Default value: not in use.
secondary_memory_max = number
Default value: accept what device returns.
These three settings are used to control dual-memory handler, available from version >= 3.0.
If your modem/phone receives messages to the Mobile Equipment (ME) memory after the SIM memory (SM) has been filled up, with dual-memory handler messages can be read from the Mobile Equipment memory too.
Defining secondary_memory_max is needed, if your device does not tell how much there is space in the Mobile Equipment memory. For example the Nokia 6210 does not tell (it returns 0 as max value) and with this device 150 is reasonable value.
From version >= 3.1, multiple parameters can be defined for memories, like SM,SM,SM. Double-quotation marks are not necessary to use in the string.

priviledged_numbers = list of numbers
Default value: not in use.
Available from version >= 3.1.5. This setting overrides the setting in the global part. See the global part for details.

queues = list of queue names
Default value: not in use.
Specifies the Provider Queues that this modem shall serve. Use the same provider names as in [queues] and [providers]. If you do not use the provider-sorting feature, then leave this line out. After version >= 3.1.5 special keyword modemname can be used, it's replaced with a name of modem.

read_timeout = number
Default value: 5.
Available from version >= 3.1.5. When smsd reads data from a modem, timeout will occur after read_timeout seconds if an acceptable answer is not received. Some very slow devices might need greater value than 5 seconds.

regular_run = filename
Default value: not in use.
Regular_run for a modem is available from version >= 3.1:
This setting defines an external script or program to execute. A modem can be used as it's closed by the smsd.

regular_run_cmd = string
Default value: empty.
Like regular_run_cmdfile, this string can be used to define modem commands. This setting can be used more than once to define multiple commands.

regular_run_cmdfile = filename
Default value: not in use.
This file can contain command lines which smsd will write to the modem. Modem result of each line is logged and written to the regular_run_statfile (if defined). After a file is processed, it is removed. If you need to use permanent commands on each run, use regular_run script to create this file or define commands using regular_run_cmd settings.

After version >= 3.1.12 the expected answer can be defined. By default smsd will expect "OK" or "ERROR" from the modem. Any other answer will cause timeout. Line can start with opening square bracket, and the expected answer can be given between square brackets. The expected answer must be a valid regular expression (see man regcomp for details).

Example:

regular_run_interval = number
Default value: 300.
Describes number of seconds between each run. Value 0 disables this feature.

regular_run_logfile = filename
Default value: not in use.
Defines a log file for regular_run. Syslog cannot be used for this. If a log file is not defined, smsd's main log is used.

regular_run_loglevel = number
Default value: LOG_NOTICE.
Defines a level of logging.

regular_run_post_run = filename
Default value: not in use.
Available from version >= 3.1.7. This setting can define the second script or program which is executed regularly. The same script with regular_run can be used. The script gets an argument $1 which is PRE_RUN for regular_run and POST_RUN for regular_run_post_run. There is also the second argument $2, which includes a definition of regular_run_statfile.

This is how the regular_run for a modem currently works:

regular_run_statfile = filename
Default value: not in use.
If defined, results of commands are written to this file. Old file is cleared before each run.

report = yes/no
Default value: no.
If you enable this, the program requests a status report SM from the SMSC for each sent message. This does not work on many mobile phones and on some modems.

report_device_details = yes/no
Default value: no/yes.
Available from version >= 3.1.7. Defines if a details from device are printed to the log when modem process is starting. With beta versions of smsd this setting defaults to yes, otherwise it defaults to no.

routed_status_report_cnma = yes/no
Default value: yes.
Available from version >= 3.1.7. Defines if +CNMA acknowledgement is needed to send after routed status report was received.

rtscts = yes/no
Default value: yes.
You can disable usage of hardware handshake wires by setting this option to "no". Please don't use this feature in commercial applications because the hardware handshake wires ensure proper communications timing between the computer and the modem.

send_delay = number
Default value: 1.
If your modem does not support hardware handshake you should use the lowest possible baudrate to ensure that the program does not run faster than the modem can do. However, if the lowest possible baudrate is still too fast, then you can use this parameter to make it even slower. A value of 300 means that the program waits 300 milliseconds between sending each single character to the modem which makes the program very slow.
From version >= 3.1.5, value 0 means that whole string is sent at once without any delays. This resolves slow communication with LAN based multiport servers, like Digi Portserver II. If, for some reason, "tcdrain" is still needed after sending, use value -1.

send_handshake_select = yes/no
Default value: yes.
Available from version >= 3.1.9. Instead of checking the flag TIOCM_CTS, select() function is used when the serial transmitted is busy. If because of some reason the old style should be used, this setting allows it.

sending_disabled = yes/no
Default value: no.
Available from version >= 3.0. This is for testing purposes.
You can test your eventhandler and whole system around the smsd without sending any messages to the GSM network. All other functionality is working as usual, so this is some kind of "mute" to the modem. However the modem should be connected and working. This does not have an effect to the incoming messsages.

signal_quality_ber_ignore = yes/no
Default value: no.
Available from version >= 3.1.14. Some devices do not support Bit Error Rate when signal quality is asked, and this always provides "Bit Error Rate: not known or not detectable" to the log line and to the status file. With this setting ber can be ignored.

smsc_pdu = yes/no
Default value: no.
Available from version >= 3.1.12. If the number of SMSC is set in the configuration, or in the message file, the number is included in the PDU instead of changing SMSC with a command AT+CSCA. The number can be presented in international format, or in national format starting with 0.

socket_connection_alarm_after = number
Default value: 0.
Available from version >= 3.1.7. After defined number of retries, an alarmhandler is called. Smsd still continues trying, if socket_connection_retries value is bigger.

socket_connection_errorsleeptime = number
Default value: 5.
Available from version >= 3.1.7. Defines how many seconds the smsd will sleep after an error with socket connection.

socket_connection_retries = number
Default value: 11.
Available from version >= 3.1.7. Defines how many times smsd will retry when a socket connection fails. When maximum number of retries is reached, modem process will call alarmhandler and stop. With value -1 smsd will retry forever.

start = modem command
Default value: not in use.
startsleeptime = number
Default value: 3
stop = modem command
Default value: not in use
Available from version >= 3.1.7. If defined, start command is sent to the modem when a modem process starts. After a command is sent, startsleeptime is spent. When a modem process is stopping, stop command is sent to the modem.

status_include_counters = yes/no
Default value: yes.
status_signal_quality = yes/no
Default value: yes.
Available from version >= 3.1.5. These settings define if message counters and explained signal quality is included in the line of status file. Modem setting overrides global setting.

telnet_login = string
Default value: empty.
Available from version >= 3.1.12. If a network modem requires telnet login, it can be defined with this setting.

telnet_login_prompt = string
Default value: login:.
Available from version >= 3.1.12. If telnet_login is used, this setting can be used to change the login prompt.

telnet_login_prompt_ignore = string
Default value: Last login:.
Available from version >= 3.1.12. If telnet_login is used and after successful login motd contains a string which is the same as telnet_login_prompt, this setting can be used to define which kind of a string is ignored. For example, telnet_login_prompt can be login: and telnet_login_prompt_ignore could be Last login:.

telnet_password = string
Default value: empty.
Available from version >= 3.1.12. If telnet_login is used and device requires password, it can be defined with this setting.

telnet_password_prompt = string
Default value: Password:.
Available from version >= 3.1.12. If telnet_password is used, this setting can be defined to change the prompt for password.

trust_spool = yes/no
Default value: yes.
Available from version >= 3.1.9. When a modem process is searching for a file from the spooler, it assumes that the file is complete when it exists and it's not locked. This will speed up sending, because one second delay is avoided. If some other process or checkhandler is creating files directly to the spooler, it may be necessary to set this to no.

unexpected_input_is_trouble = yes/no
Default value: yes.
Available from version >= 3.1.5. With smart_logging, this setting defines if unexpected input activates trouble log.

using_routed_status_report = yes/no
Default value: no.
Available from version >= 3.1.7. Smsd can detect routed status reports, but usually it's not recommended to use them. Modems should store status reports as an ordinary messages, which can be read when smsd will need them. However, some modem cannot store status reports, and therefore routing is the only way to get them. With this setting smsd will change some severity and text of a logging.

ussd_convert = number
Default value: 0.
Available from version >= 3.1.7. Defines if a text part from incoming USSD message is decoded. Possible values are:
 1 Unicode format is converted to UTF-8. This requires that USE_ICONV is defined.
 2 GSM 7bit packed format is converted to ISO or UTF-8.
 4 Hexadecimal dump is converted to ASCII. (Available from version >= 3.1.11.)

Decoded text is appended to the original answer with two slashes and one space character.

verify_pdu = yes/no
Default value: no.
Available from version >= 3.1.14. This setting is for testing purposes. When trying to send SMS and modem does not accept the PDU, there may be a communication problem which breaks the sent PDU. With this setting smsd changes "echo on", and verifies the string which is echoed back after the PDU was sent. In case of mismatch "Verify PDU: ERROR" is printed to the log and also both sent and received strings are logged. In case of success "Verify PDU: OK" is printed to the log. After the verification is done, "echo" is changed back to "off".

voicecall_clcc = yes/no
Default value: no.
Available from version >= 3.1.12. Defines if AT+CLCC is used to detect when a call is answered. This is required if modem returns OK immediately after the call and voicecall_cpas cannot be used.

voicecall_cpas = yes/no
Default value: no.
Available from version >= 3.1.7. Defines if AT+CPAS is used to detect when a call is answered. This is required if modem returns OK immediately after the call.

voicecall_hangup_ath = yes/no
Default value: no.
Available from version >= 3.1.5. Defines if ATH is used to hangup call instead of AT+CHUP. This setting overrides the setting in the global part.

voicecall_ignore_modem_response = yes/no
Default value: no.
Available from version >= 3.1.5. When a voicecall is ringing, some devices give OK answer after couple of seconds even if a call is not yet answered. With this kind of device DTMF tones cannot be sent. If a ringing time is defined in the message file (using TIME: n), the waiting loop is breaked too soon. To avoid this, use voicecall_ignore_modem_response = yes in the modem settings. With this setting call rings n seconds (if not answered) and after this voicecall is over.

voicecall_vts_list = yes/no
Default value: no.
Available from version >= 3.1.3. Defines how VTS command is used to create DTMF tones: yes = AT+VTS=1,2,3,4,5 (list is used), no = each tone is created with single command like AT+VTS="1";+VTS="2" etc.

voicecall_vts_quotation_marks = yes/no
Default value: no.
Available from version >= 3.1.7. Defines if quotation marks are used when sending VTS command to the modem. NOTE: previously quotation marks were used, now this setting default to no.


smstools-3.1.15/doc/configure2.html000077500000000000000000000200141223712572200171650ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Configuring Providers

Using [queues] and [providers] is optional. If you do not need it, leave both parts out. If you use it with smsd version below 3.1.7, you need to write both parts together into the config file and both parts need to have exactly the same number of lines with the same names. With smsd version 3.1.7 or later, default value for the provider is "catch-all", which is the same as "0,1,2,3,4,5,6,7,8,9,s". See also example 3 and example 4.

Configuring providers enable a sort function in smsd. It takes a look at the destination phone numbers and sorts them into many queue directories - one queue for each phone network provider.

The individual queues allow you to assign modems specially to individual phone network providers, which can save a lot of money in some countries.

You can configure up to 64 providers, 64 queues and 64 phone numbers for each provider. These limits are changeable.


 [queues] 
 name = directory 
 name = directory 
 ... 

 [providers] 
 name = number prefixes 
 name = number prefixes 
 ... 

The name is only a short name for the queue directory. You would typically place the name of the phone network provider here. Spaces and control characters are not allowed here.

The number prefixes are the first digits of phone numbers that belong to the provider. This can be a single number or a comma separated list of many numbers. Write them in international format but without the first "+" character.

Example:


 [queues] 
 telecom = /var/spool/sms/telecom 
 vodafone = /var/spool/sms/vodafone 

 [providers] 
 telecom = 49160, 49170, 49171, 49175, 49151 
 vodafone = 491520, 49162, 49172, 49173, 49174, s 

From the version >= 3.0 it is possible to define 's' for short numbers.

Example 2:


 [queues] 
 finland = /var/spool/sms/finland 
 other = /var/spool/sms/other 

 [providers] 
 finland = 358, s 
 other = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 

In this second example one queue is used to send messages to finnish destinations. Also messages to short numbers are sent using this queue. Another queue is used to send all other messages.

As defined in the How to configure, list of queues used should be defined for modems, in this case:


 [GSM1] 
 queues = finland 
 ... 

 [GSM2] 
 queues = other 
 ... 

Automatic queue sorting can be overridden in the message file. For example, header line Queue: other causes modem GSM2 to be used.

Example 3:

In some cases a modem should be selectable in the message file. This kind of usage overrides a queue sorting, and if a modem is always selected, sorting is not required. However, queue directories should still be defined, because they are used to store messages of each modem.

With smsd version 3.1.7 or later, the configuration could be:


 [queues] 
 GSM1 = /var/spool/sms/GSM1 
 GSM2 = /var/spool/sms/GSM2 
 GSM3 = /var/spool/sms/GSM3 

A section [providers] is left out, because all definitions can default to "catch-all".

Modem definitions in the configuration could be:


 [default] 
 queues = modemname 
 ... 

 [GSM1] 
 ... 
 and so on... 

A modem can be selected using the Queue: <modemname> header in the message file. NOTE: if a queue is not selected, a message is sent using GSM1 because it's the first queue in the list.

Example 4:

In this example provider sorting is not used. All modems will serve the MAIN queue. First two modems will also serve the GROUP1 queue. GSM1 will serve GROUP1 queue after MAIN queue is empty. GSM2 will serve MAIN queue after GROUP1 queue is empty. In addition, each modem has its own queue which is served first.


 [queues] 
 MAIN = /var/spool/sms/queues/MAIN 
 GROUP1 = /var/spool/sms/queues/GROUP1 
 GSM1 = /var/spool/sms/queues/GSM1 
 GSM2 = /var/spool/sms/queues/GSM2 
 GSM3 = /var/spool/sms/queues/GSM3 
 GSM4 = /var/spool/sms/queues/GSM4 

A section [default] is handy when the setup has lot of modems with the same settings:


 [default] 
 gueues = modemname, MAIN 

Modems GSM1 and GSM2 need their own definition for queues:


 [GSM1] 
 queues = modemname, MAIN, GROUP1 
 ... 

 [GSM2] 
 queues = modemname, GROUP1, MAIN 
 ... 

 [GSM3] 
 ... 

 [GSM4] 
 ... 

Messages without the Queue: header are placed into the MAIN queue.

If it's needed that messages to short numbers and without Queue header are always sent using GSM4, this addition can be used:

  1. Ghange the order of queue definitions: place GSM4 right after MAIN, otherwise GROUP1 will be used to send messages to short numbers.

  2. Define providers:


     [providers] 
     MAIN = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 
     GSM4 = s 


smstools-3.1.15/doc/error-messages.html000077500000000000000000000745211223712572200200740ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Error messages in SMS Server Tools 3

PRELIMINARY

Last updated: 02.09.2010.
Applies for the version of smsd: 3.2beta0.

This table contains error messages which are used when an alarmhandler is called.

 Message  Additional information  Function, file  Severity
 Cannot create file <filename>: <reason>   Cannot create file for incoming message.   received2file, smsd.c   ERROR 
 Cannot create shared memory for statistics.   Will terminate.   initstats, stats.c   CRITICAL 
 Cannot handle <filename>: Access denied. Check the file and directory permissions.       getfile, misc.c   ERROR 
 Cannot handle <filename>: Dont know why. Check the file and directory permissions.       getfile, misc.c   ERROR 
 Cannot handle this routed message. You MUST DISABLE message routing with modem settings.       detect_routed_message, modem.c   ERROR 
 Cannot handle this routed status report. You MUST DISABLE status report routing with modem settings.       detect_routed_message, modem.c   ERROR 
 Cannot open file <filename>: <reason>   Cannot handle concatenation storage.   received2file, smsd.c   ERROR 
 Cannot open file <filename>: <reason>       db_store_incoming_message, database.c   ERROR 
 Cannot open file <filename>: <reason>       db_store_sent_message, database.c   ERROR 
 Cannot open serial port <device>, error: <error>       openmodem, modem.c   ERROR 
 Cannot open socket <backend>, error: <error>       openmodem, modem.c   ERROR 
 Cannot read sms file <filename>       readSMShex, smsd.c   ERROR 
 Cannot read sms file <filename>   SMS file exists, but cannot be opened.   readSMSheader_file, smsd.c   ERROR 
 Cannot read sms file <filename>   SMS file exists, but cannot be opened.   readSMStext, smsd.c   ERROR 
 Cannot receive status reports because receiving is disabled on modem <name>       devicespooler, smsd.c   WARNING 
 Cannot rename file <filename> to <filename>       apply_filename_preview, misc.c   ERROR 
 Cannot reserve memory for shared buffer.   Will terminate.   initstats, stats.c   CRITICAL 
 Cannot reserve memory for statistics.   Will terminate.   initstats, stats.c   CRITICAL 
 Cannot write statistics file. <filename> <reason>       checkwritestats, stats.c   ERROR 
 Cannot write tmp file for statistics. <filename> <reason>       savestats, stats.c   ERROR 
 CMGL handling error: message <number>, <error>       check_memory, modem.c   ERROR 
 Conflict with .LOCK file in the spooler: <filename> <directory>   Smsd will retry once and then terminate.    mainspooler, smsd.c   CRITICAL 
 convertSMStext failed with file <filename>       readSMStext, smsd.c   ERROR 
 Could not send character <character>, cause: <reason>       write_to_modem, modem.c   ERROR 
 Could not send string, cause: <reason>       write_to_modem, modem.c   ERROR 
 Couldn't connect socket <backend>, error: <error>, waiting n sec.       open_inet_socket, modem.c   ERROR 
 Couldn't open serial port <device>, error: <error>, waiting n sec.       openmodem, modem.c   ERROR 
 Couldn't open socket <backend>, error: <error>       open_inet_socket, modem.c   ERROR 
 Data is too long in sms file <filename>       readSMShex, smsd.c   ERROR 
 Destination <number> in file <filename> is blacklisted       mainspooler, smsd.c   NOTICE 
 Destination <number> in file <filename> is not whitelisted       mainspooler, smsd.c   NOTICE 
 Destination <number> in SQL SMS #<number> is blacklisted       db_check_message, database.c   NOTICE 
 Destination <number> in SQL SMS #<number> is not whitelisted       db_check_message, database.c   NOTICE 
 Destination number <number> in file <filename> does not match any provider       mainspooler, smsd.c   NOTICE 
 Destination number <number> in SQL SMS #<number> does not match any provider       db_check_message, database.c   NOTICE 
 Failed to initialize modem <name>. Stopping.   Modem process will terminate.   devicespooler, smsd.c   CRITICAL 
 Failed to set type='FAILED': SQL SMS #<number>       db_devicespooler, database.c   ERROR 
 Failed to set type='FAILED': SQL SMS #<number>       db_devicespooler, database.c   ERROR 
 Failed to set type='FAILED': SQL SMS #<number>       db_mainspooler, database.c   ERROR 
 Failed to set type='SENDING': SQL SMS #<number>       db_devicespooler, database.c   ERROR 
 Failed to set type='SENT': SQL SMS #<number>       db_devicespooler, database.c   ERROR 
 Failed to set type='SPOOLED': SQL SMS #<number>   May also contain information about the queue if queues are used.   db_mainspooler, database.c   ERROR 
 Failed to store failed SMS to <number> to database       db_store_sent_message, database.c   ERROR 
 Failed to store incoming SMS from <number> to database       db_store_incoming_message, database.c   ERROR 
 Failed to store sent SMS to <number> to database       db_store_sent_message, database.c   ERROR 
 Failed to update details of status report for message #<number> in database       db_store_incoming_message, database.c   ERROR 
 Fatal error: <infotext1> <filename1> <infotext2> <filename2>. Check file and dir premissions.   Will terminate. This message is used when moving files from directory to another.   stop_if_file_exists, misc.c   CRITICAL 
 FATAL ERROR: Looks like another smsd (<pid>) is running. I (<pid>) quit now.   Smsd will terminate.   mainspooler, smsd.c   CRITICAL 
 Fatal error: sending has failed <number> times. Blocking <number> sec.       sendsms, smsd.c   CRITICAL 
 Handling of concatenation storage aborted, creating <filename> failed: <reason>       do_ic_purge, smsd.c   WARNING 
 Header handling aborted, creating <filename> failed       change_headers, misc.c   WARNING 
 Header handling aborted, reading <filename> failed       change_headers, misc.c   WARNING 
 Hex conversion error in sms file <filename>: "<data>"   Value is less than one.   readSMShex, smsd.c   ERROR 
 Hex presentation error in sms file <filename>: incorrect length of data: "<line>"       readSMShex, smsd.c   ERROR 
 Invalid alphabet in file <filename>       mainspooler, smsd.c   NOTICE 
 Invalid alphabet in <filename>       sendsms, smsd.c   NOTICE 
 Invalid type of number in file <filename>       mainspooler, smsd.c   NOTICE 
 Invalid type of number in <filename>       sendsms, smsd.c   NOTICE 
 LENGTH termination error in sms file <filename>       readSMShex, smsd.c   ERROR 
 Looks like your device does not support reading of messages.       initmodem, modem.c   ERROR 
 Memory allocation error in handling of headers, filename: <filename>       db_store_incoming_message, database.c   ERROR 
 Memory allocation error in handling of headers, filename: <filename>       db_store_sent_message, database.c   ERROR 
 Memory allocation error in handling of query, filename: <filename>       db_store_incoming_message, database.c   ERROR 
 Memory allocation error in handling of query, filename: <filename>       db_store_sent_message, database.c   ERROR 
 Memory allocation error in handling of sender, filename: <filename>       db_store_incoming_message, database.c   ERROR 
 Memory allocation error in handling of text, filename: <filename>       db_store_incoming_message, database.c   ERROR 
 Memory allocation error in handling of text, filename: <filename>       db_store_sent_message, database.c   ERROR 
 Message limit <number> is reached.       devicespooler, smsd.c   WARNING 
 Modem did not accept mode selection, <error>       initmodem, modem.c   ERROR 
 Modem did not accept SMSC, <error>       initmodem, modem.c   ERROR 
 Modem did not accept the init string, <error>       initmodem, modem.c   ERROR 
 Modem did not accept the second init string, <error>       initmodem, modem.c   ERROR 
 Modem did not accept this PIN   Will terminate.   initmodem, modem.c   ERROR 
 Modem is not clear to send       write_to_modem, modem.c   ERROR 
 Modem is not ready to answer commands, <error> (Timeouts: <number>)       initmodem, modem.c   ERROR 
 Modem is not registered to the network       wait_network_registration, modem.c   ERROR 
 No destination in file <filename>       mainspooler, smsd.c   NOTICE 
 No destination in <filename>       sendsms, smsd.c   NOTICE 
 No destination in SQL SMS #<number>       db_check_message, database.c   NOTICE 
 PIN handling: expected "+CPIN: READY", modem answered <answer>   Will terminate.   initmodem, modem.c   CRITICAL 
 Registration is denied.       wait_network_registration, modem.c   ERROR 
 Regular_run <name> POST_RUN returned <number>       run_rr, misc.c   ERROR 
 Regular_run <name> PRE_RUN returned <number>       run_rr, misc.c   ERROR 
 Regular_run_post_run <name> POST_RUN returned <number>       run_rr, misc.c   ERROR 
 Saved routed message for later handling. However, you MUST DISABLE message routing with modem settings.       detect_routed_message, modem.c   ERROR 
 Saved routed status report for later handling. However, you MUST DISABLE status report routing with modem settings.       detect_routed_message, modem.c   ERROR 
 Sending SMS <part> to <number> has failed (modem initialization has failed)   Modem initialization has failed after sending has failed, no more retries.   send_part, smsd.c   WARNING 
 Sending SMS <part> to <number> has failed (smsd is terminating)   Smsd is terminating while retries still left. Not retrying..   send_part, smsd.c   WARNING 
 Sending SMS <part> to <number> has failed (too many retries)       send_part, smsd.c   WARNING 
 Shutting down database interface because of a failure       db_terminate, database.c   CRITICAL 
 SMS file <filename> rejected by checkhandler       mainspooler, smsd.c   NOTICE 
 Stopping. Cannot open dir <directory>: <reason>   Will terminate.   getfile, misc.c   CRITICAL 
 Stopping. Cannot read blacklist file <filename>.   Will terminate.   inblacklist, misc.c   CRITICAL 
 Stopping. cannot read whitelist file <filename>.   Will terminate.   inwhitelist_q, misc.c   CRITICAL 
 The answer from modem was not OK: <answer>   Will retry twice.   send_part, smsd.c   ERROR 
 The file <filename> has no text or data       mainspooler, smsd.c   NOTICE 
 The <filename> has incorrect first byte of UDH       sendsms, smsd.c   NOTICE 
 The <filename> has no data       sendsms, smsd.c   NOTICE 
 The <filename> has no text       sendsms, smsd.c   NOTICE 
 The <filename> has too long data for single part (Autosplit: 0) sending: <length>   Binary messages are not sent partially.   sendsms, smsd.c   NOTICE 
 The <filename> has too long data for system message: <length> (max <maxlength>)       sendsms, smsd.c   NOTICE 
 The <filename> has too long text       sendsms, smsd.c   NOTICE 
 The modem did not answer (expected OK)   Will retry twice.   send_part, smsd.c   ERROR 
 The SQL SMS #<number> has no text        db_check_message, database.c   NOTICE 
 <title> Connection failed <optional details>       db_connect, database.c   CRITICAL 
 <title> Failed to create a connection instance with name: <name>       db_connect, database.c   CRITICAL 
 Too long text/data in file <filename>       readSMStext, smsd.c   ERROR 
 Unexpected answer from modem after +CREG?, waiting <number> sec. before retrying       wait_network_registration, modem.c   ERROR 
 Wrong modem name <name> for SQL SMS #<number>       db_check_message, database.c   NOTICE 
 Wrong provider queue <name> for SQL SMS #<number>       db_check_message, database.c   NOTICE 
 Wrong provider queue <name> in file <filename>       mainspooler, smsd.c   NOTICE 
 Your SIM is locked. Unlock it manually.   Will terminate   initmodem, modem.c   CRITICAL 


smstools-3.1.15/doc/eventhandler.html000077500000000000000000000061631223712572200176120ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Eventhandler

The eventhandler is a program or script, that smsd runs, whenever it receives or sent a message or when it was not able to send a message.

Smsd calls the script after it has moved the file from the provider queue into the failed or sent queue.

Smsd gives two or three arguments to the eventhandler. The first one is SENT, RECEIVED, FAILED, REPORT or CALL. The second one is the SMS file filename. The third argument is the message id of the SENT message, it is only used if you sent a message successfully with status report enabled.

There is an example script in scipts/smsevent.

If you use UTF-8 character set as a locale, you might want to use eventhandler to convert received messages from ISO character set (which is internally used by the smsd) to UTF-8. See scripts/eventhandler-utf-8 for details. Also you can use the checkhandler to convert outgoing messages from UTF-8 to ISO, see scripts/checkhandler-utf-8 for code sample.

Alarm handler

The alarm handler is a program that smsd runs whenever an error occures.

See this document for possible error messages.

Smsd gives the following arguments to the alarmhandler:

Example:

#!/bin/sh
# This is an example how to use an alarm handler with smsd.
echo SMSD alarm: $5 $6

This script displays a message for each alarm.

 

The book describes how to use eventhandler to do different things automatically, like forwarding to eMail, storing received messages and status report into SQL database, running a self-test, publishing received messages with a webserver and more.

The book is about the version 2.x and written, maintained and owned by Stefan Frings.


smstools-3.1.15/doc/faq.html000077500000000000000000000264611223712572200157050ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Frequently Asked Questions

1) What hardware do I need?
2) How fast are the SMS server tools?
3) My GSM modem hangs often
4) Can I change the sender ID of sent messages?
5) How can I resend failed messages?

6) Command echoes are missing some characters
7) My modem does not answer to any AT-Commands
8) Error Message: Modem is not clear to send
9) Received messages or answers to AT commands contain often garbage.
10) How can I check the modem manually

11) What means +CMS ERROR 512?
12) Why does my modem answer ERROR?
13) I cannot receive messages
14) I cannot send messages
15) Error loading libmm.so.11

16) Can smart phones be used to receive messages?

1) What hardware do I need?
You need a Computer with at least one serial port. It does not matter how fast the CPU is and how much memory you installed. You also need at least one GSM modem with SMS command set according to the european specification GSM 07.05 (=ETSI TS 300 585), a character-based interface with hex-encoded binary transfer of message blocks ("PDU Mode") of it, and alphabet support according to the GSM 03.38 (=ETSI TS 100 900).

The software runs on Windows and all Unix-Style operating systems.

2) How fast are the SMS Server Tools?
The performance depends on the number of modems that you use and on the modem itself. Sending a single message takes between 5 and 10 seconds. You can increase the performance by using up to 64 modems.

3) My GSM modem hangs often
This is mostly caused by a weak power supply. Most GSM modems of Falcom and Siemens work with a 12V 800mA power supply. You may try to add a capaciator at the power supply (2200µF/25V) or change to a stabilized power supply.

4) Can I change the sender ID of sent messages?
This software uses the signalling channel that is limited to the senders phone number only. You cannot change it. If you really need that feature, you need a large-account to the SMSC and another software, for example SendXMS. Be prepared to very high costs.

5) How can I resend failed messages?
The program does already two retries. If you need more, then simply move the files from the failed directory to the outgoing directory. You can use the script smsresend to do that. This script inserts a retry counter so that the number of retries per message can be limited. Run this script with a timer (for example cron) if you need automatic retries.

6) Command echoes are missing some characters
You see in the logfile that the commands are echoed by the modem. But the echoes from the modem are missing some characters and therefore the modem does not answer or answers with ERROR. This happens when the modem or cable does not support hardware handshake and when the handshake wires are simply bridged. Try a lower bitrate or use the send_delay option in the config file to make the program even slower. But to make it really stable and fast, it's highly recommend to replace the bad hardware.

7) My modem does not answer to any AT-Commands
You probably set a wrong baudrate or a wrong serial port name. If both are ok, then ensure that no other program is currently using the serial port already. Many unix systems have a "getty" program for login through the serial port that you probably need to disable. The command lsof might be helpful to check this. If this is Ok, then check the modem manually.

It could bee a good idea to test the modem under another operating system (for example Windows or Knoppix) and it might also help to check if another modem is accessible on that suspicious serial port.

8) Error Message: Modem is not clear to send
The modem does not signal that it is ready to accept commands. The CTS wire is not activated. This can be caused by a broken cable, missing CTS wire or missing hardware handshake support in your hardware.

Try to disable hardware handshake in the config file and use the lowest possible baudrate.

9) Received messages or modem answers contain often garbage
This is mostly caused by missing hardware handshake, but it can also be caused by a too high serial bitrate. Try 2400 baud, this will have only minor effect to the overall performance. If this does not help, then try to use the send_delay option in the config file to make the program even slower.

Another cause might be that another program accesses the same serial port while the SMS Server Tools are talking to the modem.

10) How can I check the modem manually?
Check the modem using a terminal program like Hyperterminal, minicom or tip. Use the 9600 or 19200 baud, no parity, 8 bit, 1 stopbit. Enable hardware handshake (RTS/CTS) because this software also needs this. Here are some interesting AT-commands that you might try to enter:

Command Answer Meaning
AT OK Serial cable works and current serial settings are useable  
AT+CPIN? READY   The modem knows the PIN already
  PIN The modem needs the PIN number
  PUK The modem needs the PUK (the pin is locked)
  ERROR The SIM card if not found
AT+CPIN=1111 OK Enter the PIN
  ERROR The PIN is wrong or it was entered before
AT+CPIN=12345678,1111   OK Enter the fixed PUK and set a new PIN
  ERROR The PUK is wrong
AT+CREG? 0,1 The modem is connected to the phone network
  0,2 The modem is connecting to the phone network
  0,5 The modem is connecting to a roaming partner network
  other The modem is not connected to the network

11) What means +CMS ERROR 512?
This error code should be described in the modems manual. There are a lot of possible reasons. Most users see this error code when they try to send a message while the modem received another one at the same time. This causes a collision in the message transfer protocol between modem and SMSC. There is nothing you can do against this, therefore the software retries sending 2 times after an ERROR.

12) Why does my modem answer ERROR?
Normaly the modem manual should contain a description of every command incuding possible ERROR causes. Some modems support the command AT+CMEE=1 to get more informations about the ERROR cause. You can try to put this command into the init-string.

13) I cannot receive messages
First check that you have defined a modem to receiving. Default value for setting incoming is no.

Some modems store received messages in the internal memory instead of the SIM card but only the SIM card memory is accessible via serial port. In this case, the init-string AT+CPMS="SM","SM","SM" tells the modem to store all messages on the SIM card (SM). To store messages in the internal memory, use "ME" instead. Some modem do not accept three values in this command and answer with ERROR. In this case, try only one value: AT+CPMS="SM". Another possible setting is "MT" which tells the modem to access both memory locations. Falcom Samba and maybe some other modems ignore the AT+CPMS command. In this case, try AT^SSMSS=1 instead.

Some modems route received messages directly to the terminal and do not store messages on the SIM card. To disable this routing use the init-string like AT+CNMI=2,0,0,2,1. Check the manual of modem for correct values.

14) I cannot send messages
First check that the destination number is given using international format. If this does not help, define a correct SMSC number in the config file. Note that this number must also be given using international format. This setting often helps when the default SMSC number that is stored on the SIM card is wrong or missing.

15) Error loading libmm.so.11.
If you see an error message like this when you start smsd, then the operating system did not find the file libmm.so.11. Add the correct path (probably /usr/local/lib) to the environment variable LD_LIBRARY_PATH. GNU/Linux users can also set the path in /etc/ld.so.conf.

16) Can smart phones be used to receive messages?
Usually no. Smart phones do not show incoming messages to the AT command interface. Those devices can be used for sending only.
Generally:


If you did not find an answer to your question, please visit the SMSTools3 Community.
smstools-3.1.15/doc/fileformat.html000077500000000000000000000575021223712572200172660ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

SMS file format

Text messages

An SMS file is a text file that contains the message and a header. You have to store all SM you want to send in these files in the outgoing directory. The filename does not matter but it has to be unique. You may use the mktemp command to generate unique filenames.

Easy example:
To: 491721234567

Hello, this is the sms.

Write the phone number in international format without the leading +. When you like to send a message to a short number (for example to order a ringtone), then preceed it with an "s".

More complex example:
To: 491721234567
Flash: yes
Alphabet: ISO

Hello Stefan, how are you?

NOTE: Headers are case-sensitive.

You can add as many header lines, as you like. When the program finds an unknown header, it simply ignores that line. You can use the following header lines:

From Senders name or phone number. This field has currently no function to the software.
To Receivers phone number in international format without the leading +. When you like to send a message to a short number (for example to order a ringtone), then preceed it with an "s". With version >= 3.1 the number can be given using grouped format, like 358 12 345 6789.
To_TOA Available from version >= 3.1.5. Can be used to define receivers Type Of Address. This is also called "numbering plan". Possible values are

 Unknown No type is defined. Short numbers preceeded with "s" uses this value by default.
 International Number is international. This is default for other than short numbers.
 National Number is national. No country code is included. Some short numbers only work with this value.

See Using Type Of Address selection for more details.

Flash Boolean value. If yes, then the message appears directly on the phones display. Most phones support this feature, but not all.
Alphabet Tells the program what character set is used in this sms file. Possible values are

ISO
Latin
Ansi
Normal 8 bit character set, also called Ansi or Latin-1. All three keywords do the same.
GSM 7 bit character set, as described in the GSM specification.
UCS
Chinese
Unicode
UCS2 character set, maximum 70 characters. All three values do the same. The header must be written with an 8 bit character set but the message text part must be written with the 16 bit Unicode (big endian) character set. Please checkout the scripts directory, it contains some useful scripts for file format conversion.
binary The short message contains 8-bit binary data, no text.

The program checks only the first 3 characters of that line, therefore keywords like ISO-8859-15 or UCS-2 will also work fine.

UDH Only binary messages: Boolean value, tells if the message data contains a user data header. Default is true.
UDH-DATA User data header in hex-dump format. See udh.html and GSM 03.38. From version >= 3.1 also binary message can have UDH-DATA defined.
SMSC Phone number of the SMSC. From version >= 3.1 this setting is ignored if there is no smsc set in the config file.
Provider
Queue
Name of the provider, can be used to override the normal sorting algorithm configured by [providers] and [queues] in the config file. Both keywords do the same.
Report Boolean value. Controls if a status report is requested for this message. Without this line, the setting from config file is used.
Autosplit Controls if and how the program splits large text messages. Without this line, the setting from config file is used.

 0 disabled
 1 enabled, no part-number
 2 enabled, text numbers
 3 enabled, concatenated format (not supported by some phones)

Priority Available from version >= 3.0. Possible value is:

 High Message is handled first when moving to spooler and when taking from spooler to sending

Validity Available from version >= 3.0. Defines a message validity period. Without this line, the setting from config file is used.

You can specify value as a number following one of the keywords: min, hour, day, week, month or year. Validity period will be rounded down to the nearest possible value.

If you do not use any of those keywords, value will have the following meaning:

0 ... 143(value + 1) * 5 minutes (i.e. 5 minutes intervals up to 12 hours)
144 ... 16712 hours + ((value - 143) * 30 minutes) (i.e. 30 min intervals up to 24 hours)
168 ... 196(value - 166) * 1 day (i.e. 1 day intervals up to 30 days)
197 ... 255(value - 192) * 1 week (i.e. 1 week intervals up to 63 weeks)

Incorrect values are ignored.

Voicecall Boolean value. Available from version >= 3.0.

With this feature the smsd will make a voice call to the receivers phone number given in To: header. If the receiver answers to the call, some DTMF tones are played.
The message text must start with TONE: keyword. After this there can be number and space, which is number of times to repeat the tone sending. Supported tones are #,*,0...9 and the tone list must be comma separated.
For example:
TONE:   1,2,3,4,5,6,7,8,9,0
- this plays all digits, and it's repeated 3 times which is the default.

TONE:   5   #,#,#
- this plays three #'s, and it's repeated 5 times.

TONE:
- some default tones are played 3 times.

After version >= 3.1 additional TIME: number definition can be used. After a time has reached, hang up is done. If a call is answered before a time is reached, normal sound playing is done. NOTE that this time counting starts after a command is given to the modem and there will be some delay before receiving device starts ringing. You should test this with your own handset to find a reasonable time which works fine in the network you are using. Example:
TONE:   TIME:   15   2   #

Before using this feature to serious alarm purposes, you should test if this works with you modem/phone. Also notice that automatic redialing should be turned off in the phone's settings.

After version 3.1.3 VTS command usage can be selected with voicecall_vts_list setting, see the How to configure for more details.

After version 3.1.5 there is a new voicecall_ignore_modem_response setting for problematic devices, see the How to configure for more details. Also notice voicecall_hangup_ath setting if AT+CHUP does not hangup call on your device.

After version 3.1.7 there is a voicecall_cpas setting available. If your device returns OK immediately after a dial command, with this setting AT+CPAS can be used to detect when a call is answered. With this setting TIME: has no effect.

Hex Boolean value. Available from version >= 3.0.

Together with Alphabet: binary setting the binary data can be presented in hexadecimal format.
One byte should be presented with two hexadecimal characters, for example 0F. Text can have empty lines and comment lines starting with /, ', # or : character. Also after hexadecimal bytes there can be a comment character marking the rest of line as a comment.

Special keywords available:

STRING:A normal string can be presented (without needing to type it in hex)
INLINESTRING:As STRING:, but Inline String token and termination null are automatically added
LENGTHSet this keyword to the place where the following bytes should be counted. Next LENGTH keyword will place the counted number to the place where the first keyword was. Nesting is not possible.

See example below for more details.

Replace Numeric code 1...7. Available from >= 3.0.9.

If a receiving device and SIM supports "Replace Short Message Type n" -feature, a previously received message with the same code is replaced with a new message. Only the messages sent from the same originating address can be replaced. If there is nothing to replace, a message is stored in the normal way.

Include Filename. Available from >= 3.1.

Some parts of a message can be read from different file. If an included file contains only text part, it should begin with one empty line.

Macro Definition. Available from >= 3.1.

Syntax is: Macro: name=value. Multiple macros can be defined. All name's found in the message body are replaced with a value.

System_message Boolean value. Available from version >= 3.1.

With this setting message is sent as a system message. This kind of message has fixed values 0x40 for Protocol Identifier and 0xF4 for Digital Coding Scheme. A message cannot have User Data Header. Maximum length of a message is 140 bytes.

After version >= 3.1.7, value for this setting can be 2 or ToSIM for communicating with SIM applications. SMS is sent as SS (no show) and stored (sent) to SIM. Currently this only works with binary messages.

Note: In case of boolean values you can use true, yes, on or 1 for positive values. All other words are interpreted as negative.

Available from >= 3.0. After a message is sent, there will be automatically generated Message_id header line if a status report was requested. With version >= 3.1 there will also be Sent timestamp:
Message_idID number of a sent message.
SentTime when the message was sent by the program.

Available from >= 3.0.6.
Modem Name of the modem which was used to send this message.

Available from >= 3.0.9.
IMSI International Mobile Subscriber Identity from the SIM, if this request is supported.

Binary data

The data begins after the empty line and goes until end of file. No conversion is applied to the data. Data messages are limited to 140 bytes.

Example:

To: 491721234567
Alphabet: binary
UDH: true

gs2389gnsakj92"§Z/%$§"($)$(%ÄÖÜ?))((HJHG&(()/&§")(LJUFDZ)=W)==/685tgui
3ge^!"§$EGHWZFT&Z%8785ttghjjhdjkgfjsbfjwr793thruewgfh7328hgtwhg87324hf
hwer32873g&%§=)(/&%$%&/(/&%$§%&hdsgrwwq(/&%$fgzw543t43g5jwht934zt743g

Another example, available from version >= 3.0:

To: 491721234567
Alphabet: binary
Hex: yes

// This is a sample Wap Push message:

06 : user Data Header Length (6 Octets)
05 : Identifer Element (16 Bit port addressing)
04 : Length of Parameter values (4 Octets)
# WAP Push connectionless session service (client side), Protocol: WSP/Datagram:
0B 84 : push dest port (2948)
# WAP connectionless session service Protocol: WSP/Datagram:
23 F0 : push originator port (9200)

01 : Push Transaction Id
06 : PDU Type Push, (WAP-230-WSP Table 34)
LENGTH // Headers Length will be placed to this position
AE : Push Header Content-Type: application/vnd.wap.sic 0x2E | 0x80
# (http://www.wapforum.org/wina/wsp-content-type.htm)
LENGTH // This stops the counting and places the number

02 : WBXML version 1.2
05 : SL 1.0 Public Identifier
04 : Charset = iso-8859-1
00 : String table length
45 : s1, with content
C6 : indication, with content and attributes
0D : Token for "href=http://www."
### There should not be extra spaces after keyword:
INLINESTRING:xyz
85 : Token for ".com/"
03 : Inline string follows
STRING:ppaid/123/abc.wml
00 : End of string
11 : si-id
INLINESTRING:1
01 : close of indication attribute list
INLINESTRING:Wap push demo from smstools3.
01 : End of indication element
01 : END of si element

# Specifications can be found from here:
# http://www.openmobilealliance.org/tech/affiliates/wap/wapindex.html

Received messages

The received SMS are stored in the same format as described above but they have some additional header lines. For example:

From: 491721234567
From_SMSC: 491722270333
Sent: 00-02-21 22:26:23
Received: 00-02-21 22:26:29
Subject: modem1
Alphabet: ISO
UDH: false

This is the Text that I have sent with my mobile phone to the computer.

From Senders phone number.
From_TOA Available from >= 3.0.9. Type Of Address definition of senders phone number.
For example: "91 international, ISDN/telephone".
From_SMSC The SMS service centre, that sent you this message.
Subject The name of the modem that received this message.
IMSI Available from >= 3.0.9. International Mobile Subscriber Identity from the SIM, if this request is supported.
Report Available from >= 3.0.9. Tells if a status report is going to be returned to the SME.
Replace Available from >= 3.0.9. Replace Short Message Type 1..7 number, if defined.
Sent Time when the message was sent.
Received Time when the message was received by the program.
Alphabet Tells the character set of the message text.
UDH Boolean value. Tells if the message contains a user data header.
UDH-DATA This is the UDH in hex-dump format if the message contains an UDH. See udh.html and GSM 03.38.
Length Available from >= 3.1. Length of text / data. With Unicode text number of Unicode characters. If non-Unicode text message is stored using UTF-8, number of bytes may differ.
Flash Available from >= 3.1. Boolean value. This header exists if a message was received as a flash (immediate display). Note that usually phone devices do not save flash messages, they can be saved manually if necessary.

From version >= 3.0.9 there can be additional headers in case of some problems:
Error Tells if there was fatal errors and a message was not decoded.
Text part of message will tell more details and has no usual content.
Warning Tells if there was minor proglems in the message data.

The filenames of received messages look like modem1.xyzxyz. They begin with the name of the modem that received the message, followed by a dot, followed by six random characters.

Status Reports

You can request and receive status reports, if the SMSC and your modem support this feature. Example:

From: 491721234567
From_SMSC: 491722270333
Sent: 00-02-21 22:26:23
Received: 00-02-21 22:26:29
Subject: modem1
Alphabet: ISO
UDH: false

SMS STATUS REPORT
Message_id: 117
Discharge_timestamp: 00-02-21 22:27:01
Status: 0,Ok,short message received by the SME

Message_id This is the ID number of the previously sent message, where this status report belongs to. The SMSC gives each sent message such a number.
Discharge_timestamp This is the time, when the message was successfully delivered or when it was discarded by the SMSC.
Status The status of the message. Please take a look into the source code src/pdu.c if you need a list of all possible status codes.

Using Type Of Address selection

When SMSTools sends a message, it must tell to the Service Center what kind of number is used as a destination number. This is called "Type Of Address". There are three possible values: "unknown", "international" and "national".

By default SMSTools assumes that:

However, all "short" numbers do not work with "unknown" type. This is an operator depended issue and varies by country. Because of this, version >= 3.1.5 has a new header To_TOA which can be used to manually define Type Of Address.

Type Of Address selection can be automated using two global settings in the configuration file: international_prefixes and national_prefixes. Both settings are comma separated list of numbers.

If international_prefixes is defined, Type Of Address is international only if number matches to the list. If it does not match, national Type Of Address is used.

If national_prefixes is defined, Type Of Address is national if number matches to the list.

And last, if there is To_TOA defined in the message file, this setting is used as it overrides everything else.

For example:

Another example:


smstools-3.1.15/doc/gpl.html000077500000000000000000000446741223712572200157260ustar00rootroot00000000000000

GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.  
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Programs

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

one line to give the program's name and an idea of what it does.
Copyright (C) yyyy  name of author

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details
type `show w'.  This is free software, and you are welcome
to redistribute it under certain conditions; type `show c' 
for details.

The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright
interest in the program `Gnomovision'
(which makes passes at compilers) written 
by James Hacker.

signature of Ty Coon, 1 April 1989
Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. smstools-3.1.15/doc/hardwarecomp.html000077500000000000000000000135731223712572200176120ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Hardware compatibility

This Software needs a GSM modem with SMS command set according to the european specification GSM 07.05 (=ETSI TS 300 585), a character-based interface with hex-encoded binary transfer of message blocks ("PDU Mode") of it, and alphabet support according to the GSM 03.38 (=ETSI TS 100 900).

The list of all AT commands used in SMS Server Tools 3 is here.

Mobile phones have more often compatibility issues than real GSM modems.

Some USB devices are not supported by Unix operating systems.

The following list is incomplete and very old, mostly from the version 1.x times and it's not actively updated. It shows devices that are reported as well working:

Falcom A1 (mode=old, baudrate=9600)
Falcom A2 (baudrate=9600)
Falcom A2-D (baudrate=9600)
Falcom Twist Serial
Falcom Twist MC35 (init string AT+CNMI=2,0,0,2,1)
Falcom Tango
Falcom Samba (init=AT^SSMSS=1)
Falcom Samba 75 (status report works with init = AT+CPMS="MT","MT","MT")
Nokia 30 (status report not tested)
Nokia 22 (baudrate=115200)
Motorola G18 (with init string ATE0)
Multitech Modem MTCBA-G-F2
Multitech Modem MTCBA-G-F4 (unless set to unsolicited mode)
Siemens M10 (baudrate=9600)
Siemens M20 (baudrate=19200 Status report works fine with some firmware versions.)
Siemens TC35 (init string AT+CNMI=2,0,0,2,1 Some firmware versions are bad)
Siemens MC35i (init string AT+CPMS="SM")
Wavecom M1206
Wavecom 1206 b
Wavecom M1306B

Vodafone Mobile Connect Card GPRS (from Option) (see Note 3)
Vodafone Mobile Connect Card 3G/GPRS (from Option) (see Note 3)
Vodafone Mobile Connect Card 3G/W-LAN/GPRS (from Option) (see Note 3)

Nokia 6210e
Nokia 6310
Nokia 7110 (only with original RS232 cable)
Ericsson GM22 (see Note 1)
Ericsson R320s (see Note 2)
Ericsson R320 (init string AT+CPMS="SM")
Ericsson T39m (init string AT+CPMS="ME","ME","ME")
Sony-Ericsson T300 (init string AT+CPMS="ME", baudrate=115200)
Sony Ericsson T60
Sony Ericsson T68
Sony Ericsson T65 (do not use kudzu on the serial port)
Siemens S25
Siemens C35 (baudrate=19200)
Siemens S35i
Siemens M45 (initialize with AT+CPMS="SM")
Siemens ME45 (initialize with AT+CPMS="ME")
Siemens M50
Sharp GX-10 (only infrared tested)
Siemens MC60, M55 (see Note 4)
SIMCOM SIM600 (see Note 6)

Probably all mobile phones support status report but you cannot read them out with a computer. Status reports appear only on the phones display.

Serial adapters and converters

USB2Serial adpater cables, based on Prolific PL2303 chip
Digi Etherlite ethernet to serial
Equinox multi Serial PCI cards
MOXA CP-168U V2
Digi Portserver II (see Note 5)
MOXA nPort 5410 (4 serial ports to ethernet), MOXA nPort 5210 (2 ports), both with TTY driver


Notes:

1) Ericsson GM22

2) Ericsson R320s

3) Vodafone Mobile Connect Card

4) Siemens MC60, M55

5) Digi Portserver II

6) SIMCOM SIM600


smstools-3.1.15/doc/history3.html000077500000000000000000003417551223712572200167300ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Future plans (some of them)

Coming to the version 3.2.x, not yet released

Estimated release time for following additions is fall 2009. was fall 2009, but the publishing is delayed.

  1. Faster spooling algorithm. Required if system has lot of modems and very much messages to send. Current algorithm works well on systems with less than 10 - 15 modems and with about 500 -600 messages per hour per each modem.

  2. SQL database based spooling and message handling. Can be used together with current filesystem based spooling, or instead of it. At least MySQL will be supported, probably SQLite too.

Version history

21.10.2012    3.1.15

After a long time, some changes and fixes are pending, but in this version only one fix is applied. The bug with lockfiles is very critical, and hopefully the creation of new package is easier for maintainers, when only one fix is done.

Bug fixes:

  1. When creating a lockfile, main process used incorrect offset -1 with the table of names of processes. This caused segmentation fault when smsd was compiled using latest compilers.

Download


21.09.2010    3.1.14
  1. New setting for a modem: signal_quality_ber_ignore = yes/no.
    Default value: no.
    Some devices do not support Bit Error Rate when signal quality is asked, and this always provides "Bit Error Rate: not known or not detectable" to the log line and to the status file. With this setting ber can be ignored.

  2. When logging "SMS sent...", and there has been retries, count of retries is shown in the log line:
    SMS sent, Message_id: 12, To: 358401234567, sending time 34 sec. Retries: 2

  3. When logging "SMS received...", and message is a status report, explanation of status is included in the log line:
    SMS received (report, Message_id: 12, Status: 0,Ok,short message received by the SME), From: 358401234567

  4. New setting for a modem: verify_pdu = yes/no.
    Default value: no.
    This setting is for testing purposes. When trying to send SMS and modem does not accept the PDU, there may be a communication problem which breaks the sent PDU. With this setting smsd changes "echo on", and verifies the string which is echoed back after the PDU was sent. In case of mismatch "Verify PDU: ERROR" is printed to the log and also both sent and received strings are logged. In case of success "Verify PDU: OK" is printed to the log. After the verification is done, "echo" is changed back to "off".

  5. New setting for a modem: loglevel_lac_ci = number/word.
    Default value: LOG_INFO.
    Sets the verbosity of logging the Location area code and Cell ID and their changes. This requires that AT+CREG? returns location information. It is automatically enabled by pre_init using a command CREG=2. After the Location are code or Cell ID changes, quality of signal is also logged. This feature can be disabled with the setting which is more than current loglevel, for example 8.

  6. New setting for a modem: log_not_registered_after = number.
    Default value: 0.
    If it's known that the modem gives "not registered" after a message is sent or received, with this setting number of log messages can be avoided.

  7. When "MODEM IS NOT REGISTERED, WAITING..." is logged, also alarmhandler is called with a severity LOG_NOTICE.

  8. The script email2sms can handle e-mail with multiple recipients.

  9. New global settings:
    logtime_ms = yes/no
    Default value: no.
    logtime_us = yes/no
    Default value: no.
    With these settings the timestamp in the log file can have microseconds or milliseconds shown. These settings can be used when a default format for timestamp is in use. Value is shown after seconds and is delimited with a dot.

    If logtime_format is defined, these settings have no effect. Milliseconds or microseconds can be included in customized logtime_format using keywords timeus or timems.

  10. When max_continuous_sending time is reached and smsd will do other tasks, number of messages sent in max_continuous_sending time and average time for one message is logged with loglevel LOG_NOTICE.

  11. When the sending of SMS fails, trying time and number of retries are logged.

  12. New global setting: shell_test = yes/no.
    Default value: yes.
    When executable_check is enabled, testing of the shell can be omitted with this setting.

Bug fixes:

  1. When executable_check was enabled, smsd tested the shell using a /tmp directory for the script. This caused failure when /tmp was mounted noexec. Smsd now uses the incoming directory for the test.

  2. Handling of headers failed when /tmp directory was on different mount than /var/spool/sms tree. This caused that message file was not moved to the sent or failed directory.


02.09.2010    3.1.12
  1. Handling of files in the spooler directory has changed. When large number of modems are serving the same queue, previously more than one modem tried to pick up the same file and in some cases this caused conflicts and performance loss. This new version will avoid conflicts by using the internal list of file candidates.

  2. Global setting devices can be given in the shorten form: devices = <prefix>* <first_number> - <last_number>.
    For example: devices = GSM* 101-164 produces the same as devices = GSM101, GSM102, GSM103, etc. ...GSM164.

  3. Modem settings device and logfile can have a special keyword modemname, which is replaced with a name of a modem.
    For example:

  4. It is no more mandatory that the modem section exists in the configuration file. If all settings, including device, are set in the default section, modem section can be left out.

  5. Handling of timeout has changed. Previously smsd was expecting the whole answer from the modem in the given time, now timeout occurs if nothing is received in the given time.

  6. Lock files contain now process ID, and name of a device. This is for monitoring purposes. With large number of lock files in the spooler it is easy to see what modems are sending messages currently.

  7. Modem commands +CSQ and +CPIN now allow "echo on". However, in the normal operation "echo" should be set to off.

  8. When a quality of signal is logged, it's level is explained: Marginal (-95dBm or lower) / Workable (-85dBm to -95dBm) / Good (-75dBm to -85dBm) / Excellent (above -75dBm).

  9. When reading commands from regular_run_cmdfile, the expected answer can be defined in the begin of a line, between square brackets. See the How to configure for details.

  10. When reading a file from the spooler and waiting if it is still growing, the time of waiting is reduced from one second to half seconds.

  11. New setting for a modem: smsc_pdu = yes/no.
    Default value: no.
    If the number of SMSC is set in the configuration, or in the message file, the number is included in the PDU instead of changing SMSC with a command AT+CSCA. The number can be presented in international format, or in national format starting with 0.

  12. New setting for a modem: voicecall_clcc = yes/no.
    Default value: no.
    Defines if AT+CLCC is used to detect when a call is answered. This is required if modem returns OK immediately after the call and voicecall_cpas cannot be used.

  13. When check_memory_method is set to use any CMGL method, larger buffer is used for handling the data. In normal cases about 400 messages can be handled. With some problematic devices the status report may contain extra padding which is almost 300 characters. This causes that about 50 status reports can be handled simultaneously. If this limit is reached, use some other than CMGL method.

  14. When a device is a socket (network modem) and is using Telnet protocol, smsd uses the Q Method implementation for option negotiation. Simply, every DO command is answered with WONT, and every WILL with DONT. This feature is very simple, and may not work with some devices or with some options. It is recommended to disable Telnet in the modem setup.

  15. New setting for a modem: telnet_login = string.
    Default value: empty.
    If a network modem requires telnet login, it can be defined with this setting.

  16. New setting for a modem: telnet_login_prompt = string.
    Default value: login:.
    If telnet_login is used, this setting can be used to change the login prompt.

  17. New setting for a modem: telnet_login_prompt_ignore = string.
    Default value: Last login:.
    If telnet_login is used and after successful login motd contains a string which is the same as telnet_login_prompt, this setting can be used to define which kind of a string is ignored. For example, telnet_login_prompt can be login: and telnet_login_prompt_ignore could be Last login:.

  18. New setting for a modem: telnet_password = string.
    Default value: empty.
    If telnet_login is used and device requires password, it can be defined with this setting.

  19. New setting for a modem: telnet_password_prompt = string.
    Default value: Password:.
    If telnet_password is used, this setting can be defined to change the prompt for password.

Bug fixes:

  1. A modem setting trust_spool did not work properly. When the spool directory was trusted, smsd did not check existence of lock files and this caused conflicts and slow performance when large number of modems were serving the same queue directory.

  2. When headers of SMS file were handled, temporary files were created to the spooler directory. With large number of modems and slow system this caused unnecessary error messages to the log. Temporary files are now placed into the /tmp directory.

  3. When a command was read from the regular_run_cmdfile, control characters were removed from the string. This caused that PDU termination (Ctrl-Z) could not be used.

  4. Even when modem_disabled = yes was used, smsd tried to initialize modem if incoming = no was used. Also send_startstring and send_stopstring did not check if a modem is disabled.


21.06.2010    3.1.11
  1. A modem setting ussd_convert = number can now have a new value: 4. With this value text part from the USSD answer is converted from hexadecimal dump to ASCII.

  2. Some cleanup has been done to the code. The fix for "overlapped buffers" bug now uses it's own function containing a simple loop, instead of using memmove().


19.06.2010    3.1.10

Bug fixes:

  1. Global settings international_prefixes, national_prefixes and global and modem setting priviledged_numbers did not always work because of an "overlapped buffers" bug. This failure happened on 32bit environments too.


17.06.2010    3.1.9
  1. New setting for a modem: trust_spool = yes/no.
    Default value: yes.
    When a modem process is searching for a file from the spooler, it assumes that the file is complete when it exists and it's not locked. This will speed up sending, because one second delay is avoided. If some other process or checkhandler is creating files directly to the spooler, it may be necessary to set this to no.

  2. When searching for a file to send, the lock file is created before the one second delay is spent.

  3. New global setting: log_read_from_modem = yes/no.
    Default value: no.
    In some cases, when resolving troubles with a modem, it's useful to see exact data which is received from the modem. This setting is similar than log_unmodified, but displays the data as a hexadecimal dump.

  4. Programs regular_run and regular_run_post_run now get the device name as a third argument.

  5. The script sendsms has automatic detection of character set and creates Unicode messages if necessary.

  6. New setting for a modem: send_handshake_select = yes/no.
    Default value: yes.
    Instead of checking the flag TIOCM_CTS, select() function is used when the serial transmitted is busy. If because of some reason the old style should be used, this setting allows it.

  7. With a setting trim_text = yes, whitespaces are not removed if the message is going blank. This is because some people, really, need to send a message which contains only single space character.

Bug fixes:

  1. When manipulating strings, the coding style assumed that strcpy() and some other functions start copying from the begin of a buffer. That's how those have worked for years, but it's against what the manual of those functions says about overlapped buffers. With 64bit Ubuntu 10.04 running on Intel processor this caused serious failures. The code is reviewed and fixed.

  2. When a Sent header was printed to the incoming message file, wrong variable was used causing that only current date was printed.

  3. The setting umask did not work properly and that caused the message files became world writable.


05.05.2010    3.1.8
  1. Added "init info" for chkconfig to the init.d script sms3.

Bug fixes:

  1. When a signal quality was asked from the modem, and it was explained to the log, incorrect severity was used. Changed severity from LOG_NOTICE to LOG_INFO.


02.05.2010    3.1.7
  1. When a signal quality is asked from the modem, it's also explained to the log.

  2. The sample script sendsms can now use keys for protecting the usage, for example when running with tcpserver over the internet.

  3. The name of a global setting datetime is changed to datetime_format. The old name can still be used because of backward compatibility.

  4. The init.d script sms3 now uses option -n for setting the process title: ARGS="-n MAINPROCESS -p$PIDFILE -i$INFOFILE". Smsd still works without that option, because of backward compatibility.

Bug fixes:

  1. If a setting voicecall_hangup_ath was not defined in the configuration, default value was handled as yes, but it's defined as no.

  2. A modem setting status_signal_quality was not parsed and was giving a startup error.


20.04.2010    3.1.7beta7

  1. New global setting: suspend = filename.
    Default value: not in use.
    With this file, any modem process can be suspended. When a process is suspended, the modem port is closed and modem process does not do anything.

    The file is checked before smsd starts sending or receiving. If a name of the device is found from the file, suspend will start. The format of a line in file is: <devicename>: <reason>. Colon is needed, but the <reason> is optional. It is printed to the log. A special line ALL: <reason> can also be used, and this affects for all modem processes.

    When a process is suspended, the file is checked every ten seconds, or more frequently if the delaytime value is smaller.

    When a process is suspended, it listens a signal SIGUSR2. If this signal is received, modem process will send and receive messages once.

  2. New setting for a modem: phonecalls_error_max = number.
    Default value: 3.
    Specifies the maximum number of errors before phonecalls are ignored.

Bug fixes:

  1. Modem setting socket_connection_errorsleeptime (from 3.1.7beta4) did not work because it's name is longer than the code was able to handle.

18.04.2010    3.1.7beta6

  1. A modem setting phonecalls = yes/no can now have a new value: clip, or 2. This value could be used with modems which do not support reading of phonebook, or phobebook cannot be used because entries cannot be deleted. Phocecall is detected using the +CLIP Calling line identification report from a modem.

    When phonecalls = clip is used, a setting hangup_incoming_call is automaticatty set to yes. This is because smsd must hangup a call before it's handled. Smsd also initializes a modem with +CLIP=1 automatically to enable this functionality.

  2. File of a missed call has now more headers, like file of a received SMS has:

  3. The setting date_filename now applies for files of missed calls too.

Bug fixes:

  1. When filename_preview was used, creation of modified filename for message file of a missed call caused modem process to freeze.

  2. When dialling of a voicecall ended with timeout, the result was taken from the answer of hangup and was "OK". Now it's "Timeout".

16.04.2010    3.1.7beta5

  1. When running smsd as an unpriviledged user and group is defined, given group is added to the group access list which is initialized by reading the group database. In the previous versions of smsd the given group was used as only group.

  2. New settings for a modem:

    start = modem command.
    Default value: not in use.
    startsleeptime = number.
    Default value: 3.
    stop = modem command.
    Default value: not in use.

    If defined, start command is sent to the modem when a modem process starts. After a command is sent, startsleeptime is spent. When a modem process is stopping, stop command is sent to the modem.

  3. When using queues without provider sorting, it's no more necessary to define providers. The number list of each queue defaults to "catch-all", which is the same as "0,1,2,3,4,5,6,7,8,9,s". See the manual for more details and examples.

Bug fixes:

  1. In the 3.1.7beta4, when eventhandler_ussd was used and the answer was read back from the file, extra linefeed was included in the answer string. This linefeed was printed to the log and statfile.

11.04.2010    3.1.7beta4

In addition to other changes, this version contains couple of changes which were provided as a patch by yjh, a member of SMSTools3 Community. This topic contains more details. These changes were originally going to the version 3.2, but because it's still delayed, I publish those changes now.

In 3.2, handling of character sets is based on iconv and the code is widely changed. Because the "iconv" patch in 3.1.7 is not very well tested, it's disabled by default. Edit the src/Makefile to enable the patch, if necessary. The patch requires that UTF-8 is used as a locale.

  1. New global setting: logtime_format = format string.
    Default value: compatible with previous versions of smsd.
    With this setting a format of the timestamp in the logging can be set. The format string is strftime() compatible.

  2. New global setting: use_linux_ps_trick = yes/no.
    Default value: no/yes
    This setting changes the way how smsd processes are shown in the process list. Instead of command line, processes can be shown like "smsd: MAINPROCESS", "smsd: GSM1" etc. In the Makefile there is a definition which sets the default value for this setting. This is a "Linux trick", it's not quaranteed that this can be used in all possible environments.

  3. In the outgoing message file, the setting System_message can now have a value 2 or ToSIM for communicating with SIM applications. SMS is sent as SS (no show) and stored (sent) to SIM. Currently this only works with binary messages.

  4. If USE_ICONV is defined in the src/Makefile, iconv is used for character set conversions. The current implementation works with Unicode and UTF-8 and could be used with cyrillic languages.

  5. New global setting: date_filename_format = format string.
    Default value: compatible with previous versions of smsd.
    With this setting a format of the timestamp in filenames (date_filename >= 0) can be set. The format string is strftime() compatible.

  6. New setting for a modem: device_open_retries = number.
    Default value: 1.
    Defines how many times smsd will retry when cannot open a device. When maximum number of retries is reached, modem process will call alarmhandler and stop. With value -1 smsd will retry forever.

  7. New setting for a modem: device_open_alarm_after = number.
    Default value: 0.
    After defined number of retries, an alarmhandler is called. Smsd still continues trying, if device_open_retries value is bigger.

  8. New setting for a modem: device_open_errorsleeptime = number.
    Default value: 30.
    Defines how many seconds the smsd will sleep after an error with device open.

  9. New setting for a modem: socket_connection_errorsleeptime = number.
    Default value: 5.
    Defines how many seconds the smsd will sleep after an error with socket connection.

  10. New command line options: -Ex and -Dx.

    Both options can used as a helper for USSD message handlers. When these optios are used, smsd does not start as a daemon. Therefore options can be used even when smsd is running as a daemon.

  11. New setting for a modem: ussd_convert = number.
    Default value: 0.
    Defines if a text part from incoming USSD message is decoded. Possible values are:
     1 Unicode format is converted to UTF-8. This requires that USE_ICONV is defined.
     2 GSM 7bit packed format is converted to ISO or UTF-8.

    Decoded text is appended to the original answer with two slashes and one space character.

  12. New setting for a modem: eventhandler_ussd = filename.
    Default value: not in use.
    This setting defines an eventhandler to use with USSD messages. It is possible to use the same script or program which is used as eventhandler, but it's not a default because basically those scripts are not compatible without modifications.

    After an USSD message is received, and probably ussd_convert is done, eventhandler_ussd is called. Smsd checks what is the current character set of a modem and creates a temporary file which contains the USSD answer. Arguments for the eventhandler_ussd are:
     $1 "USSD" keyword.
     $2 Filename (which contains the answer).
     $3 Devicename.
     $4 Character set.
     $5 Command what was used to get the USSD answer.

    Eventhandler_ussd can do whatever is needed with the USSD answer. It can also modify the answer, or delete the file. After eventhandler_ussd returns, smsd will check if the file still exists. If it exists, it's first line is taken as a new answer. Modified answer is then logged and probably printed to the regular_run_statfile.

Bug fixes:

  1. Removed reasons for compiler warning messages when compiling with -W -Wall under x64.

  2. In 3.1.7beta2 socket_connection_retries was counted incorrectly.

30.03.2010    3.1.7beta3

One important feature has been requested, but I forgot to include it in the previous version :(. So here it is:
  1. New setting for a modem: regular_run_post_run = filename.
    Default value: not in use.
    This setting can define the second script or program which is executed regularly. The same script with regular_run can be used. The script gets an argument $1 which is PRE_RUN for regular_run and POST_RUN for regular_run_post_run. There is also the second argument $2, which includes a definition of regular_run_statfile.

    This is how the regular_run for a modem currently works:

29.03.2010    3.1.7beta2

  1. Smsd can connect directly to the network modem. This enhancement is provided by Hubert Gilch, SEP Logistik AG.

    A device definition which starts with @ character is handled as a socket. Format for the internet host is: @<host_or_ip>:<port>. Host definition can be name or IP address.

  2. New setting for a modem: socket_connection_retries = number.
    Default value: 11.
    Defines how many times smsd will retry when a socket connection fails. When maximum number of retries is reached, modem process will call alarmhandler and stop. With value -1 smsd will retry forever.

  3. New setting for a modem: socket_connection_alarm_after = number.
    Default value: 0.
    After defined number of retries, an alarmhandler is called. Smsd still continues trying, if socket_connection_retries value is bigger.

  4. New setting for a modem: report_device_details = yes/no.
    Default value: no/yes.
    Defines if a details from device are printed to the log when modem process is starting. With beta versions of smsd this setting defaults to yes, otherwise it defaults to no.

  5. New setting for a modem: using_routed_status_report = yes/no.
    Default value: no.
    Smsd can detect routed status reports, but usually it's not recommended to use them. Modems should store status reports as an ordinary messages, which can be read when smsd will need them. However, some modem cannot store status reports, and therefore routing is the only way to get them. With this setting smsd will change some severity and text of a logging.

  6. New setting for a modem: routed_status_report_cnma = yes/no.
    Default value: yes.
    Defines if +CNMA acknowledgement is needed to send after routed status report was received.

  7. New setting for a modem: phonecalls_purge = yes/no/string.
    Default value: no.
    Usually missed calls are deleted from the device using AT+CPBW=index command. Some modems do not support this command, and have an alternative way to clear Missed Calls storage. With setting yes, this feature uses Siemens compatilbe way for purging: AT^SPBD="MC". Another command can be defined as a string.

  8. New setting for a modem: voicecall_vts_quotation_marks = yes/no.
    Default value: no.
    Defines if quotation marks are used when sending VTS command to the modem. NOTE: previously quotation marks were used, now this setting default to no.

  9. New setting for a modem: voicecall_cpas = yes/no.
    Default value: no.
    Defines if AT+CPAS is used to detect when a call is answered. This is required if modem returns OK immediately after the call.

  10. New setting for a modem: needs_wakeup_at = yes/no.
    Default value: no.
    After being idle, some modems do not answer to the first AT command. For example with BenQ M32, there can be OK answer, but in many times there is not. To avoid error messages, smsd first send AT and read the answer if it's available.

  11. New setting for a modem: keep_messages = yes/no.
    Default value: no.
    Defines if messages are not deleted from the device. Unlike a global setting keep_messages, smsd continues running.

  12. New global setting: umask = value.
    Default value: empty.
    Effective umask for smsd can be set in the configuration file. Value can be hexadecimal, decimal or octal format.

  13. New global setting: log_unmodified = yes/no.
    Default value: no.
    In some cases, when resolving troubles with a modem, it's useful to see what kind of line ends were received from the modem. With this setting spaces and line ends are not removed from the string which is logged. This setting overrides the setting log_single_lines.

  14. New global setting: trim_text = yes/no.
    Default value: yes.
    With this setting trailing whitespaces are removed from the outgoing message. Does not effect with messages written using Unicode or GSM alphabet.

  15. When a modem process is starting, it's checked if reading of messages is supported. This version will not do the check, if a device is not going to read incoming messages.

  16. When checking the PIN, some modems include quotation marks in the answer. Some modems may leave a space away after +CPIN:. Those kind of answers are now handled.

  17. When initializing a modem, and it does not respond, after 5 retries the port is closed and reopened.

  18. When handling the smsc setting from configuration file, extra + sign is removed from it.

  19. Message counter file (for example GSM1.counter) is always created, even when messages are not yet sent.

  20. Startup check will check that any modem does not use duplicate device name.

  21. Locked files in the spooler: as in the past, *.LOCK files are handled as locked files, and now LOCKED* files are handled as locked too.

  22. When using a communication mode, it's no longer necessary that the name of a modem is in the devices list. It's enough that a section for the modem exists. Note, that still no more than one smsd can run at the same time.

  23. When sending SMS and From field is not defined, content of a setting number is printed to the log: "Sending SMS from <number> to <destination>". If the setting number is not defined, log line is as before: "Sending SMS from  to <destination>".

Bug fixes:

  1. When a GMGL list was handled and sorted, older message was not read first. With this fix messages are sorted by the date and time when a message was sent.

  2. When reading messages from the modem, some modems return only LF characters, even when both CR and LF should be returned. Some modems may give the answer with double line-ending. Both cases are now handled.

01.02.2010    3.1.7beta

This is not the major version change, it's publishing is still delayed. This version continues 3.1.x and contains some minor features which may be useful for some users.
  1. New global setting: ignore_exec_output = yes/no.
    Default value: no.
    Usually eventhandlers should not output anything. If there is some output, it usually means that some error has occurred. These errors are often problematic, because that output cannot be seen and error is therefore not notified. Smsd will print any output from the evenhandlers to the log file. This output can also be used for debugging purposes, but usually debugging should be done by running eventhandler manually. With a setting ignore_exec_output = yes this feature can be disabled.


30.11.2009    3.1.6

This version was released because of a critical bug in the handling of a concatenation storage. Users of version 3.1.5 should upgrade to this version. As a workaround, disable purging of a concatenation storage with a global setting in the smsd.conf: ic_purge_hours = 0. If ic_purge_minutes was set, remove the setting or change it to 0.
  1. Outgoing message files with the same timestamp are selected by name.

  2. Carriage return characters are removed from the modem response when writing it to the log.

  3. Trouble logging is slightly enhanced: if something was printed to the log and trouble is over, "Everything is ok now" is printed to the trouble log.

  4. Regular_run_statfile is created using the same mask as the message files.

Bug fixes:

  1. Purging of a concatenation storage freezed modem process in some environments. This was caused by too small buffer.

  2. With incoming Unicode messages the Euro character was decoded incorrectly.

  3. When checkhandler spooled a message and returned a value 2, trouble logging was started.

  4. Destination number was accepted even if it contained 's' only.

  5. Check_memory_method using CMGL did not accept zero as a message number.


01.06.2009    3.1.5

  1. New global setting: smart_logging = yes/no.
    Default value: no.
    This feature is available when file based logging is used. If loglevel is less than 7 (for example "notice" is a good choise with smart_logging), trouble log (with loglevel 7) about whole communication is written to different file if there has been any errors.

    "Whole communication" means sending single SMS, receiving single SMS, and other things what smsd will do after idle time is spent. When communication starts, all possible log lines are collected to internal buffer and only loglevel lines are written to the logfile. If during communication there are any errors, all collected lines are printed to trouble log when communication reaches it's end.

    This feature was made because with loglevel 7 logfile grows very much and fast, and debug level is not usually needed when there was no any errors. In case of errors it's important to see whole communication, not just a single line which says that "something happened".

    File name is created with the rule: if lenght of logfile setting is more than 4 characters and setting ends with ".log", trouble log filename will end with "_trouble.log". If length is less than 4 or setting does not end with ".log", trouble log filename is logfile appended with ".trouble". In usual cases logfile is /var/log/smsd.log and trouble log filename will be /var/log/smsd_trouble.log, or in some (Debian, Ubuntu, ...) distributions: /var/log/smstools/smsd.log and /var/log/smstools/smsd_trouble.log.

  2. New setting for a modem: unexpected_input_is_trouble = yes/no
    Default value: yes.
    With smart_logging, this setting defines if unexpected input activates trouble log.

  3. New global and modem setting: hangup_incoming_call = yes/no
    Default value: no.
    If set to yes and detected unexpected input contains "RING", incoming call is ended. Use setting voicecall_hangup_ath to define if "ATH" is used to make hangup instead of "AT+CHUP".

  4. New setting for a modem: communication_delay = number
    Default value: 0.
    Only some very problematic modems may need this setting. Defines minimum time in milliseconds between latest answer from modem and next command which will be sent to modem.

  5. New global setting: status_interval = number.
    Default value: 1.
    If statistics function is enabled and stats directory is defined, smsd writes file named status into this directory. The file contains status of all modems in the first line using Status: header (this is similar than smsd -s outputs to console) and explained status in the next lines using modem's name as a header. Smsd writes status file every status_interval seconds if a status has changed. Value 0 disables this feature.

    For example, the output is like:

    Status:     09-05-27 20:46:17, irir------------
    SONERA:     09-05-27 20:46:09, Idle,      123, 0, 321, ssi: -63 dBm, ber: < 0.2 %
    ELISA:      09-05-27 20:46:12, Receiving, 234, 0, 432, ssi: -73 dBm, ber: < 0.2 %
    DNA:        09-05-27 20:46:06, Idle,      456, 0, 543, ssi: -77 dBm, ber: < 0.2 %
    SAUNALAHTI: 09-05-27 20:46:14, Receiving, 678, 0, 654, ssi: -69 dBm, ber: < 0.2 %
    

    Timestamp value tells when status is created or modem initialization was last started.

    Status can be: (s) Sending, (r) Receiving, (i) Idle, (b) Blocked, (t) Trouble, (-) Unknown. Trouble -status means that something abnormal has happened and if smart_logging is in use, trouble log will be written.

    Counters are: sent, failed, received. Sent counter is the value from stats/<modemname>.counter file. Smsd does not clear this value. Failed and received counters are from original statistics data and they are cleared each time when stats are stored (stats_interval), or smsd is restarted.

  6. New global and modem settings:
    status_include_counters = yes/no
    Default value: yes.
    status_signal_quality = yes/no
    Default value: yes.
    Modem setting overrides global setting. These settings define if message counters and explained signal quality is included in the line of status file.

  7. New global and modem setting:
    max_continuous_sending = number
    Default value: 300 (5 min).
    As usual, modem setting overrides global setting. This setting is in seconds and defines how long modem can send messages without doing anything else. After max_continuous_sending time is reached, received messages are checked and other tasks are run.

  8. Number of modems can be defined in the src/Makefile. Default value is 64. If you are running SMSTools3 in an embedded device, you can use NUMBER_OF_MODEMS=1 to save memory. This setting also affects the number of provider queues. If you need more provider queues than you have modems, just increase the number of modems value.

  9. Modem setting init2 can be used to monitor signal quality. When init2 contains AT+CSQ (uppercase), signal quality is explained in the log file using log level "notice".

  10. If shared memory is not in use, global setting stats_no_zeroes defaults to yes.

  11. Error message after PDU was sent is changed: Previously: "The modem said ERROR or did not answer.". Now: "The modem did not answer (expected OK)." or "The modem answer was not OK: <the answer what it was>" depending on the answer. This change is because in some cases it's important to see if there was any answer or not.

  12. IMSI (International Mobile Subscriber Identity) is now logged with log level "notice". If AT+CIMI query was supported, Product Serial Number is also logged (CGSN).

  13. Modem setting voicecall_hangup_ath is now global too.

  14. Modem setting queues can be defined using special keyword modemname which is replaced with a name of modem.

  15. New section in configuration file: [default]. This section can be used to define default settings for all modems. If setup has large count of similar modems, almost all settings can be defined once in [default] section and only device depended settings like device are required to define in the modem sections. In the future versions this will replace all modem settings which are now defined in the global part of configuration.

    As "default" is now reserved name, it cannot be used as a modem name.

Bug fixes:

  1. Smsd did not compile on old GCC because two variables were defined elsewhere than begin of a block.

  2. When logged to syslog, modem processes missed the correct program name. Now the program name is always smsd and in addition, MAINPROCESS: title is included in the lines which are from the main process.

  3. During the startup check, smsd used file mode 0600 when checking directories. In Cygwin this was not enough when mode 0666 was inherited and directories were 0755. For testing smsd uses now the same mode which is later used to create message files.


11.05.2009    3.1.5beta9

  1. Maximum number of provider queues is increased to 64 which is the same as maximum number of modems.

  2. New setting for a modem: voicecall_ignore_modem_response = yes/no.
    Default value: no.
    When a voicecall is ringing, some devices give OK answer after couple of seconds even if a call is not yet answered. With this kind of device DTMF tones cannot be sent. If a ringing time is defined in the message file (using TIME: n), the waiting loop is breaked too soon. To avoid this, use voicecall_ignore_modem_response = yes in the modem settings. With this setting call rings n seconds (if not answered) and after this voicecall is over.

  3. New setting for a modem: voicecall_hangup_ath = yes/no.
    Default value: no.
    Defines if ATH is used to hangup call instead of AT+CHUP.

Bug fixes:

  1. When PDU had no sender address included, decoding failed with an error message "Invalid sender address length: 00".

05.04.2009    3.1.5beta8

  1. New setting for a modem: detect_unexpected_input = yes/no
    Default value: yes.
    Before any command is sent to the modem, smsd checks if there is some unexpected input. For example some modem may send new message identification (CMTI) if settings are incorrect. Any unexpected input will be logged.

31.03.2009    3.1.5beta7

  1. If signal quality is asked from a modem (when initializing it or when running a regular_run), the result is explained in the log file.
    For example:

    2009-03-17 12:40:05,5, GSM1: CMD: AT+CSQ: +CSQ: 20,0 OK 
    2009-03-17 12:40:05,5, GSM1: Signal Strength Indicator: -73 dBm, 
    Bit Error Rate: less than 0.2 %
    

  2. New setting for a modem: detect_message_routing = yes/no
    Default value: yes.
    By default smsd tries to detect if a modem is in message routing mode. Before sending a command smsd listens if some data with routed message is available. Also, after a command is sent, smsd checks the answer. In both cases, if there is one or more routed message coming, a notification is written to the log and alarmhandler is called. Routed messages are saved and handled later when smsd can do it.

    NOTE: This checking is done to avoid errors and loss of messages. Routing mode SHOULD NOT BE USED in normal operation. With routing mode it is NOT quaranteed that all messages are delivered. Some devices are in routing mode by default and this feature helps to detect it. Use init = AT+CNMI=... with suitable values to disable routing mode. Values depend on modem, see the manual of a modem for details. All received messages must be stored to the message store which is usually "SM" (SIM card memory).

  3. New global setting: shell = filename.
    Default value: /bin/sh.
    Defines which shell is used to run eventhandler and other external scripts.

    During the startup check it is now verified that the shell is accessible and it works. Runtime errors are now logged with details.

  4. If running of eventhandler or regular_run fails, an administrative alert is sent (if admin_to is specified). Each process has it's own error status and while the error remains, no more messages are sent unless the problem is first solved.

  5. New global setting: adminmessage_device = name.
    Default value: first available device.
    This settings defines which modem is used to send administrative messages when mainspooler detects some trouble.

  6. New settings for a modem:
    adminmessage_limit = number
    Default value: 0.
    adminmessage_count_clear = number
    Default value: 0.
    With these settings sending of administrative messsages can be limited. adminmessage_limit, defines a maximum number of messages to be sent. adminmessage_count_clear defines a period to automatically clear the message counter. This value is number of minutes.

  7. Timeout for message sending is increased. If your modem still gets timeout, use a modem setting read_timeout = 10 to increase value even more.

  8. USSD messages using regular run for a modem feature: You can use a regular_run_cmd = AT+CUSD=1,"*100#",0; to get saldo details of a prepaid SIM. When a command starts with AT+CUSD and the length is more than 9 characters, smsd will wait response which starts with +CUSD: instead of OK string. For example in Finland the response in the log file will be like (here split into three lines):

    2009-03-30 11:56:00,5, GSM1: CMD: AT+CUSD=1,"*100#",0;: 
    OK +CUSD: 2,"Liittymäsi saldo on 35.95 EUR ja 
    voimassaoloaika päättyy 27.07.2009.",15
    

  9. Sample script sendsms now checks if an user "smsd" is existing, and if it is, ownership of an outgoing file is given to the user "smsd". If you are using some else unpriviledged user to run smsd, change the script variable "smsd_user".

15.03.2009    3.1.5beta6

  1. New global settings:
    ic_purge_hours = number
    Default value: 24.
    ic_purge_minutes = number
    Default value: 0.
    ic_purge_read = yes/no
    Default value: yes.
    ic_purge_interval = number
    Default value: 30.
    These settings defines how concatenation storage is purged when internal_combine is used and messages with missing parts are received. Storage is checked every ic_purge_interval minutes. If there are message parts older than defined with ic_purge_hours and/or ic_purge_minutes settings, message parts are deleted. If ic_purge_read is yes, message is stored to the incoming folder. In this case there will be only one part in the file and a header Incomplete: yes indicates that message is broken. Value 0 for both ic_purge_hours and ic_purge_minutes disables this feature.

  2. New settings for a modem:
    ms_purge_hours = number
    Default value: 6.
    ms_purge_minutes = number
    Default value: 0.
    ms_purge_read = yes/no
    Default value: yes.
    These settings are used only with SIM600 (or compatible) modems (check_memory_method 5). If multipart message is received with one or more parts missing, incomplete message is removed from the message storage after time defined. Time is calculated from timestamp of first available part. Value 0 for both settings disables this feature. Setting ms_purge_read defines if parts are read before they are deleted. If internal_combine setting is used, parts are stored to the concatenation storage. If missing part(s) are later received, there will be similar timeout before parts are purged. After this the messsage is complete and is stored to the incoming folder.

  3. New setting for a modem: read_timeout = number.
    Default value: 5.
    When smsd reads data from a modem, timeout will occur after read_timeout seconds if an acceptable answer is not received. Some very slow devices might need greater value than 5 seconds.

  4. A modem setting check_network is enhanced. Possible values are:
     0
     no 
    Network registration is not checked.
     1
     yes 
    Network registration is always checked
     2 Network registration is checked only when preparing to send messages.

    Default value is 1. If a modem does not support network checking, checking is automatically ignored.
    With value 2 incoming messages are processed faster.

11.03.2009    3.1.5beta5

  1. New value for check_memory_method: 5 = CMGL list is used and messages are handled with SIMCOM SIM600 compatible way: Multipart message is handled after all of it's parts are available. After multipart message is handled, only the first part is deleted by the smsd. The modem will delete rest parts by itself.

  2. New global and modem settings: priviledged_numbers = list of numbers
    Default values: not in use.
    These lists can be used with check_memory_method values 31, 41, and 5. If a modem setting is defined, it overrides global setting which is default for each modem. List can be comma delimited list of numbers or their parts starting from the left. Maximum 25 priviledged numbers can be defined. When messages are received, messages from priviledged numbers are handled first. First number in the list has highest priority.

09.03.2009    3.1.5beta4

  1. New global settings: international_prefixes = value(s)
    and: national_prefixes = value(s).
    Default values: not in use.
    Can be used for automated Type Of Address selection.
    See SMS file (Using Type Of Address selection) for details.

  2. New header: To_TOA: value. Can be used for Type Of Address setting. Possible values are unknown, international and national.
    See SMS file for details.

  3. If character set conversion incoming_utf8 is used, a header Alphabet: UTF-8 is written to the incoming message file.

  4. The script sendsms is enhanced: it now accepts multiple destination numbers. While used from the command line, last argument is taken as a text and all other arguments as numbers. In Debian based distribution scripts can be found from the /usr/share/doc/smstools/examples/scripts/ directory.

  5. A function tempnam is not used anymore. It is removed because of a compiler warning which said that "using is dangerous". However, it was used safely but compiler did not know it :). Now there are no any warnings while compiling smstools3. NOTE that after this change there must be /tmp directory available in the system.

  6. Baud rate 460800 is now supported. All other possible rates are supported too. Default baudrate is now 115200 which is good value for many old modems too.

  7. The smsd main process checks periodically if it's own pid is still in the pidfile. If it's not, it means that another smsd was started and it's illegal to run more than one daemon at once. To avoid problems, all other than latest smsd will stop with logfile and alarmhandler notice. Usually this kind of usage happens in testing, in productive environments init.d/sms3 script should always be used to start and stop the smsd.

    Update: 3.1.5beta7: this checking is not done in Cygwin, when os_cygwin = yes is defined. This is beacause Windows locks serial ports and another modem process will not start.

  8. New global setting: keep_messages = yes/no.
    Default value: no.
    This is for testing purposes. Smsd runs as usual but messages are not removeed from the modem. After all messages are read, smsd will stop. Use this with one modem only.

  9. New value for check_memory_method: 4 = CMGL list is used and messages are deleted after all messages are read. There is also some more new values, see the How to configure for details.

  10. Default cmgl_value is 4 which is good for most modems.

  11. New global and modem setting: internal_combine_binary = no. In this version all multipart messages including binary messages are combined internally. Setting internal_combine now defaults to yes. When a binary message is combined, UDH is removed from the message body and it's shown as a header line.

Bug fixes:

  1. While retrieving identification from device, if IMSI was not supported, CGSN was not tried because ERROR text was removed incorrectly.

  2. Phonecall reading freezed process if there was incorrect syntax (broken answer) from a modem.

16.02.2009    3.1.5beta3

  1. When a modem is first time initialized, support for reading of messages is checked. If it looks like reading is not supported, a notice is written to the log and alarmhandler is called. This is general issue with S60 based phones. See F.A.Q. for more details.

  2. Log file size more than 2GB is now supported.

  3. When sending or spooling is failed, "Failed: timestamp" header is printed to the message file.

Bug fixes:

  1. When a CMGL method (available from 3.1.5beta) was used to check incoming messages, there was a delay when modem did not have any messages.

03.02.2009    3.1.5beta2

  1. Modem setting send_delay = number has slightly enhanced:
    Value 0 now means that whole string is sent at once without any delays. This resolves slow communication with LAN based multiport servers, like Digi Portserver II. If, for some reason, "tcdrain" is still needed after sending, use value -1.

30.11.2008    3.1.5beta

  1. New setting for a modem: number = string.
    Default value: empty.
    SIM card's telephone number. If not empty, it is stored to the message files using header "Number:".

  2. New global setting: delaytime_mainprocess = number.
    Default value: not in use.
    If this value is not set, delaytime setting is used in the main process. With this setting outgoing messages can be checked more frequently than incoming messages.

  3. HDR_Modem header is printed to incoming messages too. There is still HDR_Subject header presented for backward compatibility.

  4. New command line argument: MAINPROCESS. Each modem process will change this with it's own name. With long modem names underscores like MAINPROCESS___ can be used.

  5. Failure with network registration will stop the modem process only if SIM was never registered successfully.

  6. Phonecalls are read even if incoming messages are not read.

  7. Configuration file: Only whole line comments are now allowed. For example:
    # This comment line is valid.
    devices = GSM1 # This kind of comment is invalid.

  8. Queue directory definitions are checked. If there is similar typed definition for more than one queue, it's reported and smsd does not start.

  9. New setting for a modem: check_memory_method = number
    Default value: 1.
    Defines how incoming messages are checked:
     0 CPMS is not supported. Default values are used for used_memory and max_memory.
     1 CPMS is supported and must work. In case of failure incoming messages are not read.
     2 CMGD is used to check messages. Some devices does not support this.
     3 CMGL is used to check messages. Some devices are incompatible with this.

  10. New setting for a modem: cmgl_value = string
    Default value: empty.
    If check_memory_method = 3 is used, correct value for AT+CMGL= command must be defined. This value depends on the modem.

Bug fixes:

  1. Filename preview for sent and failed files was generated using GMS alphabet version of a message text. Because of this, skandinavian characters were not recognized and underscore was used. This is fixed.

  2. When SMSC: was set in the message file, this caused an infinite loop.


11.08.2008    3.1.3

  1. New setting for a modem: voicecall_vts_list = yes/no.
    Default value: no.
    Defines how VTS command is used to create DTMF tones: yes = AT+VTS=1,2,3,4,5 (list is used), no = each tone is created with single command like AT+VTS="1";+VTS="2" etc.

Security fix:

  1. Tone definition string used to make a voicecall was not checked and it was possible to send user entered AT commands to the modem.


10.08.2008    3.1.2

Bug fixes:

  1. Signal handlers are now silent. Previously informative messages were written to the log but at least in some environments this caused process to hang when logging function was called twice. This fix has no effect to the run script like /etc/init.d/sms3.


06.08.2008    3.1.1

  1. New setting for a modem: messageids = number.
    Default value: 2.
    Defines how message id's are stored: 1 = first, 2 = last, 3 = all. When all id's are stored, numbers are delimited whith space and there is one space and dot in the end of string.

  2. Destination number can have minus characters, like 358-40-111 2222.

  3. If a modem is used for sending only, it is initialized when a device spooler is started.

  4. Filename preview is applied for sent and failed files too.

  5. While checking the network registration status, some modems include extra space character in the answer. All space characters are now removed before second field of answer is checked. The format like "+CREG: 000,001" (Motorola) is now accepted. All additional fields are remove before testing.

  6. New global setting: executable_check = yes/no.
    Default value: yes.
    This setting defines if all executables are checked during the startup check. Usually eventhanler, alarmhandler etc. are shell scripts or some other single files which can be executed and therefore checked simply. If using a settings like eventhandler = /usr/local/bin/php -f /usr/local/bin/smsd_eventhandler.php, the check will fail and smsd will not start unless executable_check = no is defined.

  7. If stats directory is defined, smsd will store and update message counter files in this directory.

  8. If a file which is failed to send is empty, it's deleted even if there is failed folder defined.

  9. While checking the network registration, result 3 (registration denied) will stop the modem process after two retries.

  10. PIN handling is slightly improved. If a modem needs PIN, but there is no PIN defined in the configuration file, the process will stop.

  11. Some modems do not include "OK" in the answer for CPMS query. In this case timeout time will be spent, but after this the answer is accepted if it includes at least 8 commas.

Bug fixes:

  1. While reading PDU string from file and directory was defined for pdu_from_file setting, file handle for directory was not closed after finding the PDU file. After a while this caused modem process to stop with error message "Too many open files".


11.05.2008    3.1

General:

  1. While running as an unpriviledged user, outgoing files which are not writable for smsd are re-created to fix the permissions.

  2. In the log, a process id is now included in the "started" message.

  3. Message sending time (how long it took) is logged. Some other changes and enhancements is also applied to the logging.

Outgoing message file:

  1. Binary message can now have UDH-DATA defined.

  2. New header: System_message: yes. With this setting message is sent as a system message. This kind of message has fixed values 0x40 for Protocol Identifier and 0xF4 for Digital Coding Scheme. A message cannot have User Data Header. Maximum length of a message is 140 bytes.

Incoming message file:

  1. New header: Flash: yes. This header exists if a message was received as a flash (immediate display). Note that usually phone devices do not save flash messages, they can be saved manually if necessary.

Configuration file (smsd.conf):

  1. New global setting: date_filename = number.
    Default value: 0.
    Defines if date is included to the filename of incoming message. With value 1 like 2007-09-02.GSM1.xxxxxx and with value 2 like GSM1.2007-09-02.xxxxxx.

  2. Baudrate 4800 is now supported.

  3. New global setting: log_single_lines = yes/no.
    Default value: yes.
    Linefeeds are removed from the modem response.

  4. New setting for a modem: check_network = yes/no.
    Default value: yes.
    Network checking can be disabled if it's known that a device cannot support it.

  5. New settings for a modem: logfile and loglevel.
    Default value: empty.
    Each modem can now have it's own logfile and loglevel setting. If logfile is not defined, global log is used.


15.08.2007    3.1beta7

After 3.1beta was released at march 2007, lot of changes are tested and implemented to the "stable" version of smsd. This new 3.1beta7 contains all changes and features included in the 3.0.10. Because the feature list of beta's was quite long, it's shortened and this new list contains only the changes and additions not included in the 3.0.10. Older beta versions are removed from the download area because there is no reason to use them.

Configuration file (smsd.conf):

  1. New global setting: saved = directory.
    Default value: empty.
    If defined, smsd will store concatenation storage's to this directory (otherwise incoming directory is used). At startup check existing concatenation storages are moved from incoming directory to saved directory. Zero sized files are ignored. If both directories has a storage file with data, fatal error is produced and the smsd does not start.

  2. New global setting: phonecalls = directory.
    Default value: empty.
    If defined and reporting of phonecalls is used, message files are store to this directory instead of incoming directory.

  3. New setting for a modem: phonecalls = yes/no.
    Default value: no.
    Report phonecalls. Currently only missed calls are reported.
    When a phonecall entry is read from the phone, eventhandler is executed with argument $1 = CALL. This event can be used to make some actions after an unanswered phonecall is received.

  4. New global setting: language_file = filename.
    Default value: empty.
    Message files can be written using localized headers. See the localizing for details.

  5. New setting for a modem: keep_open = yes/no.
    Default value: yes
    If this is changed to no, a modem is closed while it's not used.

  6. Regular_run for a modem: Like in the global part, it is possible to define an external script or program which is executed regularly within a given interval. A modem is available for script and command definitions as well as logging can be defined. See the How to configure for details.

  7. New global setting: datetime = format string.
    Default value: compatible with previous versions of smsd.

  8. Defining loglevels and alarmlevel, can use string value like LOG_NOTICE or "notice".

  9. New command line argument -a (ask). In the configuration file there can be multiple choices for values and selections can be done while the smsd is starting.

  10. Primary_memory and Secondary_memory settings can now have multiple parameters defined, like SM,SM,SM. Double-quotation marks are not necessary to use in the string.

  11. All yes/no values are checked, also the "no" value should be typed correctly. Previously all incorrect values were interpreted as "no". This might have an effect if you have errors in the current setup. Possible errors are reported at startup and the smsd does not start spooling.

  12. Device names are checked. Only alphanumeric characters, underline, minus-sign and dot are allowed.

  13. All errors are reported, not just the first one found.

  14. Numbers for the provider sorting can be given in the grouped format. (3.1beta4).

  15. New global setting report = directory. This can be used to define where status report files are stored. By default they are stored to the Incoming Folder. (3.1beta3).

  16. New global settings: keep_filename and store_original_filename to select file naming convention when files are moved between directories. (3.1beta).

  17. New global settings: regular_run = filename and regular_run_interval = number. It is possible to define an external script or program which is executed regularly within a given interval. See an usage sample on How to run/use. (3.1beta).

  18. Whitelist can specify a queue to be used with a list of numbers. See more details. (3.1beta).

  19. New global setting: admin_to, destination number for administrative messages. Messages are sent without using the filesystem. (3.1beta).

  20. New modem settings: message_limit, defines a maximum number of messages to be sent. message_count_clear defines a period to automatically clear the message counter. This value is number of minutes. (3.1beta).

  21. New global setting: filename_preview = number. Defines how many characters of message text is concatenated to the name of messsage file. Currently works with incoming message files. (3.1beta).

Outgoing message file:

  1. New header: Include: filename. Some parts of a message can be read from different file. If an included file contains only text part, it should begin with one empty line.

  2. New header: Macro: definition. Works like macros usually do. See the SMS file format for details.

  3. Binary message can be automatically splitted to the concatenated messages. With Autosplit value of 0 message is not sent if it does not fit in the single message. All other Autosplit values cause concatenated UDH part to be inserted to the message parts. If a message starts with UDH data (which is the default for binary messages), concatenation header is inserted to the existing user data header. If there is no UDH by the user, a new header is created.

  4. Unicode messages can now have part numbering as text (Autosplit: 2).

  5. Priority: HIGH accepted case insensitive, all which means "yes" is accepted too (including localized strings).

  6. Voicecall can now have TIME: number defined, where number is number of seconds to keep modem calling. After a time has reached, hang up is done. If a call is answered before a time is reached, normal sound playing is done. NOTE that this time counting starts after a command is given to the modem and there will be some delay before receiving device starts ringing. You should test this with your own handset to find a reasonable time which works fine in the network you are using.

  7. To: number is accepted in the grouped format, like 358 12 345 6789. The number can also contain * and # character(s). (3.1beta3).

  8. While smsd reads the message, all string length's are checked to prevent possible buffer overflows. (3.1beta).

Incoming message file:

  1. New header: Length: number. Length of text / data. With Unicode text number of Unicode characters. If non-Unicode text message is stored using UTF-8, number of bytes may differ.

General:

  1. While retrieving identification from device, CGSN is tried if IMSI is not supported.

  2. Dirty patch for Wavecom SR memory bug is included in the PDU handling. If PDU starts with "000000FF00", first 8 bytes are removed and zeros are catenated to the PDU until it's length is enough. Because of missing information, the Status Report cannot be really fixed. With this patch the SR still can be handled, but all result codes are assumed to be "ok, message delivered".

  3. Incoming PDU checking: content of a broken PDU is shown as much as possible.

  4. Very simple communication feature is included in this version. If you need to communicate with a device, but do not have any terminal program available, you can start the smsd with a communicate option -C devicename, for example smsd -C GSM1. This runs smsd in terminal mode which can be breaked with Ctrl-C. (3.1beta3).

  5. Smsd processes are listening SIGCONT signal to break an idle loops. When the mainspooler has moved a file to the outgoing folder, SIGCONT is sent to all modem processes. This causes a new message to be immediately handled and sent. This is especially important when a delaytime is long (for a modem and directory polling). (3.1beta2).

  6. Execution order of checkhandler has changed: When the mainspooler finds a message file, checkhandler is executed first before anything else is done. This allows checkhandler to make changes to the message file, for example queue selecting or "nickname to phonenumber" replacing. The smsd can also be notified with return code 2, if checkhandler has spooled a message by itself. (3.1beta).

  7. Execution order of eventhandler has changed: eventhandler is executed after a file is moved to it's final location. File will then have it's final directory and name, and the smsd does not do any prosessing with a file after an eventhanler was called. (3.1beta).

  8. When the modem is initialized and modem answers ERROR to some command, an errorsleeptime is spent and a command is tried once again. If a program is going to terminate while the modem is initialized, initializing is interrupted immediately. (3.1beta).

  9. Concatenated id start's from the random value between 1 ... 255. (3.1beta).

  10. Modem is blocked only if sending has failed because of a modem related reason. (3.1beta).

  11. "SMS sent" log information includes a message id and part information (while sending multipart messsages). (3.1beta).

  12. Status report log information includes a message id and status value. (3.1beta).

Bug fixes:

  1. If both received and sent PDU's were stored to the messsage files, some sent PDU's were lost if a message was received while the smsd was sending a multipart message. This is because the same buffer was used to store PDU's and receiving side cleaned it. Receiving and sending side now uses their own buffer to store PDU's.


18.07.2007    3.0.10

Configuration file (smsd.conf):

  1. New global setting: os_cygwin = yes/no. Default value is no. Defines if the smsd is running on Cygwin environment. This information is needed when some process creates outgoing files with incorrect permissions for smsd. If smsd has no write access to the file, it tries to get it using chmod 766.

Bug fixes:

  1. Command line argument -t had no effect. This terminal mode is useful when running smsd under Cygwin as a Windows service as it prevents one error message.

  2. In QNX, the OS libraries include already an unlock() function, causing a name conflict. The unlock() function of smsd is therefore renamed.


06.07.2007    3.0.9

General:

  1. Minor changes to the PDU checking.

  2. Replace: usage is ignored while sending multipart message.

  3. Character set conversion now converts incoming "currency symbol" (0x24 GSM) to € character (0xA4 ISO). This can easily be changed in top of charset.c if necessary (should not be any reason).
    The smsd does not send 0x24 while messages are written using ISO/UTF-8 character set.

  4. Documentation is updated. Sample configuration files are not, see the How to configure for details of new features.

NOTE for users running smsd with very heavy load:

Thank's for all users who have provided feedback, idea's, code and fixes.


28.06.2007...    3.0.9beta2

General:

  1. All incoming PDU's are checked comprehensively. If there is some illegal values in the content or illegal characters in the string or some characters are missing, a problem is reported and handling of a broken PDU is aborted. However, all possible junk cannot be detected because the PDU does not have any checksums.

  2. If an incoming PDU does not match to the mode setting defined in the smsd.conf file, alternative mode is tried before error is reported and handling is aborted. This means that the mode setting is now automatic for PDU's of incoming messages. Note that the outgoing side works like before and you have to use correct mode -setting in the configuration file.

  3. Number of devices is increased to 64.

  4. While reading a PDU from file, a first line starting with PDU: and space is taken if any exists.

  5. There is a simple script smstest.php included in the scripts directory. This script can be used to create sms files using a web browser. The script demonstrates a character set conversion made with PHP and can be used for testing purposes.

  6. Installation / uninstallation: path of executables can now be defined in the Makefile.

  7. Startup check: permission check for executable scripts is changed. Previously this check required mode 750 for scripts. Now owner and group settings are examined and permission is checked like a shell does.

  8. International Mobile Subscriber Identity (IMSI) is asked once from the modem when it's first time initialized. If a device supports this query, information is printed to each incoming message file as a new header: IMSI: 123456789.
    This header is also inserted to sent and failed files.

  9. Running as an unpriviledged user: if user is set but group is unset, that user's normal groups (e.g. from /etc/groups) are used. This means you can allow other users on the system access to write messages to the outgoing spool without giving them direct access to the serial port.

  10. When finding files from the spooler directories, the oldest file is selected.

  11. If a file in the spool directory cannot be handled because of file mode or ownership, error message is printed to the log and alarm handler is called. As soon as the problem is fixed, a file is processed normally. If a file is deleted (outside of smsd), the smsd forgets past problems with it and in the future a file with the same name is processed as usual.

Outgoing message file:

  1. New header: Replace: code. Code can be a number from 1 to 7. If a receiving device and SIM supports "Replace Short Message Type n" -feature, a previously received message with the same code is replaced with a new message. Only the messages sent from the same originating address can be replaced. If there is nothing to replace, a message is stored in the normal way.
    Note that the smsd does not use this value while sending concatenated (multipart) message. This is because some phones do not understand concatenated message as a single message and therefore a previously received part might become overwritten if a replace code is used.

  2. SMSC setting is allowed only if there is a smsc set in the config file.

Incoming message file:

  1. New header: From_TOA: string. Includes a Type Of Address definition with short explanation, like: "From_TOA: 91 international, ISDN/telephone".

  2. New header: Report: yes/no. Tells if a status report is going to be returned to the SME.

  3. New header: Replace: number. This header is included if a message has a Replace Short Message Type 1..7 (number) defined.

Configuration file (smsd.conf):

  1. Setting: pdu_from_file = filename / dirname/ is slightly enhanced. The original setting style will work when it points to the file which is read and then deleted. If this setting ends with a slash and a directory with that name exists, file(s) are read from this directory (and deleted after processing). All files found from the given directory are processed one by one, expect hidden files (name begins with a dot). When this setting points to the directory, no dot's are allowed in any position of a path. Be very careful with this setting while it will delete the content of a whole directory.

  2. New setting: log_charconv = yes/no. Default is no. With this setting a details of character set conversions (outgoing UTF-8 to ISO conversion and incoming GSM/ISO to UTF-8 conversion) is printed to the log. If smsd is compiled using DEBUGMSG definition, details are also printed to the console. Logging feature can be useful if you have some troubles with characters and like to know what exactly happens inside the smsd.

  3. New setting for a modem: modem_disabled = yes/no. Default is no. This is for testing purposes. Whole messaging system including eventhandlers etc. can be tested without any working modem existing. Sending of messages is simulated in the similar way than with sending_disabled setting. Incoming messages are taken only from the file, if pdu_from_file is defined. No any communication is made between smsd and modem, but a device setting should still exist because smsd wants to open and close a device. If in you testing environment you do not have a priviledges to the usual modem device, like /dev/ttyS0, you can use a definition like device = /tmp/modemfile. If this file exists and is writable for the process owner, it's enough for smsd.

  4. Startup check will now report if there is a queue defined but no provider numbers for it.

  5. New global setting: store_sent_pdu = value. Default is 1. Possible values are: 0 = no PDU's are stored, 1 = failed (to send) PDU's are stored, 2 = failed PDU's and PDU's of binary/Unicode messages are stored, 3 = all PDU's are stored.

  6. Validity period setting now accepts keywords typed mixed/upcase. Keyword can now be given without any numbers, like month means the same than 1 month. This same applies to the outgoing message files. When the smsd is started, a validity period setting is reported to the log if the setting used is less than maximum.

  7. Each provider can now have up to 64 numbers defined.

  8. Queue, provider and device/queues settings are checked. If there is too much definitions, an error message is displayed and the daemon does not start.

  9. stats_interval defaults to 3600 (1 hour).

  10. New setting: blockafter = number. Defines number of errors which will cause modem to be blocked. Default value is 3.

  11. New setting for a modem: outgoing = yes/no. Default is yes. If set to no, a modem does not handle any outgoing message(s).

Bug fixes:

  1. The smsd accepted definition of providers only if it was written as [provider] in the configuration file. Now singular/plural does not matter anymore, you can use [provider] or [providers]. This same applies to the queues: both definitions [queue] and [queues] are accepted.

  2. When alphanumeric senders had length more than 9 characters, garbage was appended to the sender's name.

  3. Setting of validity period did not accept clean numeric value, like 204 (for 12 weeks). An error message was produced and a default value (maximum possible time) was used. Validity setting also calculated some values incorrectly, like "3 months" produced a maximum time (63 weeks).

  4. SMSC setting in the message file did not work. However, usually this setting should not be used.


20.06.2007    3.0.9beta

Outgoing message file:

  1. When sending ISO coded message, all characters which cannot be transferred using the GSM character set are replaced with their alternatives. For example Ê becomes E, õ becomes o and so on. In the previous versions of smsd there was a replacement made only for few characters.

Configuration file (smsd.conf):

  1. New setting: outgoing_utf8 = yes/no. Default is yes. With this setting automatic UTF-8 to ISO conversion of outgoing files can be switched off, if necessary.

Bug fixes:

  1. Internal decoding of Unicode message was not done if a message contained only single part and no user data header including concatenation information. (Some devices include contatenation header even if there is only one part).


03.06.2007    3.0.8

General:

  1. Some modem(s), probably because of the firmware bug, gives an "OK" answer while trying to read a message from the empty memory space. This causes an additional five seconds delay to the process, because an "ERROR" answer is expected. New version of smsd accepts both messages without any additional delay.

  2. While a PIN code status is asked from the modem, some modems do not include "OK" result code in the response. This has caused an additional delay with those modems. Handling of response is changed to avoid delays. Also some informative message logging is added to the modem initialization.

  3. If a modem does not accept the PIN code, the modem process will terminate immediately. Previously it tried to use the same incorrect PIN again and again, and this caused SIM card to be locked and the PUK code was then required.

  4. When a message is sent successfully, possible previous errors with a modem are forgotten.

  5. Logging of character set conversation problems is changed from LOG_INFO to LOG_NOTICE. Log lines have now name of a modem.

Configuration file (smsd.conf):

  1. New setting: incoming_utf8 = yes/no. Incoming message files with ISO or GSM alphabet can be saved using UTF-8 character set. Default is no.

  2. If a modem needs some idle time after a PIN is entered, new setting pinsleeptime can be used to produce that. This value is seconds.

  3. New setting for a modem: pre_init = yes/no. This settting defaults to "yes" and causes "echo off" and "CMEE=1" commands to be sent to the modem before anything else is done.

Outgoing message file:

  1. When an alphabet is ISO or GSM, smsd can also read files stored using the UTF-8 character set.

  2. Autosplit works now with Unicode messages. If a message text is longer than 70 Unicode (16-bit) characters, multiple messages are created. Autosplit value 2 (text numbers) is not in use, with this setting a message is splitted as with setting 3, to multiple part with an UDH numbering.
    Please note that while creating a text part for message file, a coding UCS-2BE should be used (not UCS-2).

Bug fixes:

  1. Storage for startup error strings was not initialized properly.

  2. If a message file in the spooler is readable, but some program is still keeping it open to write, smsd tried to spool the file and failed because the file cannot be deleted. As a fix, the smsd first checks if the message file is writable.


18.05.2007    3.0.7

General:

  1. If smsd is compiled without a support for status monitor, command line option -s is not useable.

Bug fixes:

  1. Incorrectly written message file without an empty line between the header part and text part caused modem process to scratch if there was previously sent a message with less header lines or message text containing an empty line.


14.03.2007    3.0.6

General:

  1. New header Modem: devicename is automatically inserted to the SENT/FAILED message files.

  2. Message_id: n and Sent: timestamp headers are inserted to the message file before eventhandler is executed.


20.02.2007    3.0.5

General:

  1. Termination handling is changed. Previously the main process sent SIGTERM to the whole process group. With sh shell it was possible that incorrect processes were killed because they were running in the same process group. Also processes started from the eventhandler received SIGTERM and eventhandler was terminated before the job was completelly done. New version of smsd sends SIGTERM to the modem processes only. This allows smooth shutdown to the eventhandlers. If there is some eventhandlers running while the smsd gets a termination signal, an information is written to the logfile and main process will wait until all modem processes are terminated.

  2. If smsd is running on terminal (foreground), smsd shuts down when a terminal window is closed.

  3. Internal combine can now handle 16-bit message reference numbers too.

  4. Log line titles are now completely process based. For example the "SMS received" message tells now which one process (=modem) received that SMS.


22.01.2007    3.0.4

Bug fixes:

  1. Incoming PDU was not handled correctly when there was no SMSC information in the PDU string.
    Also note that in this case there will not be From_SMSC: field in the message file, which should be notified by the event handler.


11.01.2007    3.0.3

The major change is done to daemonizing:

When smsd is started to background (which is default), it forks itself before running processes are created. This solves some issues when the smsd is started from the sh shell.

It is no more necessary to use the & sign at the end of a starting command line.

There is a command line switch -t available to force smsd to run in terminal (foreground). If logging or debugging messages are printed to the terminal, smsd runs in foreground by default. There is also TERMINAL keyword available in the Makefile to force smsd to run in foreground.

The start-stop script sms3 is updated. The smsd now removes possible infofile and *.LOCK files at startup, it is no more necessary to take care of them in the script.
New script can be used with most operating systems.

See more details on How to run/use.

General:

  1. The smstools uses ISO character set in message files. If a locale is set to UTF-8, smsd does not handle outgoing message files correctly. There is sample scripts checkhandler-utf-8 and eventhandler-utf-8 in the scripts directory to demonstrate how character set conversation can be made using checkhandler and eventhandler.

  2. While smsd reads the configuration, all string length's are checked to prevent possible buffer overflows.

  3. More checks are done at startup. Fatal errors while reading the config are written to the log and starting the daemon is prevented. Executable permissions of eventhandler(s) are checked.

  4. Lockfile is detected by finding ".LOCK" from the end of filename instead of the whole string.

  5. A logfile setting can be made using the command line argument -l (ell). This overrides the config file setting.

  6. If received SMS is status report, log line starts with "SMS received (Report)".

  7. It's no more necessary to define user=root to get smsd running as a root.

NOTE for Windows users: If you are running smsd as a Windows service, you need to update settings of cygrunsrv. The smsd should be run in terminal mode and --neverexits option should not be used anymore. See more details on instructions for Windows.


30.11.2006    3.0.2

General:

  1. The smsd can be defined to run without root priviledges.

  2. System check is performed at startup to avoid some potential problems with permissions of directory structure and some other settings.

  3. If a config file cannot be read, the smsd does not stay running.

  4. Only the process id of a main process is written to the pid file.

  5. Some code cleaning is done to avoid compiler warnings on Solaris 10 (6/06 x86) with gcc.

  6. mypath setting is not used anymore.

Configuration file (smsd.conf):

  1. user = username and group = groupname settings to change priviledges for the smsd.

  2. infofile = filename and pidfile = filename settings to change file locations, needed when the smsd is not running as a root.


  3. These settings can be overridden by the command line arguments.
    See the How to configure for details and How to run/use for more details.

Bug fixes:

  1. Sample script mysmsd did not read status report correctly while storing it to the MySQL database.


14.11.2006    3.0.1

General:

  1. When syslog is used to logging, a modem name is presented in the log line.

  2. Outgoing PDU: Data Coding Scheme uses message class bits only when message is sent as an alert (flash).

  3. Some code has 'cleaned' to avoid compiler warning messages.

Bug fixes:

  1. Syslog call is now made using a format string.

  2. There was two compiler warnings (with gcc >= 4) because blacklist.c and whitelist.c missed prototypes of exit() function.

  3. Buffer used to store received PDU's was not initialized correctly. This caused a runtime error while concatenating multipart messages on latest Ubuntu releases.


02.11.2006    3.0

Bug fixes:

  1. Blacklist and Whitelist handling: fixed incorrect log message when the file was not readable. Also fixed the program termination in those cases.

  2. If write_to_modem failed, for example because the modem was not clear to send, put_command did not free the memory used by regexp.

  3. While reading a configuration file there was stack misuse which caused device_list become empty while running on ubuntu 6.10.


25.09.2006    3.0beta

This version is based to the SMS Server Tools version 2.2.8.

Some of new features were previously published as an enhancements to the 2.x version. Now these features are officially maintained in this new 3.x version of smstools.

New features:

Configuration file (smsd.conf):

  1. Received PDU's can be stored to the incoming message file. It's possible to select which kind of PDU's will be stored.

  2. Message validity time can be selected for default.

  3. Incoming Unicode message can be decoded internally.

  4. Incoming multipart message can be combined internally.

  5. Dual memory handler: it's possible to read messages from both memories (SIM and Mobile Equipment).

  6. Incoming PDU string can be read from the file. This is for testing purposes.

  7. Sending can be disabled. This is for testing purposes.


  8. See the How to configure for more details.

Outgoing message file:

  1. Priority can be set to high. This also works while the Provider Queue is used.

  2. Message validity time can be selected for the message.

  3. It's possible to make a voice call with DTMF tones.

  4. Binary messages can be presented as a Hex in the message file. There is a Wap Push demo using this feature.

  5. After a message is sent, Message_id is stored to the file if a status report was requested.


  6. See the SMS file format for more details.

General:

  1. A new sms3 script is available (in the scripts directory) as an alternative for the original sms script. This new script handles some special issues, including a smoother shutdown, multiple instance prevention and force-stop ability.

  2. If a modem reading reaches timeout, it will be reported to the log file.

  3. If for some reason the file moving fails, destination lock is removed.

  4. While creating a log file, file access permissions are limited to 640 (-rw-r-----).

  5. Provider definition can now have 's' for short numbers. For example: "FINLAND = 358, s". If this is not defined to any provider, outgoing message file must have a provider queue setting while sending to short number.

Bug fixes:

  1. In the outgoing message file the "UDH: yes/no" setting did not work correctly. However using this setting is not necessary because this value defaults to true when it's needed.

  2. Short number preceeding with 's' caused number become empty and sending failed.


smstools-3.1.15/doc/index.html000077500000000000000000000150111223712572200162320ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Manual of version 3.x

The SMS Server Tools 3 is a SMS Gateway software which can send and receive short messages through GSM modems and mobile phones.

You can send short messages by simply storing text files into a special spool directory. The program monitors this directory and sends new files automatically. It also stores received short messages into another directory as text files. Binary messages (including Unicode text) are also supported, for example ring tone messages. It's also possible to send a WAP Push message to the WAP / MMS capable mobile phone.

The program can be run as a SMS daemon which can be started automatically when the operating system starts. High availability can be ensured by using multiple GSM devices (currently up to 64, this limit is easily changeable).

The program can run other external programs or scripts after events like reception of a new message, successful sending and also when the program detects a problem. These programs can inspect the related text files and perform automatic actions, for example storing information into a database (for example MySQL or Microsoft SQL Server), sending an automatic reply, forwarding messages via eMail (SMS to eMail gateway), ... and whatever you like.

The SMS Server Tools runs on Microsoft Windows (with CygWin) and any Unix including Solaris, BSD, FreeBSD and GNU/Linux. This software needs a GSM modem (or mobile phone) with SMS command set according to the european specifications GSM 07.05 (=ETSI TS 300 585) and GSM 03.38 (=ETSI TS 100 900). AT command set is supported. Devices can be connected with serial port, infrared, USB or network modems using a socket.

SMS Server Tools has been available from 2000 (as 1.x, starting from 2005 as 2.x).

Current and actively developed version is 3.x.

This software is supported on the SMSTools3 Community. Use this forum to all support questions and feedback.

The book SMS Applications covers version 2.x and is still valid and usefull with version 3.x too, because the concept and code base is the same. Also the book is recommended reading, as it contains lot's of information about the operating systems, databases, scripting etc. etc.

The book is written, maintained and owned by Stefan Frings.

Basic information

Additional Information

Get SMS Server Tools

Support


Use the SMSTools3 Community to contact us, we do not answer support questions via letter, phone call, eMail or SMS.

smstools-3.1.15/doc/license.html000077500000000000000000000036341223712572200165550ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

License

The SMS Server Tools 3 are under the General Public License.

/*
SMS Server Tools 3
Copyright (C) 2006- Keijo Kasvi
http://smstools3.kekekasvi.com/
Support: http://smstools3.kekekasvi.com/index.php?p=support

Based on SMS Server Tools 2, http://www.meinemullemaus.de/
SMS Server Tools version 2 and below are Copyright (C) Stefan Frings.

This program is free software unless you got it under another license directly
from the author. You can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation.
Either version 2 of the License, or (at your option) any later version.
*/

General Public License, English


smstools-3.1.15/doc/localizing.html000077500000000000000000000206571223712572200172720ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Localizing

Starting from the version 3.1, message files can be written using localized headers. While localization is in use, english words can still be used. Localization settings can also be used to drop some unwanted headers from the incoming files, like "From_TOA".

A file containing definitions of localization can be defined in the global section of configuration file (smsd.conf) using the setting: language_file = /path_to_smstools3/examples/language-UTF-8.fi.

Character set used in the language file should match to the locale setting used by the system which creates outgoing message files. This is very important when UTF-8 is used as a locale, because byte sequences of special characters should match. Because of this, there is two versions of finnish translation available in the examples directory: language-ISO-8859-15.fi and language-UTF-8.fi.

Headers written using Unicode are not currently supported. If you are interested to use Unicode headers, please contact the author.

Localization file can contain empty lines and comment lines starting with # character. Keywords are case-insensitive and everything is checked by the smsd. Keyword and value is separated using an equal sign. If there are syntactical failures, an error message is produced and the smsd does not start.

First, some general settings are available:
incoming yes/no. Default: yes. Defines if incoming message files are written using localized headers.
datetime format string. Can be used to localize timestamp format.
For example: datetime = %d.%m.%Y %H:%M:%S produces 14.08.2007 12:34:56.
See man strftime for more details.
yes_word Defines a word to be used when smsd writes "yes" word to the file, like "kyllä".
no_word Used like yes_word.
yes_chars Define one or more character sequences (or byte sequences if using UTF-8) which means "yes" as the input value.
Sequence should be inside apostrophes and sequences are delimited using comma.
For example in finnish, "yes" can be defined using: yes_chars = 'K','k'.
no_chars Used like yes_chars.

While defining alternatives for message file headers, ensure that there is colon included at the end of word. Some words in the following list are used in the content part, and there colon should not be used. This list includes a definition how and where a header is used by the smsd.

Alternative content for a header can be given as "-" (minus sign only) which means that there is no translation for this header and smsd does not print this header to the files. If a header has a translation to be used as an input, and it is wanted that this header is not printed to the files, an alternative text can start with minus sign. Some headers cannot be omitted, those cases are market to the list.

To: Msg file input.
From: Msg file input: informative. Incoming message: senders address.
Cannot be omitted.
Flash: Msg file input.
Provider: Msg file input.
Queue: Msg file input.
Binary: Msg file input (sets alphabet to 1 or 0).
Report: Msg file input. Incoming message: report was asked yes/no.
Autosplit: Msg file input.
Validity: Msg file input.
Note that if word values are used, they should be written using english.
Voicecall: Msg file input.
Replace: Msg file input. Incoming message: exists with code if replace code was defined.
Alphabet: Msg file input. Incoming message.
Include: Msg file input.
Macro: Msg file input.
Hex: Msg file input.
SMSC: Msg file input.
Priority: Msg file input.
With this header a value HIGH can be used and also localized "yes" meaning will work (defined using yes_chars).
Sent: Outgoing timestamp. Incoming: senders date & time (from PDU).
Modem: Sent message, device name (=modemname).
From_TOA: Incoming message: senders Type Of Address.
From_SMSC: Incoming message: senders SMSC.
Name: Incoming message: name from the modem response (???).
Received: Incoming message timestamp.
Cannot be omitted.
Subject: Incoming message, modemname.
UDH-Type: Incoming message, type(s) of content of UDH if present.
Length: Incoming message, text/data length.
Fail_reason: Failed outgoing message (currently only spooling uses this).
IMSI: Incoming / Sent (or failed) message, identification code if supported.
Message_id: Sent (successfully) message.
Original_filename: Stored when moving file from outgoing directory and unique filenames are used in the spooler.
Call_type: Incoming message from phonebook.
missed Incoming call type. Note: this is a value.
Cannot be omitted.
CALL MISSED Incoming call, written to the message body.
Cannot be omitted.
Result: Voicecall. Result string from a modem.
Number: SIM card's telephone number (if defined in the config file).


smstools-3.1.15/doc/run.html000077500000000000000000000235621223712572200157410ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

NOTE for users running smsd with very heavy load

Current version of smsd moves files between spooler directories using the original file name. If outgoing files are created using a fixed filename (which is not recommended) and lot of files using the same name are created within a short period, it's possible that previous file and it's .LOCK file are still existing in the spooler directory. In this case a new file cannot be moved to the spool. Previously the smsd stopped with a fatal error in this case. Now an alarmhandler is called and after it has returned a file moving is retried. If a file still cannot be moved, the smsd will stop with a fatal error.

The alarmhandler can be used to help with a file moving conflict. The script can wait until a spooler can be used, or it can wait some fixed time like 5 seconds. It can also produce some notices to the administration, if necessary.

In some cases this kind of conflict is a result of a previously happened error in the system which creates outgoing files to send. In this case it's better to let smsd stop, instead of sending couple of thousand messages to somewhere...

In the conflict case the alarmhandler will get following arguments (as an example):

Running (as a root)

IMPORTANT: The smsd inherits it's priviledges from the user who started the daemon. If starting is done by the root or system startup, priviledges of root are inherited. In this case the smsd can switch to the unpriviledged user account, if it is defined in config file or command line (in the sms3 script). If the smsd is started by the unpriviledged user, account switching is not available.

Easiest way to run the smsd is running it as a root:

Method 1 (recommended for the normal use):

Method 2:

Method 3:

Running smsd as an unpriviledged user

In some environments it is more suitable to run smsd with priviledges of a standard user. There are two ways to do this:

In both cases you must ensure that infofile and pidfile are writable by the unpriviledged user. Location and name of those files can be defined in the config file, if sms3 script is not used. Most recommended way is using the sms3 script, and change settings in this script.

In the sms3 script there are settings USER="" and GROUP="".
Usual settings for these are the same group and user who owns the modem device, for example USER="uucp", GROUP="dialer" or USER="smsd", GROUP="dialout". Selected user must have write permissions to the device(s).

Selected user must also have write permissions to the spool directories. For example those directories can be owned by this user. Other users who are permitted to send messages should have write permissions to the outgoing directory.

infofile and pidfile should be moved to the place which is writable by the selected user, for example:
PIDFILE="/var/run/smsd/smsd.pid" and
INFOFILE="/var/run/smsd/smsd.working"

Usually the default logfile is not writable by the unpriviledged user, this should be defined in the sms3 script too:
LOGFILE="/var/log/smsd/smsd.log"

When the smsd is trying to start, all permissions and availability of directories are checked. If there are any problems, they are reported and smsd shuts down. This prevents problems in the future, for example when smsd was run for couple of days and failed directory is first time needed. If a directory is not accessible, smsd stops.

Sending a message

Run the command sendsms 491721234567 'Hello, how are you' to send a message or put an SMS file into the Outgoing Folder /var/spool/sms/outgoing.

To read a received message, take a look into the Incoming Folder /var/spool/sms/incoming.

Using a Regular Run feature (version >= 3.1)

After version >= 3.1 it is possible to define an external regular_run script or program in the configuration file. This program or script is executed at a given interval while the smsd is running. Because the smsd controls when the script is executed, there is no need to start/stop procedures like using traditional crontab. In the future versions there will also be some return value handling. Currently return values other than zero are reported to the log file.

This example is about verifying the delivery of a sent message. If it's not delivered fast enough, a same message is sent to the alternate phone number.

When the first message is sent:

When a status report is received, it's stored to the incoming folder as described in the SMS file format. An eventhandler can find the relevant sent message and add the Received: timestamp header to the message file.

If a destination phone is switched off, or it's out of GSM network, the status report is not received, of course because the message is not delivered.

We have to check if there is a sent message which had status report requested (Message_id field is present) and there is also an alternate destination number, but not a Received timestamp. If this kind of message is found, we have to check how long we have been waiting a delivery. For example after about 30 minutes of waiting, we could do the following: It's not necessary to remove old Message_id and Sent headers. The smsd will remove them automatically when the message is moved to the sent folder. If a status report is requested for the new message, the smsd will place a new Message_id header to the message file.

See smstools3/scripts/regular_run script for more details. This script is compatible with ASCII messages with character set ISO or GSM. This kind of functionality can also be made with MySQL, but this example is file based.


smstools-3.1.15/doc/slideshow/000077500000000000000000000000001223712572200162355ustar00rootroot00000000000000smstools-3.1.15/doc/slideshow/blacklist.gif000077500000000000000000000006351223712572200207030ustar00rootroot00000000000000GIF89a@@€ÿÿÿ,@@þ„©Ëí£œ´Ú‹³fÁ„~€ßç^h’À(¦ê©b™tózÛð™×f¸ ‡Äá/xŽJ©â,ù‹"¤4 òšdÊ^Í©×ÚƒIYGÞ®»;ƒÕÈtYL5.á*îÔUÛè÷ü¾ÿ˜ÑòÅRu÷‚!——V—V¡õeåt)9ñÄæfIgA†ƒvzª§c”iš¹šzøJÚeH÷&ˆ›«»ËÛÛG6Ç…Çt8ŒòƨÔÊÙv%†½@Ù‰ ôT§èl£- Zh¶½6~·4÷ þ,N…^t݉ ݬZ¿ž‡cé²ÅâoLÌ—À ”ÌV!bµ´µHèã™Cô¼@©ôŽÚ·}&ö0¢£41Fiöò‘ëXM ·RÈú…ÁRK]Œuhà•£e‘‡X5»ƒïØÁ¡D‹ýÓNžÉ¥>IsÏ£i¼”éÈŒ»P7Ç ËV¦âHuU#y•D&mµVNÕbåÊŠf¸r:ÅjòÄ4QÀ¦B™ý 8°àÁ„ >¨;smstools-3.1.15/doc/slideshow/eventhandler.gif000077500000000000000000000006351223712572200214120ustar00rootroot00000000000000GIF87a@@€ÿÿÿ,@@þŒ©ËíBŒŽ.û l3o†J&ÀÙ¡ÇitŸ*•_ŒRªMnî®·ï°`l>^ëö&‹°àЏ„ö¦¼j±â„¦|Wê‘ ³@ã2 çI«×ì¶û Ëçt¬}×Gõü{r¦G#vFfØ4ˆ¨gåtEÙŧÕ#é¨UxÙTµ„Ùµåu¹" ™¤”ªy9„tt·ÇõG;{'x»ËÛ›!Ä'WÆ!ìˆgúhbg˜\³ˆ–,-S켜u(5£z­1C [*ý]Rmê•Îm;ÎÁŽãÙ¬ l\+òÍŒ+LÌ&;ï+ Ààìí1çíX;7E$R zf2Ò—ƒÜ!}u L5Ô¶­’FP.¥™C¤‘Fâ°äƒ÷âF‰``墓qWC— úü ´§1OÃ0íd8Í“'ùÖ¢y#Þ8F%‘‘šò‹ÒhþZae‡mœM³JÚrÔDÑ*"ÕÎÔÚu,дƒÚ½«“.Þ¦{õûWÞ»ƒ |¸;smstools-3.1.15/doc/slideshow/logfile.gif000077500000000000000000000006711223712572200203540ustar00rootroot00000000000000GIF89a?:ÂÿÿÿÆÿÿÿÿÿÿÿÿÿÿÿÿÿ!ù ,?:þºÜþ0ʤä•ëýÝ´qŠå•fÉŒŸºv^ÅŽ§–62ÚÚî}ã¡ ×Ûil1ku2IǨHùüANi4Úä‚ËwøÒ·Æ´zÍneËi+̽ҠnÈLéõáÉœ;smstools-3.1.15/doc/slideshow/modem.gif000077500000000000000000000004561223712572200200350ustar00rootroot00000000000000GIF89a@/³Æÿ„„„ÿ)ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,@/ã°ÈI«½8ëÍ»ÿ`(Ždižhª®lëT³L߯ÛüTû¸×®'ùƒG¡ÅX`:‘¬!Ø„2WÒät«íF—Ö°’ú$ç`æ4ו=§Úî<^šÓGö{(¯ÿðû€‚ƒ…†ˆ‰‹ŒŽh’ ‘–Œ˜†œ”œž‡  ‰¤§¨©ª«¬­¡±²³´µ¶·¸¹´;°±¿µÀÁ²Ã³ÆºË¹¼ÅÀÂÑпÇÓºÌ͸Ͼ×ÔÄÝÕßÉÊÌÎ:ã´Èáàë¹Ùç0äôõö±Ü÷úû³¼®ÿ¦EÐE;smstools-3.1.15/doc/slideshow/move_l.gif000077500000000000000000000016131223712572200202110ustar00rootroot00000000000000GIF89a@‘ÿÿÿÿ!ÿ NETSCAPE2.0!ù ,@ Œ©ËíZdsUuSFlãQ_Zc‰EÞ·š,ÊÁG›ª2IÇ÷ºñußÊ|:/sÔg>áñò,¢Ì%®‰zV´Õ%w˜c;_-™jÓA¶›k¶Áß×h¼ž¼ëéxZº_ò'8·EØ•óu¨âÆ(õØy¥$„wid•)e©ù :Zôyé‚Z“zŠ#âjòš;B"aKqkñ°ËÛë[!ù ,@šDŽh™Ëç^d³©kÆèþyœažvš$µ6­²V§œ²/xÏRÝÙï<ú‚:/D aÅLǃƘͤÃJ=&³Siͺ;—b/I6¤ÍhñúÝÆÂåî:=Œe‹å~>¶µ¸dÆuØÃe…èµØ‚H%5Tùh™¸¸“3 µIÓe£‚rƒ3zZ¢2ºêšj‘)!û1KP!ù ,@’Œo€«{ít“EV_N[vðuÚ’`T¢'µ^eö²í§Ø|ãoµç|Œè©€¢Ç¸›$ƒÂD‘˜„Di¶#tɱ£ îs;ÕÚ¸1²8eN{Éåµ{ª†£ÃÞs—ŽÇޱgj+Ü÷õ'Õ$Æ„TÈgøp¨c¤åÙ(b%SƒS™cy12ä)óyéáRzª2ÊèÂ)âÚºP!ù ,@•Œ§–‹þ`hÊiȈ3IÛõy›9:g•.ëur±úju×âr™c·ß‹e„®™ ˜Z”6#/IÔ1Ÿ-%Å*­N‘SkÃKM¹bïÊL¡×`óŒ…3åètø‹µëÝÚ(9{µ•ÄÕt³U˜e…÷ã÷TäèyÄCÓC)ó“©ÑÄ‚ :JÂ"‚ùZÊó*[Q!ù ,@”Œ§–{íPdSVÈjOލaœ’ŠŽ,’l¬|Òî,ß5w¶ß[M*ž†7D“F!óGTFEHHÕ¼N›ÕFwëÕ~a]O™¬<«¿eÛúúN³Û ±]Œ—Ò­Rt¶¯åGÕ¶DÒ'X˜qHXx4äçXE„¦cy¹tÓâ˜rBæê²òñ SŠšòÑQajÒ;Q;smstools-3.1.15/doc/slideshow/move_r.gif000077500000000000000000000016141223712572200202200ustar00rootroot00000000000000GIF89a@‘ÿÿÿÿ!ÿ NETSCAPE2.0!ù ,@¡Œ©ËíZdsUuSFlãQ_Zc‰MÞ·š,ÊÁG›Fëv×8Ë$? Ý2CM®'+"‰B%Èù;>¥QàðÒºv®A§¶+ýN«G­Jlž*ÓQrÎlKÃÝYyó;ÏÛñ{ú™ë§V†FçHÆ#–8†è¶„õHy—DEf”¹¤Y¥ª¹ãƒÙ¶9V㢚šêó$k›"aKqk‘ûÀÛëÛ[!ù ,@™Œa©¨ÛþN”“`®yõ´Eácaß5z§)¦ K¡°ùÑ$«ájýꇽëЄÀž«4 f†Ë¤1ńȊL¤sª«>G¡*Ãû‹bqàqwãE¤ÍhðúÝÖÂåî:=ì,ëµl<ÐJ%f¶˜%XØT4¦Ä¸%uå—ãƒ%y¤xat339¥YÃiÕ²##jJò¡ڲzS2ù:+{rP!ù ,@—Œ §¸šÍ`xJd]>`cÙÞÒye6§š’­øbñ´js¹á]Ë1žºz¼—xr”>#³ØŠ"—L$ÅJÕ5S+ëûåb¡]¬Î›@“Oê6š v›ãôwø;¯ñÓ=-[3¶(UÕ—¤µeÓÓ§Hó“Ø4IôDÉrCT”™³iƒb2ÃZº3ú‡"²ªº3q1ò K Q!ù ,@”Œ€«{íR4ÐÕ™îÓ”ç x!¶q¡9Z)³"§«¶œÑ§vÇ|_=ûrå„ÁRªXCö*K¢òg “Åå®9›Z£[jÐÚg‡bï¬@›¥b5:çnËÏsõzüí–÷oîjæ'ˆ7ååÔ‚…wÈôÃSHäˆyH¡s‰é¤19ÓISŠâ±"‚zªJ"‚Q ûêP!ù ,@‘Œ€«›þ`hN ,ÊX'op%2åv~`¶¦¡È:¬7˱éÖ¸¢«é¬ðfDN„¾ÓU<2—Si³dM"¥L®öæ¥v³\aYlöž×åÛú~ÆÃt²ý‰·¯$|¾_å·w¥ä—WèÖ4Ô˜èÈG£”d“3)VÉÓ²9ÂYÙÒ!:2jS*q ªêjP;smstools-3.1.15/doc/slideshow/page1.html000077500000000000000000000100001223712572200201120ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Overview

  • This slideshow was made to give you an overview about the SMS Server Tools.

  • SMS Server Tools has been available from 2000 (as 1.x, starting from 2005 as 2.x).

    • SMS Server Tools is originally created by Mr. Stefan Frings.

    • The book SMS Applications is about version 2.x, however basic information applies to version 3.x too.

    • Developing of version 2.x is freezed at 2006. Bugs are fixed if any found.

  • Current and actively developed version is 3.x.

    • SMS Server Tools 3 is maintained by Mr. Keijo "Keke" Kasvi.

    • This project has moved to the new author at 2006.

    • After this enhanced version was created, more than 250 changes and enhancements are done (situation in 2009).

    • Developing of version 3.x continues...



smstools-3.1.15/doc/slideshow/page2.html000077500000000000000000000070371223712572200201330ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Core functionality

  • The picture below is an overview about the core parts of SMS Server Tools.

  • SMSD is the main program which starts a sub process for each modem and controls those processes.

  • This example uses 5 modems and it uses the sorting-by-provider function to save money. It's explained on the next pages.

  • Click on picture to see it in full size (use Back button to return).



smstools-3.1.15/doc/slideshow/page2.jpg000077500000000000000000001175611223712572200177530ustar00rootroot00000000000000ÿØÿàJFIFHHÿÛC  !"$"$ÿÛCÿÀœ"ÿÄÿÄX  !"12AQ#aq‘4BR’±Ñ$35STs²ÒVWb‚”•¢7Cru¡´ÁÔ%&6t“ÓáUce„ðÿÄÿÄA!1AQaq‘±ð"2¡ÁÑ3BRr²á4S’ñ#b¢Ò$C‚ÂÿÚ ?û.”¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JPˆÚêÀ§ÔÜ×—kl¥Õ5"~ÖYy-º––R²¬pµ `àÃæ¶­ê ž#·{¶/Â÷ ²{[9^î|»~9ôøÖ¦…Óð[lÅ…™B`–ì¶ã¡/< éwjÔHÜ~5„ÏMí ðáÙÓßLeFK ZXc»±ƒµ#r0ó€îÉPÆO­w™m¬3½›FÙzËM7&êaŸÞ&ßlJDGoæä,¬!¥I@ZŠ3¼œ¸9ùcš÷µÜm÷XH›ktUä!èÎ¥ÆÕƒƒ…$y¨c½1¶H˜ô©—YÒ\”–i…%ï¤qÄùKe# u~ƒåòÍHô}½7h6æ§Ìš’ás|•SÔ€JxôdŸ²¥9_4k© *>¤®ÍÍ)JÌ®)JP R””¥¥)@)JP R””¥¥)@)JP R””¨¥ÃSM‰¬›µª4dÛK0d(¸£Ýpd¤¤¡µd¶RU¼`ò†ÒÚg r›j$®•TYº©tº@·,±ÇíK”ûK|¤%òàC-¤‘ÈPX÷ˆRåU$WLoDÈ»†3˜œÌ7ßò©Ç›o*d¤:µÀ Œr0T'R,ß<h4šßbgJ¯fu Ú$ÆfìÂî4Û‹íCr:ÉqO¡´¥ ¢¦‚|á nÎpxÛY5å¶ë~fMºéD†”´*[hi;’T c+ÊÔ6(åIÇ;°h§c,-X«Û"YJR³+ŠR””¥¥)@)JP R””¥¥)@)JP Á¿ÿ$½ö§û³«ÿü’÷ÚŸï ¡¥?‘­ùeà͸µj#ÛÍ_}6š¾úì)_0ÕG¹vuØ>jûé°|Õ÷×jSU ³®ÁóWßMƒæ¯¾»Rš¨]vš¾úl5}õÚ”ÕBìë°|Õ÷Ó`ù«ï®Ô¦ªg]ƒæ¯¾›Í_}v¥5P»:ì5}ôØ>jûëµ)ª…Ù×`ù«ï¦ÁóWß]©MT.λÍ_}6š¾úíJj¡vuØ>jûé°|Õ÷×jSU ³®ÁóWßMƒæ¯¾»Rš¨]vš¾úái•z|ë½p¡”‘Q(«0™(ƒø‹Õ§õWµTz˨÷í?­tݲ›&‰’mîÆ æ%(Œ¤¾É8å%ÅŽNÓ…‚G¼˜Š:Ç«"i4Ë0q“lÔȶº÷kkw&TÛç¶ G¨>QÆPH9 ý2ž>•8(J÷K‡RóåhrCHâ©Æµ5K5ë-îQñ_ÂVú*•óýã«Zª ]vÃpŸYdF—÷ØILt9!b?´ã8R’0I /Ê Ht_Rï÷Nª³a—bšÜ;¥±™©Œ´¤¹l;<ÅjËj Uƒ•§€NÚÛ!FRQÎï«­¯cW’:B•Özº±MûKbŒeúfŸ¹­¶¾A¶kˆn8Ìfî IJÁ„ôU2ãò"8ñZœm.å [m"3yXÏàSLÁÕÓ)©²#]×5ˆ{"ªXAZb‚¤’•:T•'Ë„c)'G`…Õö˜µß-wI5"¥kZ5,x‰îR¬6¸Ž¹÷±’pÀ½CVÛ/)ÓvÍS©îS$x7[\‹›-:’¶§©m°œÅlä´£ïr3Åã—;AƒÕ4ß%.ô.2#±ô°ˆ%ÖÂ# X퀧 %x'Þ;p2S°šž¯±—ÄÙO=%É~5˜ìÄ> –„±á·#͹’¥áYÚž0¬oÓÚý‹|ë–¡Ô²ÛáL2‹ìih” ðâW²#j@‡'qÁ¬)öž¥\åN™a™©$B¦0ÐkT3( Éu—":¯FÁÉYÉ'ÓÒ€¹ô®JÓ–å^7{DÆoÅoBR®æÑ»!¤ú„’3èq[¥õ]¢ýcƒz·@Õš®ç=Ò¨oɺ%·¹3TÂÛ Ca° P0¥6¢ ‰ç€57=Ô˜ZkQÝnÚƒWÚSm´I™Æõia×›ARP¤h!'¡_ |s@_ÔªcWXõÄKqoJMԮÊê(oS"2R®Ëng飼¥Ü<îÇ©®ìYïÖYQÑxÔÚªSòtìùïE“wK‰Šû Œã µ¼}2³¸àp9È%*³i.¦ÊºEb}çWÄ·8¬95½UÅ¡8WkÁ “ǸϩÅw“oÕ2´íº=–ù©¦]=§sŒã¾ÛÖûQ¥:ÊJ”¶œl©I;[#Œ ï¥Rôö¶lné¨u®„ânöø©†oìÊjC/Êe••)¸Í-$ŒP~½Ô·«®”²êÆ­jº\ÛÎ";ÎÈï*þÍ/÷Is9@Zú«Œœ$á9¨EÉî,apÓÅVm“¶y•*бuwS\¥hH°ÊqµI…:"NSe¯á,+9 Nå» ,t,iì½gÕÏh%ÜZäLŸøÕ¶VÆR[º¡m¸v£r^O ÉIä(¢©¡×ÝØþgL¹¥èí·´¶Þqñƒ\3[¯o£kSt›ñ÷Çë5IOêöªh×M¢©—§nû£JSHS=“07ážûÛI ’@<‚ÆÜµÜd^-P.’­rmOÊŠ—‡'ÆIÏ”ÿùÁÆ2Èç*±4ëà-ÒOÅx¢–/@bôdUZÖ³vɧž¬eÜÔ—ÎÙ_ßhúþúm_ß]©_8Õ•λG×÷ÓhúþúíJjÇ€¹é9Ÿ_Sñún«M ñæ~Óúnké‰I`ê~wúbSÄûHR”®È®)]y¦Üm·Bé)m*PdHâp ûy§Ûî0êFJw!@Œ‚AAh¡6;Òº¡Ä-KJ•«ä¤à‘ÁûEv¡•Ô-j@ZJÒRAägÓõí@+^ýŽÌýÝ»»Ö¸n\"J™Ipc8ç׌œ|²qëQ}ae³_z§!_-0.‘“eº:–fFCÈ @A*g #?Yªþ$ý‹ 3.ß¹Õæç®3O® 9<­ ¸P…+¶TP—JRNIÛ¹k’¤ÖÆ\ÒÚm--”Ø­Á·í­8Z6%½¤c‘±NÁ#åY1,¶˜–ôÛã[¢µ.¥àÒ[=Ĭ,/œ³ë*¨ÖLè[ žÓqÑsÓ9_N„Ø&;ÝφšqIÁ)I$ð¢7¾\·éVçACÒŒE“! -OÂm·›S“ÃxAµYm t€¿.0}P¥Å‘“©7µ–­ÆÇf¹<·n¸’–ã]•—š Ü7”çÔy×úGç^0tΞƒ1‰ì–øòc ¡—[a!Hvpqœù×ϯ_3šzCºU›!¸'¡)rQ.»%±.ÈmåCDŽÙO†,6T>%8 Vä#ÖÄ7w¼3no v(}ÉHo|È  K‹Ü³µ…%+KI (QH+ÞØYR9Yrvµò/*T7DÙìö=e© Ù-0-qKPÜ,ÃŽ†PVRà*)H8gê2©0¥()JJÆÕöw_l8dFŒùP2CE¸ò6‚NÅž=T3Àœšõ‘«tÄ{„ÈoÖèïCiÈÉBiZÔØÉ'Ü‚ø3ê+eÄÙÌÔÙªÍÝ+NuFž$¥«Ýµõ€Ê”†å¶T”:¤% #wºw§㑌ägªuv”TTKN§²î=ØC¢{[æØì`ƒ^EN²â9©ðfê•£¼j‹m®k±ä71iŽÚ]–ûLµ Îâ‡ à“Œà œk.]úÇ …?.ónŽÒ ‚–ì¤%))_mY$ñ…ùOÉ\zÒèŽjye´ØÒ°½ÙYeO;w·¶ÒŠ–©( HHDœü’Oýa󬨒cÌŠÔ¨µ";© mÖ–…¤úG}t¹-+´zÒ”©1¥(`ßÿ’^ûSýáYÕƒþI{íO÷…PÒŸÈÖü²ðfÜ?ÚǵJ WÌOlR” ¥()JJR€R” ¥()JJR€R” ¥(p¿pý•Ípç¸~ʉld­¦ù‹t)Û®ÆC“"ÇSl:®Kip#~>݉ç׬ה}9b Û ‹\fã[ñÚJ|­;…à|Uô‹999Q>¼Ö|ÄXþ­?ª½«êØxEÒ‹¶åàXбõT»__Õ÷¾,Õ/NX—LZãò¦ ²ÛÃχæõþqÜ”úü ÅeE¶À‹p™p¦åÍ(2^ ó»±;P ùè=OÌÖ]+r„V㈫$Ó“³ë}_Eܸ(Zú=~×:gOê iÓâ2­ˆ` Ž:܇ ÂPF?¶¶¦·k}µ==tÙý£-ȪB·<8ÜÕÕc$£w¢äÕ££‘`Bf. kø±Ù@CmG»%¤$ðJ[|Î$“êkººANwW¯5Òß6à«›eä”%Ô§·€îŒƒ¼äVF’¨µt7Qé[´ E9ZkÃ@‡œx¸FB| ¶r~b²5G/Úâû?P[NŸ×p¸0ÇnDùœ%cûjÒ=%A)îu¨n„­+Øíå+A)PPÊTÙ  ‚¡W*é3eלo¨zþ?yçZ#ÝPË{ÜYZÈB NT¥9&€­Ž¹Ø:g©44£k3æ"ÐÒÊ–Xû“¨FâR€O<}õÕ=ÔÖm5t¼ÉV—ì@ˆä·{N¼VRÚJÔ[8IøÕæ¾’°ä)Q׺áÔJ\uº·g´·²ÃÖ¶º¦ŠÐ¿6 2N}MyOèìyð¤Â™ÔN¢?KJeæ—{ÊV… ('¡ŠÕ®—Þ:…®n3-J²„A},¸'-Ĩ•Ga@¨PÆ+@è+—N®7·oe)s4õÙôx-I A†;’Ÿ˜«áÓN¸?9Þ¡ë¶Ÿ|¤»án FBˆHH% 4”ç ã$òÒ'JÑRä}­¤:¸®Ä*—5‰8iÂ’´€ë* ÉB2F”PÔÜíª§¸ôƜқ%8§Vëû€QÜ3ô^¸5&¼èÉÚ£¦v-ÛS1Kª3!K å™®`„“Î8©Ó}%KhBÔ®¤ ·~ýJïûÔ4-ñ #^ëf™ˆóÏ´¦¦°Û»ÝR”áS©d8½ÊZ‰ QÇÈ` žÉÑ«þ‰¿ZµÄéã›­½¢!¸és+šÂF©ùÕåk…å}Ö¶éÍ¢Êu–^l’Ш¥CŽyŠÑ’¶¥²·z‡Ôû/µ! ~쇑Ümah% l¥XRRpATÃLXUer{Ï^n7yžK®½5,¥IÚ„ %!–ÛN0œò É<ú`ÕòdÆN-J.Í‘íÈòcI;.Eác” $2Ï—È8J|©à~hùV=»MØ­Ñ-‘ Úã0Ŭ¨Âm á’ B”?Ò!JÊ'q9É5µ¥c©Þ“ZÖ×ví}WÞøš¦´Ý‰¨îGn×-;<Ü\Nßã$•ïî«ó•»gÓÂéøïúƒõšÚÖ¦éøïúƒõšåy_´z²ûËæm§V¥Iúòoµ˜r_b+ ~KͰÒ9SŽ(%)ûI®Š—eR˜Hm®òòà[çÎ~IàóéÁ­N¶µÉ»Z[b+ ¼ãRðKŽvÈÛœ«j’Ü•‚8ÎDíÓ½A9°–äw­-Û$!'jKIyçJp”’~…€BÜ!)àWÏ)Q§5yJÇ£J•9«ÊV,ã>[È3#…0Žã ºœ¶Œgr¹àcœšïTYŒáÉfCDÒÂÒOÚ*¹¢o®"ìÓ­ÈàËgqp(8§XØœÞô+!;Žõ$„𜑲[¦-³#ÜîW)P¢ÛÄIJ„ÄŒæô‚ØV\QÚ‘½[‚xÊÚ9øJPŒn¥r'JÔ®I!~<ÏÚQ­ÍGŸ›ØË·9ÎöbCeɹ´«b‚¥IÀ€3Pé=i°Ç¨ßS!ç¬rZËL>•ø¨Ž<Ú†•è¯+€ã8Îp¬×sÈúôé`êk»zïôÄÂ–ŠÆc¥|=7+YeÅ»/‹]—Ì´©U’úɧ•2ó!fO†´*élq/a%’âÑÈÊ’•’ 9W­—¬zZë¨,°#ºóti,7$+ Œê?!~îH9È)'­XÊ ÛYyÈÎ\œÒ‘‹“¡+%uµ¼/–ÜšÚ·]HÓ÷kײ%YU3­rŒÆ ê)Nð’r +%$ü”j/nÐzÂÑhi³^Ó H–à Jâ–¶@Øñq·O›Ü)QúÊz[¯lúÿN¦çmWjSXDØkV\Œá‡æ“ƒµ^„èB€ß_.Ú˜Œë­-Àü¶b€Œp\X@'?MgJ©T‹ºejÂÖŽ¬¢óMoúgØÓÞ™]Z´v³‹{LÄKr4Wn¸é¾>ê#¥JZÔ’_ÈicjÇ’SµÑúSQÚdÚŸ™y)l–„ÀýÕ÷Òâ|K¾U’ 26( ã íšÖÐTWB _a‡G¥nË\L˜K¨äð NáŸJͰjH×y¦#¼Ë©mÅ+v mõ²âxù-Ñ@ä|jÓÀÔ‚rka^XéÍ[,ÑÍ–Ê-úšõrm¶ÕÀ0¬¤’¥-!aE_z@ú†8Vî”­IX¯)9;²)vH_V,9V ¨8$ãíÿȬ='a×Ë‹TÍGŦÜÓ0¦8”ër»I¼çsÄ—w))Ü08ô #6çÿ;z{þÁºâ-õ‹¯P/:6dEd9(!hRðÓAl P¥÷½8'ÃŒÂu7†e…Ýx¡•%Æ6«pJUÞNÕ¨gâ}Xo»ÖÁ¦öµB{uwU /2U¸¢R²2§{œ?ÚE°u9›±vf©¶Üc.{nÚW -Çß”%¼8IJ[| ¹çï Äó4Ó®0´Õ®âo޹1 ¦¥ÊÆ;ï%-~ƒÞP'Ðzú ЦOUŢؗ`éssWŒ6,£j†S@¬¹ nJˆäú§²ô»Azö‰šše²$F»íª ½=ÆžN-9½`8•ä¼Îà’e“ÿõ'ÿ/ õ;RJÙ?øÿRòð¿Sµ$ ¥(*3«u­«MN \;¼É6„7o€ä•eA‘„rCNž>9Ç©ýô­ y†ÓšÎ?}öã¡r4ü–[î8°„µ¤%9R’9#’( Éz ,«{vÇï7%[ã6´@Ž@D*mM‚’•lBÔ”…0yÜpF¶é '°§.V[ˮݔðw¹,¶„îÜù*i@Iu8(#z=ÏT-^mšg[:”¨§{:rSˆV2•%$(qê ‰ê…QÕÌ[ †¥Y½´) Z’] ÁqàŸxQE'tEJõ*-Y<‰µ*¿Õ{4¨¬Ë¦µ»ñÞ@q§ZÓr–…¤Œ…$„A‚85°¶u Õ>k‚mWØðdLL$È“´òŸðá JŽôžïå<^9¬$”¥¬ÿòKßj¼+:°oÿÉ/}©þðªSùß–^ Û‡ûXö£@)AJù‰íŠR””¥¥)@+ër·Za*mÖ|XP@SÒ^KHúeJ VUE:ŒÉS6©a«ªLI…ÔË·0$;Eµ§qgbˉ!E$’7>c8EJVftâ¥$™¼f÷f{Àönö÷=¡»Ám’ƒâvŒ«·ÏŸ“Œâº¿~±Çjk¯ÞmÍ7A¹HHŽ£È|„äpqëUMÎß|”‹UÝ›áë[óg°¶­Ë޹;]ŽæT×%:”º§\œ àqg²\6ãs´ÞZ‘.áêë±ÞüW–—R’Ú ïmK ”m$G)g£Â׿›–ú,-}o7ú„W¥ãÚ˜»HÔ–f­ò±™KœÚYq\ð••`žü+an å ¹¶é‘æEte·˜t8…˜P$¬¡Êál»ÏµOr#w§Ý2ãX¤6ü„*GuØèJ–“¿É¸¤gh8v‚iÕ?~¹& ð!\.=ø¬>Éie!–Ч dëJŽâFI­u)(«ùì5T£FëÎ{;|ؓҔªåaJR€R” ¥(pç¸~Êæ¸PÊH¨–Æ(ƒø‹Õ§õWµTz˨÷í?­´Ý²›&‰’mîÆ æ%(Œ¤¾É8å=ÅŽ|§ y1úÇ«bi4Ë6÷.2mš™×ímnäÊ›|öÁHáѵ)eƒ’ÒéãéS‚„¯t¸u/>Qj‡$4Ž*œkSQ´³^²Þä¼WÅp•¾Š¥|ÿxêÖªƒ]°Ü'ÃÖY¥Ä}öSHdí8ΤŒH ÁòƒR Ô»ýÓª¬Øeئ·élfhŒ´¤¹lVÏ1Z‡«j Uƒ•§€NÚÛ!FRQÎï«­¯cW’:B•Vzº±MûKbŒeúfŸ¹­¶¿½ÓLê<»Ì­lv"äÞ›Hv(eo&o=¤½”~2žA…d| i® õ~›¶¨sŒé*l¡Èâ !eS£µÞà(ÉŽ6îãw q1+KZž~›µ\ê¤[råCK‹fëª_iаJJ¶à§n}O$ü0æÿÖ§ˆV.¦]¤0ï„Rn2µ*Ìv”¶® qÆü»s¯Tç?š¼rçÐTòá0¹ Úò›Iq8ÆŽGß^Õó½¦Û+±%ÛÇWW2J\à¢YõkŽüÿH……aX#ØAÆîG±îð5“§PihM˜Ëqnš²CN¶–¥:Òx;ŽÝ¨O$’y >¥PZ’ÞÔ[%õv£_çv“i\k›ú™Õ3OÍS.¥Jl„lÚ’RHDÆ4W—Hš{PN¹u•ÈÖ©ÀnÓ«VãË’”„”¬`‚F09Î(¦©T&º´]#ê‰ìYúŠ,Q˜q,¦=ÛUÊmJú×½;Ê”ypƒÎ8œúZ­Í7)”þø½BïàìùsT>ã ÊaQ¶ì-) ÁçŒqÅ{Ò¾qµZ®¾ÔŽÕ߬ðÛ·ùgX8%àã… »³ŒOZÍŸnšt•µë~¼ºB’»¥É“r¹êieöX”ëM…Å( ¥)ÉëÈ@Ò¾x·Û¤&#.\º¯&åp]âÞÃP­±×PëÊe§BÇ j×ÊHÇ51¿Ü§èÛ.®fÊ/J^q,»"àìµAÅ´½ÝÜú—ä lyrUÀ$íVš„\žâÆ 0k†kuíômV=_Ôš¾Ç{ˆÎ™±¢âÓyÕ˜2ÚêVÚ[FZàd-ÅsëÛÇÍB®^ÕQíé¶áº™zvïº4¥´…3Ù3~ུ $È)ÜmË]ÊEÞÓé*Ù*Ôü¨©qÈr1ÜdœùN?óÁÆ2È(ñ´§ƒMFö’vý/–3@bôlUJö³z¹;ç«w5%ó¶W¬µ¨ê»dÖbØfÚ§~‚[6çu%.œGí‚OÒ4¿‰îl) I¯'µ×SYRþŒd6—K1àL}I;e)MíJw¥L²Ö@)%ðà;@I¶ò~tÉù× ±´­niyó¿ë?UÛiV^µF½²êNÌ$Û´Qq[‹–×”„°ˆl)M¥MŽBÝî¥$mP%Y)Ðê½YÕž‘pE´Ý¬3Є- eyåÊZ⩤…¥]Ó®%%jN´íÛµUxäüé“ó©§Ž§ WÍ&Õ¾¢¶ÑtÕ†4‹Sì[#¶»CG·àq Ú=!nõÆFy9©Z•yNð–^+å~ó ÑØíG ©‰ ç;Ë;Û'%ÿ'¾Ú·[lR¶þºÝÑ‘n×[Zœzß|nÝru†°Ô¶TÓ§sdð—FÄ’œãÓ„åMëEâ$MjѶ©çì[z,¦ÐäeÊB;/ ¶æÅ œ€RwZöý¦`ZíVÈÖ–DKL‘.j%A€ 9>eyÔr¬ààŽ@ tf™U¾áorÒ˱®S¼|ÖÜ%BCÝÀæW“æNä)òàc⫬>/VÜæï—×ìõå¦9>ê9,¶²{wk&òü©«^Þµ¶E2¡ê楲]l¶­S;N®éj”mΛlÕÅaMÜRœœBB H ÔcFëm8nVí/§:}N¢ï{¶­÷غwrY’Ûƒ)(÷HõõYý\,µ«ín\t}ÛS[K‘Ü~4KI˜…% ÏI*lÊVó'ç@8$G'ÏÒŽ*Þ«DµškWX/ ©ÒHh°Ú%4·U¹¼¯´¯! ’0sŠô¢šJçVP”Û‚²¾J÷²Ü¯¿´‚Ýu6‡µê'•$Ôšž×vÐSµmóK±s…7T²âínÍÂ6ÆPŸ¤ ç;½+sã£Ñ ´]ú¨îs’ ^–½ ÒËäoÜ¿1Ø8Æ@ô¯çZCOúY©Þ´9¨D–ì§K’”0-¨d9°ŽÐÔ¢¬qŒš“1£µ®žrT-+§4 M:ÕÊ÷m}çXº²¦¦2±”” ðœzZÄÖwQ\`ßúkoÔ3Âà¥LzæYR’©òNГééëS[Œý,ç€öE5%–cwXxÔi$4Yi¹m8éÜÞWŽÚWÂA'Ó5ÚÃK·ãE÷¢ú’û-W9î¦jôš]î4ì·oÜ/žùPýcWôÛTê©ö©jЗíÊ›½/!«ƒ…H.Œ O¾ ¯kÍ%m°ß£iî˜A²É»Û·9)‹ÁZ’‡S·;K|àqǧ­X—IÖ·íf-½0ÔÐ-î½hÎ4º$¡™ÅéJkµ…¨‚0xφ°“£çi+Ô+?@õk“ðnÉÒ  ¡å6 …Bˆ9øb€ëû¡.zjÛÔæÆ£ÑQ5?~ËÃ÷æ˜þkò7c 9ݹ?¡Y½ ÕÖíA¯mÖkF—gO@²Ø§XjwˆIIŠ£ù#('ãïVÃTÝôôÞ M¹Ýza©uT%Ú¢1ÕéU¸qJS©BR¡¶‰ `ñÏ4µßOBê +¥«¥ú“KCE¦c]N•[a×ôU6’#¥J8 ºyó“È•ÕÚ"ÌÝ¢QéUºMÚÚW´ Ü¡n>Þ>—nÂT7cŸZ¹ì#|襦òXðþÐÕQ¥ö»›û{ïÉ^ÝØÆqœ i ZB“²Ã¼ôQJ¹±†æ>½ ÊÔãÉm!j*'*%@œŸZÞÙn = ­Úz—Ôv©S³%˜.iù ¢z¡Š½ùÏ7úxõ„´®‚tõVÞÜwê5ú¬ïþÛÚòh¤ì]x¹c½Ü-OHSW/g]RÓw6T—W»Ü·éæü¤‘-ÐÝA•ª/¿LN†÷vÏpx3-,”¶¦‚”!šàz¨$ŸPs6‰¢ôÌX¶8¬ÚšK%ÛÚÉÚÒÈÆò3…/’w+'q*õæ»»g·Y¬ÓÑmŠˆâ\µÌSêëÎ8µ¨ú’s¨Uu Lpu\çt¡/Òüÿ“,f”еԣ†Â¸ÊO'}ž½×Ã+uÛdSzñJ Wy¢”¥¥)@)JP WŒ¹Qâ%µIy-KH*ü¥¨á#í&½ªl+ʼnQÞ‘";O%nÇRRòª @ì ÑùQÙ‘;¯%ÈR’ʪÈDìÒÌÔ¯RãÌeOEy.¶—\h©>hYBÇڕ쯗[t?âe¶×Œ%ÉÜŠdïÂÏÕôkýMW{X\Ì¥c»:#PÌÇŸKLm+sÊ3»hüÏçY³”¥@¥()J\/Ü?es\9î²¢[+i¾bÝ Cöë£ÑäȱÔÛ«’Ú\ß·byõãë5åNX£Â¶Âb׸ÖǼD6’Ÿ+Nácx}"ÎNNTO¯5Ÿñ?«Oê¯jú¶t¢í¹x#Æ–"¬}U'n××õ}ï‹5KÓ–5Á“V¸Æ<©‚l†öðóáĹ½œw%>¿è1YQm°"Ü&\#Äi¹sJ —‚|îìNÔ~@zA“ó5—JÜ¡¸Åâ*É4äìúßWÑw.ÍZK§šTèÍ=xµ*ȘÆÜ–±-×¼¥Åç„¶¡í¬·´mê¦4sʵûJB¢),±æjì¡“³w¢äÕ€×Gm 0ÜxZ¿]ÁŒÒP<ÃHH!ë8É9'$“^ƒ¤v¾Ê›V­Ö«ZC¾%w•*JJ´¤%ò;©H¸6¥@yÕÇ&²4tÃTØ_xž»æÖàŽóªY€àÀøüÅzÌéÞ¤ÕæÝíj²Æåqh n¸•å3äÂ[PÇöÔ徑ÀC­¸uÇP^Ø´¬!ýDóͨ¤‚7!yJ†@á@ƒñÚOI ?)ù?†šî:Ÿuo-/®FkzÔT¢h% ʉ'd’}M›¥.Ö¨4´µ[ÂH²¶Ú™ZË9zæâS’R<ð¶´ÚÓ¥¶Û¤/W)KÓ½ˆ^çi÷JÊP‚£€Z8ãV¸éM¿Ù’íËÕÚÅæ¥­•ºã÷Nëùeaƶ¾´—P±¸¨ ’~'8²º7j—è²µ·Pßaô)·ZsRÈZ• ”“‚ã€Ñkí}ÕÚÖï&ΫJQJpLqi$˜Ì¨W‡§´µÛG]&G¼r—#N]ÞGƒZÔ0•CÉO<ÔâñÓ(÷[‹³äë]lËîã¹ànæ@†„•`¸Œàžqlé|;|‡d5«õ„—]Œ¸ªTû›†–RV”÷н[S¸ÎÑònz?¬e%RwMöß%Ô¾ðVr3ô^¼ÖÆV¹_tE“NÁ0S57kÂI}j e©®…`„“ð㊖§¤0N¼ê8`ª$cõÖK½.„å¦%¬jí^Ìxn8ë+r ¿½Â¥8Tò^å(“¹G$äó@WmtçRé™öÛÅÑV3»µµ²"¼âœÊç0‘€¦Ò=Oέ{\(×+îµ·Nh=S¬²ód…Dm*sÈ$V™Ž’@jCk¯$)‡ûh—~rK[Р¤Û¡HV¦zÈ‹C“žUÂmÂLç’ëÏJ(ÜJP””€GÃâhÕòdÆN-J.ÍíÈòcI;.Eác” $2Ï—È€8J|©à~hùV=»MØ­Ñ-‘ Úã0Ŭ¨Âm á’ B”?Ò!JÊ'q9É5µ¥c©Þ“ZÖ×ví}WÞøš¦´Ý‰¨î0Ý®2[vy¸¸¿ÆI+ßÝWç+pgÓÂéøïúƒõšÚÖ¦èq7ãî‡Ök•傌tz·â_3m:µ*Oד}¦=+®áõýÔÜ>¿º¾c¬¸–lv¥uÜ>¿º›‡×÷SYq=á~<ÏÚQ­Íi`g3ëê~Q­Õ}#‘.ø:Ÿþ˜”ñ>Ò¥+²+ŠR””¥¥)@hu&›UÞíëýt³Ì„ÃñÐä$ÇVöÞSJXPy§«( €¯Î°ÿ¯Ÿç#U³Ûô•*¥ü¾œUþÏmÿÒSðZùþr5Wû=·ÿIRªP_ÁkçùÈÕ_ìößý%?¯Ÿç#U³Ûô•*¥¤ÓZ}vys¦H¾\ï¦öÂÝš–P””†ZlcÌO ž}q[ºR€R” ¥()^*•2Ó RYTéd¬o)ù„úãë µÏjR±æOƒ8‘2lhì•m:êPœü²N3Á¡)7°È¥y7&3‘Û’‰ -‡6ìq+*Ü@N¡É žkºV…-HJÒTŽå?h,ÎÔ¥(@¥)@)JP Á¿ÿ$½ö§û³«ÿü’÷ÚŸï ¡¥?‘­ùeà͸µj4”¯˜žØ¥)@)JP R”áÓá:û*\µÚäC“1]K°JŸx%Æ×Ûqe[V„ì)H)à(|Ží#=#}§Ôà¼ÅpvšB‘p–ÐÚs‡Tevò¦ÈÁÞ¬`à‹^•j8ÊÑVLÁÓ‹*Éý'vj$npG}’φLE¦#Cj-·ÜÊHØïc¶¥$ŒVÂùÓ¹W-e±9>Øòí¡Ôï‘n¼-µ¡;J’¤‚“ó@àg"Ã¥:el³Ø9¸•4þ‘Γ|¨c%SŠw@ïá'ʧRGˆC°ãnr2gô±ùkyÿhÛã)Û[–äÄ¢#!~+ΆʎŸœ+>À0âÐ¥KÇV{þŽj%I?£îÉ.O´í®&J~•¹îðp{‰mEJ8@PIÂväƒIÍ[t¥j«^¥[k½†Q‚ŽÁJR´™ R””¥®÷Ù\×{‡ì¨–ÆJÚJ þ"ÇõiýUí^0cú´þªö¯¬aþÆ‹Àð'í1JR·ŠTnµ²J¾¥I‰oKJy»Œ¦KQm$-.Ÿ.Ü‘‚¬ndîךµþ’DõÆzõ–‘ÄŽ<„Å-å€t¼™-Ï%\PŠTrí®´m®Þ¹Òõ=¡-&(˜6ÌmJ[$à8JIàÇ×R$©+HRT’29PÒ” ¥()JJR€R” ¥k®nVÆÜÚ ãh5çi=%KFÑ窦Õ핯ŸkFpƒ›²64­?‹—ü÷ü"ž._óßðŠð=4ÁN}Ñÿ±·£Ë‰¸¥iü\¿ç¿áñrÿžÿ„SÓLôçÝû.&â•«‰&Jå¶…»”¨œ£å[J÷tV•¥¤éJ­(´“¶vàžæøš§f)JW¦`)QÞ¡êô¾œ7v"¢WnCIqµl¬o#ª Ýñ8­ »©q–«²¦ÛŸ,Û%-®1J¿°† ä¯r“”ý:N¹D€y¬ÒvføaªTŽ´VEJ¯OTa&à¶Í’êäw"1*i ©Ù ,½—B{žæÖÓ€p¼¨µ±‹Ô/ÜŒQe¼!¡/Âø¥¥ÖL•E Çs~Òêvû¹äÉR,—„­¨˜Ò´ëtÐZ ]†¦Tá,¸¢ó m[™@R·¤'×ËO˜'Y'sL ãkï¥*LE)JJR€R” ¥(*¾êŸQ¥h‰qÐÝ›Œw HuÕOìÔà|§ËÛVSˆëç9Rx#$G,½e¿^Ð^µhHÒc76,7ßöêR–W!Ô´ÙP-n#r†v‚pÒ€¹*©4½òf°UÒÖ FÞZP–²S± O™µ6IXÜ­ªCˆo08!p×zï9»Ÿ³£â…ð±çQV‚ZÆAç¼ãšÙ^z±©l¶×ç]ô$H‚=Ñ6Ç™7䩯ÜTpøYÚÉIAJ’8Q9>˜É¨”T”ªÊ“m–ý!­VÌ«ÌÖÞa§ÔÒEòC‰/“b”HIRmÿ"÷¿ÓÌ@ÙÃÑ—Á=/fT¥Ã]®jÝ}ä¼$/gjBIy Éq(p ÐTfÍÖ=AycÅZô$Y:4dêR†\}Ô4‚¡ÚÜFå§;Aã<ĽuÞešt¨sô”$;ç™%7¬¥e·TÚˆË ãrŒ€~ªÁSHÝ,eI+eæÿRQwÒ:¦Mä÷OàmÌŠòVìµ”©¦œŽ­ž$4…Ó‹Üœg pBu´V¼eô<‹’ØÞàT€›ÛêqÇl¤<\R PžÒ‚“‡`d^ú™«ì–«¥ÆñÓèp“mðªu•ßÒ§‰ö´ìh§AY@á'ñ,~º]¦ÄºHµèFg‹\''JKwÄ$¡–ÆT|íŒý”t“2Ž:¤U¬‹cH[¥Zì,EŸ%ù39[î;%o²yÁY$å[z¨µ·Wo:;P -ëI[{ʈ‰iq‹â”ÙJ–´móGJ·Ù'Œ`ŒÎ34§Ruf¥›˜ÞËr&E•&þn!—m`0¥…nu>©ƒÏ¦sJÊÅYIɶËF•EZ:ÿ6èìÑ4Sdå6Û1Íé!Ek “ÚÛœœzãë©·\ê·­o7 l Û²-¶oŠrK+3D2­‰cb°³»ÎSò‘­ï€·€O'g‡+JT2­ 1jR­,M’J ¸ÃS¬¯ôù×òtfääè—ÏhÇoÄ­1KÆ1yžòÂ’Úp‚ð….AVA:y¿¾Äx¬%™w !qÚq×Qy·Èx)°’” ·åhŸÊÊøPNvÛ¢ÄÙ·¨³êeAq¬! .£–Û„€¶~› ¤(+b‰úEoQÈá>|bj=GÔ;2î²îîÜ­v¥J-Çt+q !¢¤„¢˜ü8 Á +vquR³X¸ï§â9·Å”òdõ}ût‡wÏiÕ6ÚÓÚ‰Œ·ŽÙYÊTPW¼- ÁhHVÿS7­^¥®ö'Æ–-΢sL®;"B×ìx«‚€öTØÝÇ”ŒàØT¬^+4Ôwa©ÖVŒ^ubúw.èì÷œ–«¤h1†Ë*ȘC1Üy°¤ì˧º´îò„©Æ3][ýòŒEÈ“&ìâ‚QÚ…ânÓ….y“³¼WÙ Ú2æÐ8"Í¥GIJö‚ÛÀN²®–žª©¶»©ar")+a,á׉!DÃŽÛ¥¦‘ùÞ«È$Ö¾:¥×S ]De©÷YT–¢-ǧ^?L2|’žÞ$å'já¥JÅîÔqß[!¦6µM‚V[´ó9«|—^x3­é!°YB‚›ÚVG”¯Æ´M¯ªrõ$èËvån¶kí=²…-¥HØv{ªPÂ’¢3ÇÍ‹R•Œq:±¶ª~â\.ö•rOTÚ— ~k¬*JKŽ%ˆ„ÍEQBÓ„ýY’2’å÷—6)ZêÕç-’VàLcm┥j2Þáû+šá~騖ÆJÚJ þ"ÇõiýUíT†²^¯bHµ²Ñ²0‘x+qA¹#j”qþöÐ2s»»ÈŒÙeõjC}2}–»·sãƒj}JËðo4³ù¿_©ú2^3ôšXåqŽ«v·É|ËÔ¹(ëB5:D²¾nÖõg+?ìÏ‚iõJÒ¾a‘+©èøžÆCϵN™R ï)°Ü ´’x,ÆUÀNðNÐ1.Ò’µ’õ~¥pDeiô^`o)ZÊœdG.&9#%вàðJÛ ¤ÒÔ~S/q‘•Ê\ü·_?j1Ù}¾¶Î*KvrÖ ).Óì7ºŠ´Ú£ü 4NŒ”Ä-:‡ Ë´¥!yòäžG„# ¢NfWá„UºÛˆtæLd­2"È$„€9\Tgò•ñæªh,tôi»/á¨i¹9oA['HÆžJB””(º¸®(ùR ¸ ·×§ºw0lkmÒkT%Iz=…¾òYº8b­’•«)hd¶}ÔãÝô=ù:+§Áù vÃQuդˊ —Ö…·ÝIZII^Ü3´|ÕºÇoVinÚsª,« `«Ç5ÏüUBi×zJÒœïm¾Þ¤½ÙŠwG³ qœB÷‡ŒÞ åG£ɯ-P×O—{¹«Pê¦mWG.3Ë­+HƸ‘5ô¡EÕÅqGÊ”Œ”ÐWé0­§TY3òñ튻Y¥@ÉÔÖQÿ÷šÿSé:f‰Ôò4ª [´ºÑe\…ƳÛB…Áļ鎦ð₞ à0@ÅDn¯t~6™Ô@_­÷É’-2·²öŠb7jJ{kKÄA Ý€ 8ÏÃ4Ò‰ÕÚQC)ÔöE}“Úÿp­_¤Óïj‹"sóž×øªêâ4RµÅÉz«Q3i•ÝB#…i–.J[A†O¾äwJ@R•å ’qÍzèé:%rÏàá:¶4µÑ»”¸Öao\—’¨…µ- i´’7/Ž1šßü,Ò¿å5—ý¹¯ñWQ«ô’•´j‹!?!=¯ñWÏvåtfá§®zžß.­òU",ÈqRÉú_Øspçò¼‘þ‡¢¶zÜéöm†é{"+0–Sñu. ¸R‡µæ å9ã…qŒ%èÝ&d· Fi l€ØKä%%!¦€8à2„—˜~QÏ N4”Sœ_›|¼JtåA+M;þæ#zä:ÌÞÕ¥^"<–XLuÊl/.È '¸žTÙÉÝÈ BN@ØnfäÔìUÄ•óK PVÅíJ†8P)ZÉ\€rz%‹H\¹Á&_vÝ)¨n¥ržI†à,ÉCmŸ(9eCg OäàeÞo[§Ö6é5Øñ}eÇŠ–ëŽlRÔ¥m YÂPy<8àRtã/Vœ^³Ù—a%GUê¯=ý¤²ãÌý§õÜÔxºð*M½q8¶ï†CÎ…8pŒ»½p ×Ïé—Ô§t5¼ÚPóÅ­^”éµ'%å 7$-$ž q•p¼´ v\“Äs9Þ-Þoá´~ƒzSZJ¬aªÒõºõ¼-ŸUÞë?¨i_5êY]@]›¨l´’¨ ΄cxE8¥7s2c¦)ÀQNò£ŒgvÒ0J³•r•ÔU\5SFã%…hç¨[BŠšnG†w¶PGÒ7å8Ýë„ê^W¶£ó§waz<Ž“†·H‡eóÙÇo¯k~$•ýk¯¡&Ä‹5¤µ.;O¶•…„¸¡¸zÖ§Ki´´ÃBÅníÇRVÊ|:p‚”¡ #Ž0–Ûbò èbõjâ¸u«a_eÂÚO|±Ü“°¼£‡ÍŸŽ1» ܳ\¾üx6Õ0û¬•]á6¢ÚÊJ’§Ò“PAÁ¹Jj¬íkœæÂËGâg‡SRÕÞžO-ÝGwtn“u/%Í9kP}Àë ÆO^nOûëbˆøÖrl¶„’SnŠ Xpá±ïKÀý½ÂWÿX棑l¥´0{åEWã./ch”·ÞHúO RYÞ¯3˜Þ¬( Øi{Uò åÊåâðžßyná]畞ÛjK}ÏyÌe@Š»<=8¦ÔÓ±Gž¨ö¶lX±Ú¼=xfÞÃwÎ]”ùÖv„ò~<$° ØÒ•^Ö!ÉËk¥(@¥)@)JP R””¥NuÞë6Éxs·MT)L½!ä´— w7rIò¨x'áP[^¾Ô·ÝCbµ\u3³¢?yƒÜePZl+l–ÖŸ2[y’¯Â­ý}nÕˆÕÖË»Ëikiû‡…F¦6FíŠõñI#ƒîqÁ¬ ľ§ÞÙ‰ gOí0£¢ãS¯3¨ÃËbKo” !¼r¡ë@U·£j«TǭеSÑcFÂh@ea)Àã%²OöšÙ½ª/´Ú™›ÊÛº=©Ú+—áJ‰µ´“äÛ·ÐÉ«Ñqê¦ÜͶ?Nìò™Ž m¼æ¦í©ií%":‚N1Æã™¬6Xê33½#DÀUÅûÙž¸†úÊ[ð)Šw´TU”$gŒÐÕ«^jKýöÏk¹êWgDvïoRÙT›¦c*O™-‚9ã^·Ýu¨ôýæ}¶×©7+ŠÃI„Ó€(Ï‘Ÿ2OýõdÞ¥u:øÄX3zj…ryFXK[x„ °€IííåC×?Usg•ÔÛ2 ÀйÑWp™-§¤j Ä?!ǰP–ͼ(çã8WçRÝ®=4ÔšŠ]Ùoܘ6…7(ÆBJKw”!'æ9¨Æ©êN«Ÿ¦î%jÇŸ&#¬¼Ñ·2¤)%* ‚8$g5n]âõìÕÂt5ÁÇík»Ú\eI‡/ÄΖҤ•eIá ÆçGPbë9·»6†‡1‰–è±T‰—ÄFSjeÉ $lmÀ Cã妸#¨Rµ¤ÝçCB‡ºTT¢ñVµ¼äuw¶ØÄû€¥4çR5e¾ÃmV¼ÄxÑZe–…½•¡) HÉl“À“VŽ”–õá–YòŸ/¿'SÆy×Jw­WÔ’p“è¬í'/ªö -h±Ù$‹tbwާØ\í #vß qœg8Ï©¯k}·XKÒñl×6Ô)+Ôº>ê.u–.‚aH8 QØ6(ʾCšDcjn¤ÇÔ»¶*[´‰²F¦ç/èžYÆ]t(öÎôIóoV·Bê¾¢LOOœZ]mš›Œ‚é•Ûö„Äø¥%‡W )Úœm>¼íÂ>ˆn4fÃ!¸í 0 ÚqŒ'ä0å\&,T¥(LfR”¸]H $’¡þ‘*$Ÿ\“ó¯;¡OZüãòÓù[Ë¿bùO…tœ:.ò¾†qï¼”¯Æûõ\~nÓúéW d;íG—­“òêV¦×&.=ZkÔ3“åV7¹Mu¯¾ëM|Χ‰àÄEDmËĈƒ{›Œ,-9Qs#8I‚6ÜJ‹AÐc2CË w(uR¾d¤dþhùVõ–“CᤖBœ ””¯ ê@*VÖ~uKƒ<%Y:Ú2÷ú­~ýäâ9G…ÅFt¡„Œ¬“_wÖŒ¼®ì”w]= ¥+€<ÑJR€R” ÕÕöÚ[›T­©*Ú‘’qðâkµ( ÐõEónjk6‹l¦ü‰˜÷~àa ˆÅ!D5¿„¤¨lJJ²®Ôj³?š‰i¶™ë…%Ôâä]Kn6‰$a°A,ù’¡È)«} 2„2ÚG>‰××﮾.<38Jv§È8!õUÈÖ ¶Óø³[Œø•…ëŽÅTöÑb‚êâ/·ü°”ÁÆÛ*P-î%À¤))YZJNTn¯ýCnÞî—1 Å‘P4‡}ÙŠh ,´„Ôµdº9Ú8É51£”©&;E+HJ†Á…èÌ î¶ÛZ’¥¡*(9I#%'æ>U‹«G/Sâø«.%.:Ëq]ª A²¹s~WÜwÚ KHqheÅ…ו!“¹%{N¬»—V§0¹Vö-Дڀeésð=íªÜ”4Ci …Ê å>éÕ²aÄ ƒ‚Áú1éÏ÷Ÿ¾¹Thê)†ˆ)ÚA@äg8û+g?‡þŸÄÇRˆ­]êšãBžâ­ÐœrœBZ~ê†yA\, £ €“¹*NÒFLïI^ZÔrᤡ)‚JP¥))P%*©)$ÉHÏʶ†9úùIIòCê>Ê) H H­'NJÑŸiœT–ÖsJR´ R””¥®÷Ù\×{‡ì¨–ÆJÚIà€a19i ýÕïœükÆâ,VŸÕ^ÕõŒ?ØÃ±xý¦08àqéõSÀùÒ•¸Äù§GéNš_ômŠáªu²mWTÁK.Fxì¤)EBÁ ”¨¬Y/ØtiÍi¸š­i‡†™7i²®ÚÔÍÕE=Ð6ß~PùÕÒ4„ +M€8ZØãþ~èOò+MÿºÙÿ Oþô¢Ðôk•_&}Î<†¼,u^ã9ÜZ–µ žäšô‘¤ºg}ŸpŸª5²m7TÝ.,¹]ã±µ"så¡`JH?an~èOò+MÿºØÿ ?4'ù¦ÿÝl†€¨§Ùtm¿Fê£R"fqv6%OöƒNv»·%‡Au#jHJÒyn:Óë} Ñø6ù>ßÔ1&lK{ïÇdß"¨-Ä6T„“•1W¯àƒÎtÖ~~Êcü4üмÿîV›çÿâÙÿ ]kÍ5 u¶¼/Xê´Ù%F}±¯i±­¥Gg͵ÀI’¡ŸNÈÖ±é?z™Dê!|KÚjìì‚'µ$¶½ÑÑ·>o_\´†€ÐcÓDé¡öZ˜ÿ p®Ÿè5{Ú'M¶ÔÇøh }?è´Æ„—º”9'é\Joñp®H_LšÎzÙ¥nÚÁj¾_ÑÀ»µÝ,ÎÚh81âÖW•[’œñê5iþèOò+MÿºÙÿ p: ÁÜ4Nšçì¦?Ã@THôÆÇ>×qÓ:Ý7[¨¼[[j1»Ç{xTæ°„N“ÇÊ® 4ŸýíÕdŽ ¸ä³7C 4:'M~Ëcü5³°Ølvb³[­L¼çuÖáEC)Zð¹ApÉøò 6$0y¥( V½Xéµ\ÝãIºK›È𜊟 Ôu‡BÔOu¥ó–“‚1Œ«çVUkn >ä­í´T g W;Ên°‰áâÜ”–ÄÛØ÷$ÍÔRÌ«èýÔ¼}½¨ï)]ÇŠã-JBœ[¥;VÉFBÖTÛÝa9΄i ©ç\¢)-!½ñãABÔRÔ†‚ÉòW¶Jüç’R‚NFMµáeÿ0H~ÚxYÌÒ¶¸$ô²ÙNÙ/ú–}KZë¼­õOI,š†ñt¹I½Þã®æêÓ)l¨Çmï²¢v¥¤”äœ+&µªèfž·-±õ&£Ö›iÀØ…î¢:XB£Ù(IÉ@IQYÜNmxYÌÒ¶ž_óô‡í¤–‚QTçeþÉÔ–àïšÏ¬‡ôÿ§:Iêçov¾á•*?†p®¿])]A ÔêíEfÒv :‚ý!q G-¡×[ŽãÊÊÜKh ¥KVV´Œ}j£×]uÐ3-öQg†ð]¼ÄQ¬RÑÛCn­Â\m!A!„åY#Ô‹sWiËF¬Óò,7ØîH·ÈSjq¶ßq•e·â[jJ’B“Á• GBzd€ÚQgº$4rØ‚àÕôüzÔ;îóç3d9»=kßu»ÏWÝ~£É¾¾ôµÍ»ÕÕ}ÀJ6éë‰ÜŒ1Î+Û÷ôé±NïhÞñœgðjåŒÿ³×Tt#¦(Ù²ÏtFÀB6ê €Ú¨OÆkÓ÷éÎ6û>õŒç„—gÿ¯Xúö[<í#©¬¹|.øu[ß}ÄËHjK>­Óñïö K•o§Û‹aÆTTÛŠm`¡Ä¥I!hPäJÛV£Gé»>’ÓìXl1œoan-¶Ü}Ç”ãŠqd­Å)J%kQäŸZÛÖf”¥¥)@)JP R””¥¥)@)Jˆµ®"+PÝ-n"*QmZ’þÉ]ÙX IÜ#¡%deiIm3…9Nú«a.¥C‡QôÑ}²™*T5²\ïöœ'x@Al§vâ³´$¬ñŒÖ\Mqat¶Ûí;1sžS[ÐÉC$2ã¥+QH X à¶HXÜ ¨×cÃVY¸²MJ†Á×ö÷§Ü‘áf‡c­-KïÊ+Cý‘ü +ó+Óׂ>uÃG°<¦¼Kå2ÛŽÐm‡ýÉ`•« ú „¤ïÛÈ#׊xñµíªLéQ»^¹ÓIlD·Ü’ûêÃHDGŽáŒ…ûŸÅ‘èç¸yÁàÔ’²M=†¹Ó”¤¬)JT˜ R”°oÿÉ/}©þð¬êÁ¿ÿ$½ö§û¨iOäk~Yx3nícÚ¥+æ'¶)JP R””¥ár™Ýn“p˜án4VVóË *Ú„‚Tp'€x5¢s\étÜ·¦â㲞”äD4ÌGWuòNÔcäð@$5#qqµ6âR´(¤¨d}A²hm3f–Ä»|4ûµ!e娒ÛN4’¢O˜ìuÌ“êNO5¶Ÿ5g¯{ÖÜklÝOÒ×ì¼ì‰{ΆÚ.D{jü(¯;8oéR7«ì­”]kd¸i¥ê CN„‡›h+°ã[ÊÖ”‚žâFäùÁÈÈ<óXÑ:u¥¢."ãF–шSÙÙ5Ôá)KiÚp®R{-’ÄÎVr­Z'NÚíÓ CŠòYšú|®CŽ-kAII*Q'©ûqÎktº6Øßàbµ÷˜ÏuG³RWs|¶–ûÄ&…+³Œ÷ÂB2YÇý(çVN³—Üã¿ –æ¹­Ô,,x±ÜØQ’¨n$%;”I€x¯ÿJí3a·Õ2E­“å‡]qÅD) ðèWp Âä|J ­Ôí¥æ—L¨ sºâœWÓ¬y”©**<Òß B¡tc/üD·¿‘ê\ÝXîÐ/vÔ\m¯£­JH*mM¨)*)P)P I‚fÖ’ÙÑnnBñiJç”âÉQ$åJ$úŸO@8³j¤µuž®Ãb½³¥+E)JJR€W{‡ì®k‡=ÃöTKc%m%cú´þªö¯?ˆ±ýZU{WÖ0ÿcÅàxö˜¥)[ŒE*ûáÄ%Þël•ÅpJÕ~àéu)mn‚¦‘•¤-´)iÀQÀ‚T@9±z…¤¤Ï‰7Q*RûhièO´¤+¸¦Â\ @í- JBöî# Í)¥hX¿Ê:ÃØlÏÇmÆz4®ûk%²ØQ(IÊ. ¤úí9 ã:ß$x‘.÷[d¨6+‚V¨÷K©Kkt4Œ­!m¡KNŽ¢mJ‹Eê’“>$\ÝD©Kí¡§¡>Ү⛠p-´T´)) Û¸Œ'5) ¥()JJR€R” ¥j®eBnÔÁè¬|ëÊÓMhÜ?<ã­šV½¶™Ó†»±µ¥hw/ùÇ?HÓrÿœsôs>›GúòýݬßR´;—ü㟤i¹Î9úFž›Gúòý‡Fë7Ô­4%/Ʋ ÖFOGäks]…ÒËJQ•U [;m¾äú¸šªSÔv¥+Ø5ŠV§Y\åY´­Ê醟•:œe·TR…¯àG f¢Vn¦"áu”Ãvy/2[‡àPÁOzC2óÎ$÷”„°±É¡C×±sIÙ›éáêTƒœVKöú–*½WU-ŽJ‚ì;eÂU¶k*,„¥´™N!Æ‘µ°¥Œcz÷w6{£Íd»Ô»r êö ô³¢ëΖÙHBRÃO¹”©Ð¬¡$‘·’NãŒÇ9&]·á'4¨æ¢¾\­Wû[ ‰Ûl¹Žë¹Y[J^@*8Ú»bFI++ÀÆ9‘ÖIÜÓ(8¤ÞñJR¤ÀR” ¥()JJR€R«N«]õd{ä+~˜¸OŽâÖÂ;0áÆ}n÷–µ>¤§ œyÓÁW¼v ŽçT“3®Zªûla(1Ä˹·D‰-²¢ÚÚuÐ BóæO˃ÍuV¢~œ´Î¶\-ÒXZãÜïÈÅV6à‚Øž=8ûj¦j_V%¾ƒñªU÷Gj@±ZÜm-©\(¨¾…¨y=´“‚B}+"ã'¨±— 6±¼Ý.q/‚Þ¯h‡Z0‘'r[s`IÀ .ú$àdàCI™FN.é“–úm¤‘F_RàªS…@÷{¡[³ÁÏ09ÎEg[ômŽÊ5Â3rÃÑ•ÜFùŽ­*s¶¦»ŠIQ ^Åî<ãÐ ­™sª-ÃEÆé«/–Øé¹Á†¸òìvöÝy2$6ÉRÛ$mîgÌ>ŸÇ“pê›®ÌrÓwÕSb72TvWÍjx} ‡åKy’I-çܸç1©Ljªòr}åºþŸ¶=i]±M:–%rÁCÊJÐòž/¥@äê'Ž>œV¨tûL  µK%.‡T[–êKН|îóòÃdîÎH$ûÊÌQ9Ô ³+×—;•Î*­¦?†´Âm+¥6vÖŸ2†ÒAî e@IÖÜ%õr=†ûtR_­iµ[œ?OÛv>[IWm%§Ü œz”ãõŠ{ˆj‘Ù&ZºGXl/2í²;íxt©¶©.- €6¥*$@|ªATö°w¨5{Ök£Ô÷&·±)Å5k¶>´©ÇN Z˜a®0ùY#Œäi†zƒ#QÚ _u½ê3w lÉ~Ù#Èal=,€òPxžÀsê*RKa„ç)»ÉܶiT6™¸uv÷Ñ%7­LË7ÙsÅûÖ¶K€äø€²€s°+’&¶ÉÕ´ÄKäýgp¸ÊoP¢Öû …¨Ï7íA GjZî$ì;†÷°yT˜–+Á¹Ýðý¹L/Ä£¸Æ×î§îOç r>b¸LèKB™‘Ô•¼¦C …8’R¤yP)P#ÔŸ•EÑŸ7>|£"°oÿÉ/}©þð¯e΄€ùTÈéÜKOèµ’®x$-ùÃæ+úû ‡&*^l¾ÚZqm éJ—„¨P J€?§äjŽ”k¡Vü²ðfÚ’©mëåõF”R‚•óØ¥()J««CM)ׄ$©Düõ¯(#OЉQ²±”¬‡ÌgáZ.¡é§5Mˆ-» =—ËŹ‘ËÌ;–œ@ @PÎÒ°±Ï BMW·žÜS Ù.‘˜Ý®LT$ÄóºV‡€@Z”B.ò@ŽIÕ*T§Zv}†”“É•ÎTò|JZ–þ!ÙGöWµT×^‘Ê›C Üí‘!i*Š‹^b´•§km•’Œ• ÉVþFî<&ô¢uÞȲ§4ÐbK^Ï2Ø¥4®û©Nü}+Î)d/vv S’¡Aÿìø­.ÁJ†jmP\#K¸˜r–ÃQø¡g H;Œú¹²>J ä*?ûÓ¾KbñÃe*0‰RІ؊³¿Íìܦ°2Ty!J“YÎÞâ\¥À³aK1•=äºÚ]q¢¤ú¡e hRT?²½ª¢ýç,ÉhÝ-É"Ì`l‚´øpø65‡<­øòû§â S,Óz †ôäûz!ÅmÖæ6ãq£·%ç[[`”üXÿÜ8ÆNf¥*+ÙýÁJ[Ñ,T˜é–ÜEtjLwd=§·X :€rPHÈå‘ÍTpº7&±1¹ÙJ’ÛÍ'u¡* B×Dy”¢sáÖ2²¢;¾§o9®“Ü-p^nÞÔ™.£¶·Í½YR<;Œm8s8JV’IÆç9º{eSàÈÖŸÙ¥Bº{¡ÞÒ—ËÌÿæn*ßÚj.ÅnÞ¥)eEJ÷±Œàc '8Z­R1Œ­tf›k1JRµ’)JP áÏpý•Íp¿tÔKc%m7¬\íì2Xz[(v44Iy^d4B€Y,¡\ýF½™º[^rlΌꦰ©v8hmÊÐG¼Ÿ:9œ*ªÔº7R\:´Î¨…>kVØšx!À„ ™%J{ø"FÜž *ŒŒv”Å´çIuÌ$èŽÞäCvß"dé.²¤¨[P¾ÆA9 Rˆ^G(ʗ€%_I£Š­qJÖ_%ó}Åêz GT¦§<\bÚ½¸=YÊÞçÿu¶Ú÷Ì{Ý¢DhrX¹Eu™®–#-‚—\D Î‘ê6‘ð®íÝíŽ6óÏŽ´±(D{jÁí¼T”†ÕòVTžçèkçÇúQ®%XT†§=Ë®¬MÁˆéWòkICÿÂRxpåTã@'' •ií ª!ëýC©¤Î–`ʸBiˆFùݧ™Ý%Ĥ¤ «PÀåG„ð½Åו¯OÍŸŸ,F€Ñ”Ôœ1‰ÛbãëE[¹¼öz­û.ë¨Ô”ÁQ¹]ÞŠP¢ÃF¸©˜ÁÁÉiµ# ’•0Ô¨¥;A ú1ªº1ø@u{ÉÓ!!ÇZ‰=)ZÔ§]BW%å%'ý?€5\›¦ÛÓV(÷m›ÄƒmJŒ“}z.R\^±|êCÔ¬ê¦ûp°¥Qa´å¼\”Ò†®£øä¤ÉÓá^‰Ç"c¨=.·jyW”jÈ›rZq·!NSn(á ¥(-c9À|TqÉ9Õ}èî÷ãÉy-°§Z]qSQIK¶Ê0Ú¹Rxh*HÚ )¦5M¦vÏcш´ì6ûâúä’6(”á+OÖ¯B3Ÿª¼54Í6Õât{¾‹MâG¸ ßž‹”™ò0ˆãç@L˜Õ=ö»ú²<벌û~*SPn)l-ÇTZC¸oiËŽ«jUêVG9ÅH¦u§¦ð˜[òï²Xi´•­nZ¥„¥#Ô“ÚàTRjq©ôf¥¿M´%¶d; 3‰!w'º w Œ‘Q+Ž©±ÚôÖ¢nË¡[·H¹Z¤A\Â_)K‰#! I0xÆh ¾gUô<9ŽB™>çK`)l»eš…¤A)-dfºAêîƒ%ÈЮW/¶Òž[lÙf­Im$,€Ï ŒŸA‘ó¨VfØ£k[‹W(/n.@-8oÄí§Ã±”í@Áçœ×¾—Ô(¿<˜ðìé´Fµi{´VLõIòæß2’øæ€™Ž²ôè²—ÅêYi@¬Zf`ƒèНQÕÝ`ÆŸí+ˆ‰)Å5óešui* JUÚˆ)V@ôÚ~UQ[µ>š³_v…ÓÔ ÑQCçR?ʹIVÂ’žrxÁÖ÷PN…ûßZ®k0¹Æ~ñwqPüzã€W9Õ'é3åÏËšxzÉÓÁ!¨þØš^yÄ4ÓbÏ0©kQ J@írI ñ$T‚°Óó Íše¿ ˆ/¡‰*¸ÃzmkÛ°úpw§qϯ­RzwS[vEÓv]$‹33/vɸ/NJ;‘:9÷VœòŽ©T¬—]G§µí¢ÌÛÒ&;:n: {èØáEc„ ¤åœdudáNRŠ»H¹£èSÄâéQ«-XÊI7Á7›ÿ9q-w;tA0ÉšÃ^ &Vå€YhîÂÕòO‘|ÿ¢~UÙ©ð^u†š˜Ã‹ÉÂPà%ֆܭ8õOŽ<ÃçTeæÉ«‹r—|™,Zôz຦ö•\]W@#–ÆäeJá8¼ÈãOt«[0:}N • Ë:åK”ûJJŒ$¯³¶3g,« vQ‚® §Ò«ÞÜÓòןw¤zE¨ktØïÜ÷FO·j_Ü—´¬îèW›LذeD¹D}‰ä¦#ˆtù RˆIø¢GÃiùÄ•!‰R ±ÝKˆ›%?!jB‡ö)$ePön“k§´ ®Ú»‹ö§åj&î`(ii-»—r“žá%8JHçnprE‰;UY4LhöíM:{rœ3¤!j·ºòÞi•¹Õv)î¶q„ä¸09¹îQʶ'¢é´ï~Oü•ôžŠÁ`[xlB©ë5e¹gö>pººhôê5îm™˜FèñKŠqKîv–Œ€’êß©‚ ¢ÛèH‹\5íñ7yPc6=2‚qQÊJXS,¹#rIÏ«¤g^*j.¢éxpfxu®ï.*Ó˜lÇp­IîvËÃÈGe* ïâÁN ®©ê—O|!«ÇˆßŒ:Õ¾CC„â[(ÆØî«9ÆÖʽk‹¥B¤cýÝJž"”"µ¢Ÿù4¨Öw5Æš‡/Qá‘cí´Ùm†×1 ¯ò‰I^6¼”«pV3‚5Ñ·'î0æw¥39¥­†f´%);†8ÈR”ƒŽ7 úz 4Í}¥a\µ¾öÚ  döâ¼ââ¸êÜÃ/2„Ý 9>\q¹9×j.­é«n™7[i—°Úw*3`²ß`¼…¹½6• …X Ï—8"’ÃUª’…-¶Ïvioó´Š•éÊ-(ÛÏìYp¿gí?¨ÖLkÝ¢LhR£Ü¢ºÄçK1\C ¥×Q(Iø¨l^G¨ÚG¢wYé­M}‘bÓ÷·œÛ+*~4G†A HZ]R Dä2H8ô"ª´ôŸ\¿¦RÂ'?Û–ªEÁ˜éWòkICÿÂRxpåT‘è€NN×rZUðØI®m»Íþ˜[¼·£´n®ñ8…NÖJûï{÷Zÿ ­AûnÑáßÚQ{Q嫸0ÓåIHm_%´ŒÎ1\½yµ2n!ÛŒdkaÙ©. ÇAIPRǨ ~8?#TF§é޳ž{1™O­‡¢D‹ÍST‡ÙÝ)Ѐ6¬ŒpTp7dÝúk¬Íîñ?Úóf7I¹lSëÚ^»¾¨ë½£òAR<ÊʉJFT­Ê3Å׿ٿ7ú'åãÉíãwŒøuÚÝòqíWÝ$¯'•o¸w­®–d‚Êu“浕“ó¡_ukäé½,#-‰kXe値-„©ekPøz•:áûV¯™¨ÇF4µïIÛý›{žü÷QnŠ”¸¼3…È%„+ÕI@R}I÷†00û¨8ö}¯?þµÿйJN¤¤¬øÖ>”0˜™R¡S^+d–Ç–Óºôþ–òß6«3®ÌBXmº“…#zÿ• ~fG¥dE²é§#ñmÖÕ0ä|Úm;TÊÛK~ƒªCiOÈ„ð­kZ"@ šú²üwÜÀrÚ–¹c ]_§ m>jÎÓúj=¢i˜‰.¼êšq Üœ}o8~ÍÎÉÔÕÙÒäÜewØRçjq}æ|‹5¦EÕ›«öØŽÏ`a© hq…zñ¹Xùn8õ5JUr›Ú)JPJR€R” ¥()Jœë´åÊó›v[7g_ƒá×OaÁ†îJV¹8òQ‘ÅB-ºSªv½AbŸ©§j- ^`‰•{ï¶­Ò[J2Žê³ç(>‡ðÍ\ºïG_/—È7kªE…øªB÷›je)JB$ crÂ@)’æFÒr‚0AÓÊÐF˜#¢áÕfæ0̸ò».iÆ•©—PêŠÈNpAÇÄP„Í#Õ«Œ§§X'j&íOáQúm(À÷QÞyϰzË­ЮØâʺ TNÐuäÜvÈ$ZÚR²öþ|„{ÓŠ°#è~¤Åe1áua¸±ÐOi–ôÛ-§$„‚¥’@ôä“ó$ó]Óív€§[êLTN]ÇÚNy²ò×á„}¤{a;ŸD‘ëë+[f–ê…ªùgª&ßÝ´¢ïo"]ëÄ6T©Œ„eÕgÌSð¯KΘêmÞñp¥&ßšµ•Å DKχl,Oäî§œüqVD­ ÔY¡„\zªÔÖ—WaÝ8ÊPµ²ê@QBÒ¬nBs‚×\ÇнE‰ßE»ªlÀŽô§åvÓŒ©[ΩÕà¸â•ëQåGÖ€€9fÖºk©-y7%êWM¡1ÝzáÜ|-w†pîó´…cpÇÕQU£zÃM]%Ý.•Vö":ì°õÿ¸…2”’àR{Çp)##ŒUÃ/§ºædImÌê<7äÉvž5Zyæ¼+áö‚B] ã~s¹ 8QçÓo]?êEâÏ:Ó;«Åq'Gr3Én0% IJ†w|‰ #½i±ë{ßTJt\««=’1˜ ܼ.íÏÉíîúDnÆ×1댟L×NŒXµÅ“ªÍþÊ»>±Kð‚uËÅmÚü^æß¤^ÜîF}3ò©ËFuV¡~÷¨ðmr$Df#¨‰§PP¤4·Vƒô¯,ç//ÐéÇÍnÑ}A¨cß%õ"ÒDxDiôê—VÒÔ~‰äå”N1ž=0-¦´wX&iëlËMÃR¦ÜüV†–oý´%•$§¼6¤``cåV~•b|N†ÙbÝ–òî êhÍÊSÎ÷V§EõeKÉÜwgœœÖu›§ýH´Z Ú õw·vã²Á¸çBBR2UŸ@+>סµSˆ–k–°¶Ü µuEÍ÷=ˆZ’ó‚h˜Fô¿± ¬c†ý߯šAã颿©”ãw¡"n’ð¶™^IM½`°TÂÀå¥;ž÷9)Ú5Ú'FõÐÇ$[áÂEÊ*šð½Ón–¿!Äg I%%*ÎßAù@«èÊWŸü:ÚÚÏ¿­?—›#¯|±ÅsN—7 5oe~Ç _¶û¥$þi°è~¢Ûm¶ög*CíAÖÈ•ui)Sª˜ ŽD°£Ë­1#ÔJ¹IÛ.鮘Õú{YkÇõ<õÏLÃÄM,³rµìRNpƒ)-Œ„îHP’«¢°oÿÉ/}©þðªXü)á*É7”eúZóû“_•¸¬leFtâ•K'd¯íFY{×u·Å3@)AJùùçŠR””¥¥Gu³Z‘á ½;-èªËÊyM¡£¸†”[J»‰P .m8øZ‹­þ¤Nì¦EÆÚÃ)uÆc-¨„¼¤· b°’³0ùH?X5¾5Õõ’íf.VÜYTªòÆ÷Qÿu'´úîÈQ6÷]’W’w%´#Ê y{ŠVJ†N2šèßS¦Ú„u=yC ¥¸Ó1–\L‚¤€9FÒÈ;¸Wl2à¬-ݵ×yýE¹J¬ þùÏÏ-Ë•:3BqS¥ D(톤†•‚KjXŒ<ÉÞ÷*Û‡qs«˜†ÔEÜînm×^(„9í²VÒÓ±9Fâ÷))PÁåD¦¥a.í¯ò9Φ[4ªÆîö³¶iý5l´›‚.k‹ ºÛHaÍΠ'¶S¹Ã{ŽRw`ðG¨í1}LŠó2$ÈR–\B\j0aÄ(-a!{*F̨¬«q>QQÑož²ï'_¨³)TÞ—½ëËíºuÂ×r»Î‰J nD®C½ˆ]´oJ‚”¸¹düS…g;=Qœ!{Zët·')mýŒB*Ú_™•“ÛPÜýÜNFw”°n)Þk¿ö!T¾âÓ¥@zs#¨.__N­aÿäœ )a 2þÆ÷! FV¬¨»•)XÚϪ½Z|Ü­tû âî®)JV²E)J\9î²¹®÷ÙQ-Œ•´”AüEêÓú«Ú¼`þ"ÇõiýUí_XÃýŒ;àOÚb”¥n1>qÑié²tmš>µÑwÉW¸ñCO,éÉÎÅINä6GXǨ9²$+CûaK]ÛÒhr*$Ä6 ‰*!›–ãÚ-÷Ÿc$<Ãäqô5( IéM¶æŸÑW¨—D¼×†xé©íöÕ½ ¨©M„€rIô®É_KÞzjõf»Ü.~Òž¢ú4ìç’¦—1çÂÐÙI“Áã8<ñWÕ(ŸîOhÓc¼Ã²i˳\³6ìd–…<”\ä­)ÔKàsÎ2Eaë&:@ö‘½µcÐW´ÝUo|B#L\{ýµvðTÞÝ·“_FR€¥u,¾œOÕwwõfšºÝÔ·ÛrÍXfHHl°ÒH m¹ È<úVôT{³ÿ¶ ¥¦2´ýÉ—û¶IqÂßuQ»@w7sÓÓã"¯jP+¼“¬¶ä½{T‡éV–¸’VyQ8oçšåô“ºrËýbºË³¦çusÁ&Ë)Ŷےr9SiFô›}GÅ_4 ()*ék.@wJèûÄ š.–÷êÓ³™i[¤­m„€JÏ'ìæ­--›…ÿTOŠ1ܘÊPãŒ-°²˜Íƒ·pñ‘Æx©]()JQ-i¦4þ ¹´õæÖÔ×ަ[+R¼¨Z’¥'ƒèJOýQò©maL†ãò;‰Z@Ú®”¸|E|"Ž7-dò÷ö¨ÉFYÇt6užÒ´ô ê_‘%'ÌIPÈ íR”¥ú)J ’Mu™¡4„Ä!lQÜB ¡%K KiK¨J!¼=V¤Œ*_ìç¿oî4ösßη÷á?„éŸé˽}K<å>$Nç¢tÊ|™ó¬qŸ•(­O:¢­Ë+i,«œñ–Ò”z¤cұ龅z2ã9¦¡–œJPâr¡ÜJ[KIJ°|ÉJ”€rzš›û9ïç[û=œ÷ó­ýÆ¥h­3Zœ²ë_QÎC‰§°X¬öÛÛs`Àm‡Ô×crTxh, à$+p3Rº×Ç‚ëruN „“Õ[ í9-…Äá°Õ#‰‹MÊùðÕŠëàÊÕä¥+¡JRºcIê~«NˆÑS5" ™ÞØí%Žðkzž}¶FTAÀÀOTv«ë†¢½Ä²¦Ï¤l±Ç´Ù‘ \nn’[e{ö§cC¶¥Œ-[€üÕg£np Ý ¹å 4ØŽà8Ć’ãkÁe*@?ÙZ_À- þEéÏ÷[?á¨i½Î_¿yœ%ž´oï¶çóiû­¼ªýÐÒ]SA% :RWuÛ€9˧ÖK5þt(¶èÓÒÛ)uvI`$qgpBÉ$6mb;ÔÄyûpíh*”¸í‰7P×h#½•Èú3Ù ì+g½»r}3Xs‘E•„ªÒix*¹‰Ô‹„“ôé¦Ú‡'r‚žž¤¾„!æZ^æûDƒ!'v’03á ©³}˜Ä¹6<;‰ŽÒ]üäV*pv‚[i!ÿ2Á8'n8¨çbeÐkðø¯©fÒ 5eÚé¦lSí,)·î7Wá­÷Y’T–“'*inìB,dãÊN2qX·-}>æ¬æ T†æÄŒêÖê+Ìäná[R’—ÊBÒ¢ ±‚ O9£v[{M*¥…Õ¹ •‡­Öç‘!AmniØÛa¦QÜRSǾ0ƒ´yUæÀɱ4•ÒMêÄÅÎL6¢wò¦Ûmòï“àI)NÕƒ™¤jF[ kajÑWšÈÛR”¬Ê┥¬ÿòKßj¼+:°oÿÉ/}©þðªSùß–^ Û‡ûXö£@)AJù‰íŠR””¥¥)@)JP R””¥¥)@)JP R””¥®÷Ù\×{‡ì¨–ÆJÚJ þ"ÇõiýUí^0cú´þªö¯¬aþÆ‹Àð'í1JR·ŠU3{ê-þß§Ù»Á»ÙgÜe¶ò¥Yœ³imk%jIRÇmIKK+N Öœl÷NSÝV»ZYZn–X)û”Èðd&à[g¶ÔÇ™õvhaJHß½JÚMnR£>Õu\9S×lf VÔÒX)˜[…M!Ån NÀô€R¥…rxÌž€R” ¥()JJR€R” æãì¶­«u W® €¯JÔÝñ¾Ÿ?Y¯Né*š; «SŠnég×se()»3câ£HkôÅ'õÝWW 4­M'BujE&¥l»ùš*ÁAÙ R•îš…*1ÔëÅÒŦ=£hJ)™µ#wqÆô‘Ê’ AøšˆÛz-)¸I•,¨ëŸ!p{³¾жÒß•]ל’.ì‘‘ŠÂU]™f–¥XkDµiU+½[’Ôù 6Ëzãª32Ò®A’|B–—–Q†ÞÚÚGhð6¨ïÅobkë‹×%6柌Ü!4Fîû@—vªjá¥}¾Ö3½!E;øI<’0aU‹2–¼v¯Š'´¨‚5LÇ:¦3àd³o_yÈOûÈS(snÎØKŠÉʸã ß/¬ÓOa^tå koW¥*L)JJR€R” ¥(*Ÿë‹sßÔ0‰©_²ír27®õ"R•·=J”9%–ð}r”Œàb¶ûYKl®áÔ×%O]ÞÚÔX¶mm1þóK–ÒJз2r…+Ýødäc4Ðêi¥(©M ¨ðIHÍqØc –üøÝålzgìÀ¯œSk¹.bïS`ÅŠã¡n7+]OjSM“•$¤/jT ³5wµ„Ûæ¦Í¯î½–u"#Ƹ\µ|´°¶ ¹·KeÔ9Èî-Dcœ€3Ž(MÏ KMVsŸÉòucÜmn\…)€XpaA (PôÁ I$``‚Àª¬"+Nê[ó.J¼Û™b-›[L}.²ä¶[t-+sw¸¥{¿xÅbÜ­·wfOT^¢Ä·¤\'4†.ºÚàÃÈKrÞiD¹Âv¡8>§ã@›Nèú"Õmƒk€Ì1ÒÔv2[NJˆ$’TIÉ*$’I9$’}k$´Ù^òÚ ˆqO<¸óT§µÃ‹b¿/NëýA)¦Í¡QîêéN2ÊžšZu*qa(Úê@* ƒŒhîv÷ãi½E2ãÕfÒô{L‡mé³ë©Ž<©)A(./dz ’p1@ÛnìúY1ã¤a,4<Ûø@÷¾mwJR”„¥! z0*†êMš\> =խܳÅE¦3Ý›¾°Ÿ*ZÞ ›!ÂTp„…dàa8'9Z ÇN¯´@‘¯.קݳÎ~àÅ¿XL‘·"*ZRHp,yXäàå\ ¹xR¾`Ò.smÖ9N©Âj3í0便ºœ‰ ¯(ÞÁ Ö ÿù%ïµ?Þªo\iw"È’ÝÕ¥´ÍÑ6É÷%«n@ü³çœp£è ®f_íW'ï6X2’üË_cÆ! á¢â‰JsèNr>U'VY'÷eàÍ´ðXšu¥M¤š¾O,ÒÏÞ×zâb PR¾hz"”¥¥)@)JP R””¥¥)@)JP R””¥¥)@+…û‡ì®k…û†¢[+iîî±²Û¯#MÉqÁtE¤\Z`™ áIl“‚±Û$¤ã‚È Ûå ¨šR[ºu-\FÍD‡M½Õ !Kl )¥ù\Êðõ)#9Ú oPtº=ÿ^GÖºÐ-(D8Åjs7º°ãŠˆNôr}p¦‰ÐKRbiKl©…Ø·$˹€T6C1¼5ƒŽp=TT>“J¦)SZ±VÊß•üí¿KÉùB.­i)5•ìõfòÿéCÞÚ¾øÍ‡St´Cº¿5q£HºSŠu xY+;^çÈ<˜ÝÈó$œ'$lbk+4©W8l©ã2Ù=¨R£”€âTë‰BŒòƒ¼üã#[IèS3¬F»Š*åMÒîóEGèR—ÂYdŒåÜn {Ê<à&·š¥lúŠå¨S>1éQQ °âûq¡²ã'g<©e-NygÔ°©‹mkEyOöŒ'£:u¤Þål½¨ü-­ŸžMÙÇdõŦYKòtæŸa3#¥ÜIÔÑÙ[@ Cˆ ãÌžF2 'kwê]ÞÕÑyж˜ }›ûA+mÔ¼´¯l¶|;¾ª qÈÍkjê^­ÒújÃi²I°¢³Rèñm)NnS‹Ï!Cì¬á®5¸hÖ/½g7¦-© ¨G 7uHÈÝŸE+ãò¯@ã‘6±uzõzñ+²hë,æ#8ИümSÄÇ«jVàB ±Áô§×ï­ò¬Ó¤ÃŸbÓm;×;õc-n-¸¦ÔB\m+r9HÎ28Á¨e»ªšÓP\¡Y®’´âáKÚØXsà•Ÿˆ ÉÔJÕÚZó6Ïe“`nn‹ijsr§È'$,û¨I?»u+VÙíW+…ãC[-É·&2ÞiýDÅ·Ý-6àRY-„î Îå‚IǦtìõÂé.-ÆE¯HÙ®‚Û É²‘UÆum²•,¤'8V¯¾Ýºu©5\çmŠ»ÆEm)–Èc-\S{†ìã>¼ÔgVu{]ÜôÅÚÙ2V˜TYpÝŽúZaa}µ¤¥[I_® ÅkêÞ®]t½áË]ÞÁ¦Úy°2¥j–ÚI%!XæP£€¤œ0ç9Æ]£¨ú®ãÙphË—ñ­)JÜ#°P㡵uóY\gIÔ/ZÖì]=vi¥BB’6(Ã'vT~ P‹o[nw+“¸ZÃ&äúËmÃoWÅ.•€INݾ£ª¶7Nª_íZ~-êå¥lqX~[ñ nê@Ú™q—Ûj[ o… §Ê³œŒddŠÝþ³u*݌Ľ,ae¶Âã¯vÔœ ùýp*EvÔ÷{N,šŠÖå¹7'.·U(¾‚YË“]+ÀÈ?Žh Ÿ«—ûÃU¯GY¥ÂX„ì¶uK.¶Ë¯8†Û í¶¥ ©i$úýF¥në…Ù¬·»–­¶³oMžS ¿à%*Zv;ÚÚ¼©¶ÕÇw‘´ðœŒž*›´õ/Wê‹Õ®Íz“`r—[rÔ"2´¹¹3˜RpJÏÄ…XÚ×H«[±¬¬,ºËq„ã/»¸†°ÃAjÀ÷l¸<G#Ôk¬æ©ÉÃm².høaç‹¥Kµ7%¬ø+çç>ǰ”\õö2\EÈSï1föÓ!‚•‰q°²K'vpÙ8ã!@‚FHï]é™R´û1î q½@ˮ۟íº[Ù¹²s”¯Ïî‘ê•‚0`³ú%oLÄ»ku(fÛ§—n´¶ûª$ÊY}Jyåé¹Üá#'€›oC,ÐÕ¤â™*z™É&¨­AÉ’Üì„@ízœ%#’Tª§Îc/ì®þµò¹Ò<'uçç{=ßí“[ø¨e}­«Ù]K u7IÌÓö{ê&-¸7IÂÞàJLY WmáŸ!òãâ<É9ÚwVÐÍnàµHi!)qÖXå§VÙ<BPHúˆôª¾A" %Ñ>rW*MÕ»…òCjP Jx%–SŽ]ÆãƒÊæ¤fë]ioéb`Øe[’ø\y³ Ìm´¡”ÉúBU‡ÛÏÔ•TFÊb±% G;«vÙßàhÒxmFïQÉë=«îçgß–ÌÕ•ìmº•oŸ>< $¹¥­D2B€QNTŽãdúœ(,œ|DVåbÖÞe¨1%&̃)ä¢fä·áÙd))9îpóÇ!…dy†yÔ]aŽ‹TÕÙ˜ŠÃí»†%\\ÛIK¥ lž?„¡kK9ÉABòF@ó_]­ íT˘IRê{×¥Eµ"K€íCŠ!A\ £Ðï},..Ê?gÄ¡OÍÅ$•¿sÛØ·ÔD¸2í–àóÈŒì„-à\wlÖÖâA {üÛ[;@I8LßEE•´»øqW-K…÷ÖÃ%)Ià…¨''hPczêüm9¨µ5²ðÜWźáÛˆ}-­QÓ ‡Ýݸãz{‹Û’²6ùO®‡WuÝ‚ŽÜtÙ´[å²”:à¼<—äNúÐm`8à+d,y7{rM ]$£”¬ïÛb*âœÓMyò‹ÚÌæ~Óúiêv’6X7wf®!o;j¶{UIkj¼LnÞþã'v>£’>Ý: Ĩ¤F–ÄY—•G‰YZÛ‡§YVÜV²–AçâȨû9Ñ8 J¹È€êÚ°»j²¶ë«ú5¸Ë‰[Ï(IS«à$œÒ:˜Ëû*ßçåo/+ÑÁrsRÙÕOÿÓšì»û«ZȰjF¢ rÔð™QY˜Ô„"ÒâœH<…Ò²\—q•o¶´Âå6‡d4ËXlŸ¤Y@àqÉ? Šô«§Ñ:øP2üf;Ï(ïHIw¸²žBRB› ŸCœžNϨÉö¿ûjþ!r‹›‚u™Íi(aiâ§$œ©îo&òßçܶÍê6ê óxqÆR­Ì(ã)”%Y+¼• ƒÊUÁÆk&Õw³ÜßíCq+w¶])-‘µÕ!C‘ï%Ä((z¤ã8È®Œé‹A°ˆ ! iCs‹Vå6âJ•“æWqJp¨ä©^bIÖE²Ël¶º…6àkµ¸©J$©j$’r¥-JR•ï,ò¢Hz}ÏV÷ݳâP¼Œý£pVà09ÿýÅsJUbE)JJR€R” ¥()J¤ë5Š^¦Ô°¬pf·D§ál}Æ{©FÆ®K9OÇ!$mEÇJ5—ºYoÓõ4 ÑâÞ`ÇjÞR÷Êm±æÏ+û*ÞÕºMê©‘æ^cÍ[ñðZ\kŒˆÅ$Ñ-<€ë£>¸Z‡¡­;}"Ñu—{7Õ©—›}°æ¢žâC¬- Ú§ˆ%*HPãÔ ¹ Ôº€Ü=So†ÄÐC[·©±Œ`«<úW²´EÚ^›D7xމìêv§™líµ4áògâ=j~zA¢ ”C7änR”B5Á É$œø’}+Ðô›E‰ŠX½v“+ÅŒ_§÷»}½å}íÄìòúúqé@VÉé^¡Ò· Uöá©`ÏŽÅâÜ…0ͼ6¥ošÊÝž0UŸì¯Gú]ÕÓg^íÚ’ ½…].M€P(¸HîÏÇÖ¬&úE¢[u§C7Õ)—[y¾æ¢ž°…¡XSÄd(8à€k—ºI¢ž}×–Õô-×Vêûz†{`­j*R¶¥à2TIµ§Ö}Ôö­%yºÈÕ¶é ‚ô•´‹`JœKh+)<3õÕ¹ûÓh¯& bò¸òœiÇ’»ìÕ)¥…¶wJJ€PÁàŒŠð“ѽ*;ѤǾ¼ËèSn¶æ£¸)+J† H/à‚8 о¨èk¶¶ê‹éµ^c[ $Bçz'{»Ü~V1ÈÆ;gô«¯Kô%ßDõV1º^¢ÜüuŽgo³³Ûí¿9äç;ÇÝS§K´•Îà«„á|vZ™C y7ùÍ©M¡JRRJ­dgÓqùÒ×ÒÝ#l¸¢ã7Ʀ!•°—•œâ’ÚÔ•- ­ã€Kh'»GÊ€¨4‡Fõ=ßJÚ.ñµeº;¡3% ®Ø¦Òâ‚IÏ$ŒÔæÁozÏÑ[MªKéô-S;Ž¥ŠEù)* øŒâ·qº7¡#0Ìxñï̲ÊÛM·¨î J@à8VÊÙÓ1n‰TÝ„HÒÓ1¸î^%8ßx?ß PS‡qîùÎs“ëš×E÷‰ž¸Ìe«ÍƒÀ\P%-K p¤+>vl‚Ùàå 'JôN]¹Ý+p™psÆ[š—àÛ–”©‡ €Û¬,`¡Cº HÀ9'‚ëÊ•Kø} ÞÞnŸŠ:gÊý*éº|æO-›µeor{UÊÇЩ–ÈÖõûH¹*˨ÄèxyInLBX* ônýó'×nFÒ™†é»ºý«®cÒ ÜŒuCRä-Kåk.RN Jp³“‚pFålWŒØè—L8¥%*ÆJO<ÿåU±Z.œ°ó%ë8´»ZhWåf’ÅFTëÎñ•¯–íe/ûÚÙkF+sìHÿÒ$þ’e=‰úDŸÒOì®KÑüá]è¡Òéq4Ô¬éÑ-°¤À"\¤¹>A Ë ­Â—+k9?/®²ý‰úDŸÒOì§£Øÿ»Ñ=.—MJÜû?ô‰?¤ŸÙObGþ‘'ô“û)èþ?ð®ôGK¥ÄÓR·>Äý"Oé'öV#±-Þ#ÚW.P—&;²O(mM¥g;p0]G_ÕEÉì{û«½Òéq0i[ŸbGþ‘'ô“û)ìHÿÒ$þ’e=ÇþÞˆét¸šjVçØ‘ÿ¤Iý$þÊ{?ô‰?¤ŸÙOGñÿ…w¡Òéq4Ô¬ø0í³]˜Ôyr”¨oøwÀÂö%xå<ð´óY^Äý"Oé'öSÑüá]èž—K‰¦¥n}‰úDŸÒOì§±#ÿH“úIý”ôøWz#¥Òâi©[+¾ |‰ÒeJKšS®¨HJA$àxèÍ¢#Ì¡ÖäÉ(ZB’rŸ•=ÇþÞ‰ét¸ššVçØ‘ÿ¤Iý$þÊ{?ô‰?¤ŸÙOGñÿ…w¢:].&š•¹ö$éI?²±SÚ«³–±.WŠm„HRxÆÅ)I;qê…qOG±ÿ…w¢z].&pç¸~ÊÝ{?ô‰?¤ŸÙCcŒF ‰?¤ŸÙPù=kÙ]è,].&tÄXþ­?ª½««( 2†’IHH'׊í_@£q‹Ü‘äÉݶ)JVÜÐs:¤t}³N3ÒÕÜ †B¯ÑZ+$•/›@çã8Ms¨òõ\}P¾™ÈnTg#„C¨Šmhm©È$»¸s0l>ç¯5rR€«n×þ¦Ý ªß'¤ÎÆe×î:Ö¡Šâд•“´(à ùZég¼u&ÆÄ¨0ºTüØë¸L”ÛÏߢ²²—ä8ð¬;›xQôÍZ´ )ëÓ½B» óÝ館óÜvÔ¦#¢ñ l©0æ *p¬)%AE +ÐsϺÂõÕKö’¼ØÓÒ^®0ˆ—ã…ÆÔ€¬cáœÕ¹J³]ï¨ëÝÖM§¥Ó¦Çžûoæ]êu¡A”6Sµ+pô`çwÇÉÁ'^ß.æeÛ¦· zfnlD»C’T© `î;œo=Ÿ†s»áж©@Vq5_TØŠË£å]¶Ò’ c|?6µ–×5í²%¶L~›Ü$Ob}ÆKì9v†Û!2Ÿqì%À┢é¡9äñŒ~”Q|¼u*ù,)})~¸Â–ã¬ß¢¼°–$¶ñ {xåCÖ¦:)'n—û¥ÂË2Ð&Êh²Ì·[ŠJBJ¾…Ť ‚99ãÓÒ¤Ô ¥(j®j"oòë5µ¬Y0›}Þâœq'ÂHý•àò‹_„T¨+Êéí·m(ÊìÖn?:n?:Ïöc_Ï=÷ÙOf5üóßxý•Ãú/¥?þäYçà`nWΛ•ó¬ÿf5üóßxý”öc_Ï=÷ÙOEô§á_܇?gŸ‰ýF·ˆÌÚy.‡%>€‘ÕYuÙògGb0iìܯ¶ùZ+äW­5't)JWFi"}]½j;Óû…ßJÛ½£weÈéeì¼¥o¶‡Ùd…¯kjZ°“ù9ô¾yÔzû­:Ž%­©6ëݘǜܷ}™¡.@žÙÜ„¬»¼-;‚I'í¯¬©X´ÛÛç/>÷Ôl„ã´âøß,šÜ×ûb·]?˜Ùê÷[×Úîé>Ö䨹yVÂøÁœº²GVzÉÛÉÓêß»ýîï>Ÿ?ã«é:V*Ék?‡Óy‰ª²¼‡R¿íë9q·%¾íĺA{ÔZ‹§ð.ú®Ýìë»ÎÈKÌxbaâWeâVÍ¥ ‰÷ª[JVÃJR€R” ¥()JJR€R” ¥()JJR€R” ¥()JJR€êêÐÓjqÅ¡©Dü­m³QXîoCfßs%ɰ¼td¶¬—#å#¸>¬©#í?mgMdÈ„üp­¥ÖÔ€~YªºÏÓV !غŽÖû_CÜiM‰ É,¨áH+mð¤ñäw“ÎrO$Y£N”¢õåb}¨b1&ë§]zjc®-ÉO2ÙFL…xY(<«RóϸGÇ#sULž“<ñJêZJÜî$¸Ð[N6#¶wùb‚¾ù ÜÖm¿¥VôÊŠýÅ«T°Èi“ -¥´¹)jm!jV¯œO' ó{’¥BËýOdÖâçÑÆ\ì³½(b”J”p ’IøWM9ËVž¶ÚÞd»#L-â0\(@IW9õÆkIÕF`9¤VõÒóm³Ã‹%‡Ý—p²Âv¸œ/rvî$$Ã’+6ݲ+B0ç“ÊûMÝ¢ñl»¡K¶ÍjHJB•°û¹$sò9J†=AÅŸ Ã+MÑÙ©mÔC— ¨åº\Sg¢9ãîõ抸£§r­¦*:ÑÓä«Í:;¥ZTÚ”ñZJ;˜vN2O¶3&tÒJ;g«ú”"I|®1Ò©Gé?=âݼ%|'rRFy8)Ín-:8tîª| Òß>$öÔä7ƒÈJŠJ€8È$~<‚+Ò$†&Df\WôwÛK8ƒ”­* â9ªõôÍÛŒ9©ë€JãÎT‘¾áÍ¡JA!!NíÏ“)Ï989¶:Q'N n§/ö;ÔKz#&ÔókeN! ‘äR€Y(ŒçÍŸL\›Í«S£Þ»ács'PYc]ŦEÊ;SJB»JV1HúBT@'$k(φ!34ÉhF|¶p« Yp„ þ‘R@ù’*/¨ôt»½ýÉ©¹0ÄWÂRò Þ¤”©µ°ÛˆP'øÄ(§*ÁR˜›} “ãØ’ôû2Ûa˜­%k Jƒ*Ž­ªÂ¼É&9àç|co0å5¸Ù 8y%yÛÜX––cZý£pvr 79èyhíìRÒÓ)G'’T‘NT+lèy¤ºÙ% ‚?î5Uþôn Š¥›•µÔ©1»½oîwK.2°•(“ÙÆæä2r$ôµç­ó!9>Øêd4Ú{®ÁYxlìá½áÁôYh«inY$S^| tpïÿgÀ³ëŹQÜ–ôVÝJŸd%N z¤+;söàýÕ‡¥­®Ùôì SòQ%ÈŒ%¢ê %Xá á#꥿黵Â}äF¸Äí 1ÜÊ”ëe)X óŒ##96í°­Aɧ,¸ûþ†ûPCMÂÃp€§Ó2bºÉtŒ†ÂFâ23ŒçÔW…‚íl™4xSCêðéZmHÞ€çO2~ú¬î=6¹›ü&QoµÉ‚âÒ㮨Kê–Û+Ü€°´•c¼c>_IÖ´1rˆ–ÄeÄÜà 0ÎÅ (e?BV>JÚyÛÎóàZæ0öIÔøƒ«CM©Ç„¥ð°lw›uî1“l}o21ç,­äd¸ Œ|Eq Ú¨Úi‹9¸¦¡¦7tîó»¼ÊR¾¾TOÌŸZ‡Aéì”Ø`ÚeOŒÚc8„º¨ét™1Ë{e]Å« X á8¤qY7-ȯRiëJÜ ´pºUÆ5É—œT$FTt ”vžt)[³ùåI#š…Eé+A‡ØŸ2 æ×¿Ød©Å–ÝB^p”©Üº X’_@œ˜=.a•—\zÞ§Ñ1™}0€q |JŽsï’ÞáŽû*§ÀÜéaÒkœøEa»t·µsnÚä¶Ó1Í»'Ìw%Å'ï ºÔ5YYú>cØÕÂám”ë±d¢:›·%´ÇêJd%á+IcvSƒ•zädûÉéCÏ;5i»C`¾´+¸Ô"öÔLNçÎÿ¤R¼X x Õ\ ªi§¡5 3Ñû‚3i¸- жSŒ¶ÙZAí„ä¤wr›Jä—n’†.)•„`óœUE§¬}B¶»âÜáà-^Ío¶â‹~&3…Ä…¨¤»ØSˆ`ù˜ç… ꨳYðrj2´’í6gIk›]È]dIå´•%ËËê mIWmEA[†×Ñ‚ HØFprvÚOë+V L›åÌ̈«k,º¸¼ÿÓ¥¦R¢„R%DÙϘ¤kd³ÔÇírÖ·å)Å¢C-D[QJVÙˆùikÂVÌ• m¬r×S Û_orèì„7 ÇS鈤-erç ·°'Êr7dnÆ Ës,Ï^iÞp»È¶j¥ý×ͼ÷î|Ô G¶‹£Ë“nKpŠVD•ñÀk!gqòùH<ðA«RN1¦^”ì§œ)çBB–~d$ýÀUKû³U~æýN™Î<ÔRäúÙl-Ä£ÇGÜR’@R€Î ñµ`ñÙòF¾ƒ¨¡êYiÓm5£¤§näÇq¦V[ºíRv¥_Fv¯vߢþ Žæ3”àÛ·&oz.‡µ9×À…ºéE³Êó‰a( ¡I!j ¤9%JNJAÀ¾þ»yyeýgyaHQb=Å/÷€Ý˸…vŽIþ+ JBžÜ•¼çÅ‘¦å;!ù“QßäϚؘÔI·Ël¦>Iqldî’#à©*!³î©ÎÐhnZçÙvرæé èšÊäÅq•-,8‡X[ Z[ÞÝ!IR¢RCn'é_Ü—z|Ñö{”kM¶ÆÔ~â< ±€QbÚv%{•æ(+#9É'$ÎÓ™éõÎäÓW)zºr£Z£²ÚCÎ?´ËÊu(lû lIY ’ +BôOîQDÚZ*lÑ%3ž¥L Æó•¶‘gXØJÊ ¶ž7-#8¡áݳ>®¥k•r“Ÿ£±Üœ0YO÷œµsu¥²R Ѧ©æS ¥,¸}Ëj)JÏ Jˆ$ð2H­J´[QÞú™®UQÚ)÷4»Ú·Ä‘º´4ÚœqiB ”¥êI­Õ-L;lÉ÷ŸL>Óa¨Ø>Š8R—ó-wǧ :îÝÞðè›{ÒÏ©¤/1­ïLd¡° u)QBÜÈÕAyJÜþ±%wˆ3,Íg‡¦vû_jœmjB9ÀÊrH&­s´){Jïޣ߿ÜÖ|QqajËÙ³êM7ÜÎ`#S=1§î/ZáÆNwÅŠ•¾µœ`}2¶>8 çÏÏp$€AÇê®B’’# CUíê;®j jvÝ«#6§ÉmËcôöRZÒ ÐÆw¨ŸÊ«ŒÅ8Y¨®eõï}盋Äôx§kÝùÜË#8ÈÏÊ€ƒœOZ®&G…. ÕcÕí¢ÒÇi‡Q°ã½“¹*⬴ O*ôósæ¨,¦mÖ]BÊ`OŒÎ{O,&l+ÊÚ¼„’> «’}STÞ=ßÙËßô)½&Óö2í~ýÅ—JÐXn³§Ý_Žõ®á3) iÉøÑ€0rŽ=sï~ÊÓu².š¸èøö­XíÄ[fÝa3ÙƒïÉ{¾…4É8$%n% RÒO­]§R5âz4kF¬u£°œR¾pŽÿNÕ¦mW6úÔV-ªh?jæ§lU e.©jl†Ô²Ü^=w„Ž+'Eê>˜²›¥ÖÛªµÅé›ä0Ûñ¤8½‰m×@u GÒÈeY+ú@N|ê §ÐÔ¯›'»Òé °êç]¡WK¤Évô´âKŽ:ćXZÊPÑZR;ë <€)7—Mn6›®ƒ³M±Ü%ÜmÊŒ”1*_ñ¯y ”p$¤òÓêž $4¥()JJR€R” ¥()JJR€R” ¥()JJR€R” ¥()JJR€R” ¥()JJR€R” ¥()JJR€R” ¥()JJR€R” ¥()J_-HÓF}G!l^Ÿ%‡„ÈeЦTו/yà_ÊÜ6}KJóqú68ÉÂNVÕìÏ4þG•ÑqÆW£YÊÜÛ½¬ó‹÷{+5Ö|qÓÍ)ÕîßEÆ×¨š[И+}·JP¢ã;AòúŒ(ãñæ­™iý}BjtÇæ7¨Ž[…:C.!=Ãßí/ á;›Ï ôøüozW•ŽäÍ<\lê5—×õØ{:z£Ó:n–—Ÿªá]U±ûyÝç÷Þ[2F¢3Ô-$ºH 8Àª×÷^—Çî}¿˜®Ej@“ní.VÎÊUãã௹äÚ®ï.3ž*Ù¨wZ´2z“Ó+¾Š]ÌÛdzü(1Ý-öÞC¾æäç;1ê=kØÑxèì<,dä ­wµ‘Rzòr>"êsZÊJfÜïšçC\$¾óéX¶)eQÓ=-ºágʇT$/´VAÏ—°¼u“x×Z¢ÄÓ· K£m†˜qT&a³°3¥J_‘=´¥ÎÉ8Så!YSvoü†àœ‰?îtÿ÷©ÿ!¸ç"Oû¡?ýê¾`W÷Ek{m¾m†_PtÜ–S ˜J E—ÐÄY! ¹»x)9Yo¶·Tà ¥v¿îl"ݦ£œ[ƒÔ‰-¶¸ÑÔ>‰6U6­¨)h FÐR²•¤w ê?ä7üäIÿt'ÿ½VßJzCôéÝ ­[>R½¶»¼yÑ¢´ÊÛZ£¥ŠmÎën'hVw$0àƒM„«_2\Ìëî oÄ+L^[eìª+2æ³)o>U:[ZŸÊ“ê…6@* )*­Ý¿Nmi¥\¥"Cì#&3ˆñàZʶ«T %5¯üÔ;²:¡«1œàŵüljýD­õ ëø!¨öã÷ÓÕ¹ÛŒ˜–¿\ŸÄþdì­ŽqÕqŒR¾Mçwïmü,ŒëTuV«öxnó×´voƒr·ãẠóý¸v¸ì_ ¹[Àøí‚¼ãê˧Ÿì5¢üÔ;³ûèêÌg8ð¶¼cp8üOåÇöüë¯à†£ÛßOVçÏ„µþn3øŸÏŸÿ[™Þþ¥>/û¥õ6ö:ŲáââÍ”Ú T°Ú#¬ŸÊ(J@ÝþäüsUMÆ{‘:‘rhhΣ¶‡$É[“mkXŽú’†’Ñ Ú”©*Þî2£´µùAyU³M^â\Z•#¨Z–{-¯r¢¿Þ–Ü>Uâ¥xä¾µ&§1NÖ±Н‡»§7žNí»®ܧãÜ•rÔV-=ÙÕArŠWs’¥2Ëi1]•¹ÅÁYSˆkj€ÂÆÂ•Fc\uQ–ë_¨H¹:€©K}m%´¶“â À[‹ Þ‚¸ ’•¤ãèjT<=7¸±)‹ŽÉüðâWQ–'눞.׫cÜmÜgÆÌlxIa—Œ••IqaJt2•çžjÇ¥+d!{(­_V»N£½•—fß™TÝ_ê5®ÛÙø_¦W¨Ö˳L·’ÓHmU·fõGHî nÜãYP!¥îzÎmæD+ö¡Ñ޹oel¼…<‡ö6•/µ‚SëoiRæ_§q¹R-¶¨‘¨eéÙ7yóâ"Àêc)Îë‘tolð9Q;°9ÆT”åB³™}µ;Ñ[àuIyN.EÆJ.Èa.„mA­¸µchPiH÷OIt{ž¨:þíÏPY¥¾å½j„¨³ãº …çò”:¤6‚ŠŽÐZБ½ *N=l»Q€mì&ÖcM§´È·¶”§Ëµ;xc˜ÅPú–à ÅëÕ¿¦3/.‘»ÉrËs–È}¹!D, % ^ðÚÔœ6æô ”›[¤p¢Ûz{lÀ­>Ãä"Ü®áìaåð žuï78JéJP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@)JP R””¥¥)@jãÚœkVͽøŸ£“ SMS Server Tools 3
                    Provider Queues        
SMS Files

      SMSD

checks blacklist

sorts messages by provider name

creates statistics

writes logfile

displays modem status

runs exernal programs

Vodafone
smsd
modem handler
Modem1
 

Outgoing Queue

 

Telia
                 
    Sent Folder

 

Cellnet
smsd
modem handler
Modem2
                   
    Failed Folder

 

One 2 One
smsd
modem handler
Modem3
                   
    Incoming Folder

 

Other
smsd
modem handler
Modem4
        | | | | |              
        Modem
Status Monitor


Blacklist
Statistics
Logfile

Eventhandler

             
smstools-3.1.15/doc/slideshow/page3.html000077500000000000000000000072371223712572200201360ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Creating a Short Message

  • Sending SM starts by creating a SMS File.

SMS File

 

Outgoing Queue

  • The SMS File is a simple text file that contains the destination phone number and the message text.

  • After writing the text, you need to store that file in the Outgoing Queue folder. Any filename is Ok.

  • The Outgoing Queue allows you to create many files without waiting for the slow modems.



smstools-3.1.15/doc/slideshow/page4.html000077500000000000000000000110271223712572200201270ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Message validating

  • The next step is to validate the SMS File.

Outgoing Folder

 

SMSD  

Provider Queue
Vodafone
 
Failed Folder

 

|
Blacklist
  • SMSD checks the Outgoing Folder for new files every few seconds.

  • If the destination number is blacklisted, then the file gets moved into the Failed Folder.

  • By default, SMSD moves all succesfully checked messages into another queue folder with the name "checked".

  • But if you use the provider-sorting feature, then the program sorts messages by provider name into one or more queue directories with the name of that provider. The picture above shows this.



smstools-3.1.15/doc/slideshow/page5.html000077500000000000000000000100031223712572200201210ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Sending a message

  • Now the message will be sent.

Provider Queue
Vodafone
 

 SMSD 
modem handler
Modem 1
Failed Folder
 

  • Each modem can be attached to one or many Provider Queue folders.

  • SMSD sub process controls the modem to send short messages.

  • If sending fails, SMSD will try a second time. If it fails again, the SMS file will be moved into the Failed Folder.

  • If sending fails 3x 2 times, then the program thinks that the modem is broken and disables it for a while.



smstools-3.1.15/doc/slideshow/page6.html000077500000000000000000000072571223712572200201430ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Receiving messages

  • Now about how SMSD receives messages.

Incoming Folder
SMSD
modem handler
Modem 1
  • First the modem receives the incoming message and stores it temporarily in its internal memory.

  • Then SMSD downloads the message from the modem.

  • Messages are stored in text files with random filenames in the Incoming Folder.



smstools-3.1.15/doc/slideshow/page7.html000077500000000000000000000116131223712572200201330ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Other functions

SMSD

| | | |
Modem
Status Monitor


Statistics
Logfile

Eventhandler

SMSD provides some more functions. Key features are:

  • Status Monitor. The Status Monitor shows you what your modems are doing. In this example Modem1 and Modem3 are receiving messages while Modem2 is sending and Modem4 is idle. Receiving means, that the modem is checked for received SM by running Getsms.

  • Statistics. SMSD collects statistic data that tell you how many SMS were sent, received and rejected. Additionally the statistic files give information about the usage of each modem. When you quit or kill SMSD, it does not loose statistics.

  • Logfile. The Log file tells you what is going on. It is specially useful for troubleshooting. SMSD can also use the syslog or eventlog feature of the operating system.

  • Eventhandler. Eventhandlers are external programs that can be used to enhance SMSD. You can run external programs to validate messages or to perform additional tasks during sending and receiving.

  • Alarmhandler. In case of problems the program generates alarm messages in the logfile and it can call an optional script or program. You can use this feature to generate alarms wherever you want.



smstools-3.1.15/doc/slideshow/page8.html000077500000000000000000000101641223712572200201340ustar00rootroot00000000000000 SMS Server Tools 3
SMS Server Tools 3

Final words

  • SMSD comes with some sample shell scripts (Eventhandler):

    • email2sms gateway
    • sms2email gateway
    • logging and storing into mysql database
    • Auto-responder
    • SQL database queries via SMS
    • sms2html converter

  • SMSD supports text messages in GSM, ISO8859-15, UTF-8 and UCS2 (Unicode) alphabet. It supports also binary messages like ringtones, operator logos and WAP Push.

  • SMSD can be used with up to 64 modems and it can run on a set of networked computers for redundancy.

  • SMSD runs on Windows, GNU/Linux, Solaris, MacOS, FreeBSD and possibly on all other Unix-style operating systems.

  • That's it. If you have further questions, please take a look at the SMSTools3 Community.

  • Stay in touch. Bookmark the http://smstools3.kekekasvi.com.



smstools-3.1.15/doc/slideshow/queue.gif000077500000000000000000000010361223712572200200530ustar00rootroot00000000000000GIF89aÈ$€ÿÿÿ,È$þŒ©Ëí£œ´Ú Þ¼{€|◌榜gªŽKë²°÷€—mà•ð“ï“1Á`¤H¬%ÆLi‚2¥ÓÛ‹šê©²Ó·»}>½â(9š=WÇat£©\ïÊs:ehýÖí?¥:gÖ&×£µÂòGÈfèÈ8˜§h¡3ùÆ'¤w·ÙשùyWÙÈYêy š*êÔ:Êp»ê ©jËJ ‹[«ë0){Å»ÜY|ˆE¹ç‹ù+u\ ) ˜™Ë<ûê¼M:Ì]Ý-,þI­ìÝŒ9ƒbY8sŽ¸Ž˜&N(?O¿n_üLÅžpè²YëUpÜ,|Êâíãáð݆ý]¡xÑâ7þSà–ñ»Ö‘cÂrÉúÑ eʇú$¶„Á/"L$7¢úˆÐäÁ]8yö´ù3ÝB•2½@t7ó¥Q¥[hªÃè5#‘å!—‰*V_E›:rù5é"°a—2]á4ªP‘Zƒ¬zªmÈ›p½uE[Ök^¼cÅî5©Ù¾ƒß² ˆE®\· ëÎÝù­­âÄ%+_5|S2åËAvFøÙçV9š936f™ ãŸZK«>mõ5ìT‹Gwtíqö´Ô¹u‡öݱéɦ#)n;8gâ¯;O–¼ÝòÍÍy‹\ýï2îëÆµ­ýý½û/|(˜×Pâ<õ2È¿`ßž!†ùôëÛ¿??†;smstools-3.1.15/doc/slideshow/sms.gif000077500000000000000000000012111223712572200175240ustar00rootroot00000000000000GIF89af]€ÿÿÿ,f]þŒ©Ëí£œ´Ú‹³Þ¼û†âH–扦êÊÀ xqð"3|φ^ïúîËâ¦vó%bIZ“ TÒ\Ñjäs{SÛ´Ùµ†‰X ³—¥RÒÏ.Øû­–ÕÖyò.¯ØÍQ£3›6¶'våf±—Ø€ÈC–×ç…׸FˆF)©Æ%8ÔÉF&¥%ÖrŠšªºÊÚêú »†#!Š–sëâY[jÊáÇ€Ô7úöXhü¬ÁYy9G‡Dý Ò¦`Ù\§ýLÈÙ½4{i]üǵþ»üÍÍ'iÌ©œÁ|>œ©«© °.v± <ˆ0¡Â… :|1¢Ä‰+Z¼ˆ #þøZR´ÇWi…¢çÉÆ:ÞŠ€Êìì}¾•õÎ;Óeò†ßŽÅpédk}=ee¼ÁúÉú‰C¯¸ÈÌ‘ä gLMït¥³îû­)¾§ôÛtõ¾Ç~žçµ?NØ8Ï\gø:M}jZf¥å›²¢g±@‘¤jYE@ä’m{ß¹!îž”ì»2ŒESv¸×GjzXéåg1Ó2ºôý‹Ÿõ\üçÜ`Aë­Þ×µÓº]Û(c ï“Dx Û?Þ²/•wþ*iÀœá¿nûO]7Ò½UžÙõvõš’(ÑbÈa¡`J&|þÞΡ[O”½Knº +¬Ã[O¦qGoL8‘†@~:òôg;†ß¾l£é¿S·¥½Ó¿·îG:v둜vÎ23ýúìðfá~ä ¾;äj(¨)êj=Ø.9&Ì+–> ,að:C÷tëgcââæu xx¶®]Clª2òp^EW^æHûRÔ³*b÷ýjÃô‰ËÛ¿|=îß¼+â¸ÉK$OP)Ò'@áˆÂ© #Æ~æÉ#Z~¡·UÛepæàܶ'†;•,q$Kq’hâ/Ôø%C’3‘2È?/ñ6ÒÙwIë¶¡†«¡•~¢Y{uÏ_í±ŽÇãûõÓçŸU¿8ª÷µ(ªâ¤ª­Ž3 ²©)Þ9RUVÇ @¤Œã9ÁÆåfôÌ®¾™­ðÇ.’¤Ž<¤ v`TµW[PLÒ™nå/R× ®ûžŽøe´Ç “µ@£·Œ$}»ž¥;xêß·íãWϤÞLÜ[ûeÏ&ꨊ®áM]$¥!XŒ‰ÕUr;‘àxÎIªý9n{]¦Jþä«Wé•2 ¨c1A0›±hYðÍÜôu8pÃ=KÜ–}­À¼|Ý6kL­n¥"qEC1–Y"O¾BÅA%2|à@'Á³w=Ë._¦®%×qèµµP óNÉ‘ãSæ"“dóòÔn~µqèÒµÿ×èÿ¬ÿÁÃôÏûgêOì~]?§ëù}¿?>5Ýݾ¨(¬Ûiîz-¡-h¿¥O¹M%ÀBi^DaØFÝÁfl/€ œ Ëv_ZW[~ŽØ•È~` a´`¸§|M¸™¦#F–›Ï«{M%U¶¦—`_žÅ[ÿO«‘`vêýeöIzŒÎ/“ƒ×ä̸‹h÷ßqÚ·š—jÕXFgY®©8*¥ÄÎ[¢€±”`HûÇ‘ã(ÉíN­‹Žr.Ù…uŸ<|:$­{ÅeoÛcÕÉ£Keª€ü{pÝÕ[ X#Šá ¾Š$º«Š‰YI;7¶ aAÏVì\$sk}XÞ¨­Ð\«8‚áMC?_f¦k“¤Rv—«|€HÇÈó©)Ù=mب²$?ëO0 ›~}¿A¬|M¿¯ûÓK£Kï úž´í[¾ÝŽ=«W_l¼Z©®QõkÐG+0+íu*Ϊ¹Çp 8Èù×·Õ%g Z6ü›óAk»ÉÒWÖJ#™–\*IìõêcîqØH~ÐXdýºJvX{Bð³òLÊø}æGÓÏÚ³ñæ&­žd}æœou~>8Ü£Ùú/ÿ¾Osûo³û>ÿ?åçOçåÏQÐï6ÙÒî·Å84¿EAþïÜüºuü<üÿó§®c¨…dBxÒg¼Ñ×uJ*€½ãñÿ¦®»—•gÑ˵wÝÏNÛÝÐ1• “ü¿o3;¥d©• ‘$ cx"ñ»î[…wÐf¿¯»õR‰{Ê¿O}Ÿ‡OóóXz£9‹œ âíÁfÛ>¢ýq¯£ZœGUì€Ú8Õ0Ž]‹#ä`cíÆrqÂÛÞ¨jj7Å£mnN5¸Ø?Rž8VYkKHžãtGöÞ$Ê÷ðN|ÄdŒ;vßVÏCnÀüœT’O-ʰÏZÚž$Ó!¨ï'Ü«lÜkº/Ù½Šê=]M4½Ct‘!vVêKrz ¨¥ßW±·8Öã6ʉ ib­+#ûmÑßÛHŸ ß ùIÁ8<ÿ"ì¾DÚ× §S·®»f¾EWª÷+#£‚ˆQeÀÁÏÝœ`e¸ý¥Õ-2d^²=0TŸ™ÊHò³0géù ßC Ô—Ò?#î}󴫦ݗÔk£¹Is{ÅÖ1D.#U,Ç8Ï^ÚFý?ò=?ñÃqÍk–é$—ÓK :L" í lK9¨ ð§ÎIúßV7ª+t*Î ¸SPÏ×Ù©šäé‡eêÆŸ 1ò<ë§ÖûG?/©ßl#ÓæTQD€4#ô)vòPr;¦—JGpOøê¾å-‹±¬×[—.×Ú*ÚéA¨©ž”Í4Ž©‹ÄAº~H Yˆ°âvîV>]Á“`]b¥QJ†¡#Œ‚F¼ƒÞ©·”ºŽ&?â¨2sžÝå¹cÞ×H䆺ª5´”¸’ d1– ‚ûˆû¾Tdà¹tuÉQKà`:çIu)¸sŸ1SoK\4V«6ßžšžNÕrżªÎŠH ÌNNB€p¦BŒKh¦–+eÛØ þôkT–ÕJÈÜÿz•zÝ7ƒ ënTÖË=¶™à’’9=Òýp Pqñœœe‰,¿$o fÃÙUû²ñ\ô4>ß»*+J{ȱŒe.3’1ãÉÏ‹‹œv}Výâ«ÞÔ¢«Š’ª¶8Ì2ʤ§xåIU[@%’3Œç5~áÈéÙÝÊ×U‡ Î’Fµ ÌøüïÜîŸh:Ùz¨9’ɲù·…¦äËDVêûuUU<³D‚fŽ'xeUb ’ŒWî%IÈùej·oÛÑ–ìµÝ+e«Ž‚²žšÊrb…g¥eŒ’cŒçxæËm®[&ìJ{k£Å%$J¥ÑóÝJ U²r1ç':ìŽÜKÂ[‹cÔ×ÒÃp¹Ëõ)(F0££ÄÊ„øb²lxì|6<ÛFoKéØöì µ¸‹}ÉD 'q><ÇßëQø»’xÁƒýj·ÚV­í|àjZí—nÛgµeÊi¡ž.µ}¼¾ c2 Aäë…Ìö5°q‡ÐEt¢ºD¢âE]ᑌ‘3„`NB±+Ÿß¯ÀøsÃüÛO²çØÂýcýL¥ HlKî¿ØïùŒüÿOèîžÞ·~5Ú6*Ûg¹afsPÒ fÑð˜FÈ0ÉÆ| àt1úÖ>z]|«| ë †¶à3'”ß¿¶¦Û#‰˜÷Vžïá›&íû\×*»•4ôïµô’"†÷:g·dlã Æ1ò~t½òq¥ãNfß´U–ú衽X¦‚”¢$’¦(ËI—#)î A+œ@0Í“´öXã™ °ýŽ©V\'{ä+ý«rmÚº8ê)èÍ\ur²©Erñ” æI;dþËóª_guĵ’¸Bé8°t¤ÿ²¿º‘‘h‘ÉêƒÜ¶t¶úV°Tý,”óÖ_„ó –Ž #àüGŒx#÷ÉîÞìÛrpÞÞ¡¼]¶žÃ¥šŽ¦ªªjyœŠvŒÌ (ÌHºøð5rò Ü·GÛöš²*Z«]E<Ôæ°L±DÑufQöž®[!NJã9Uïˆy¶ïµ(öÎûdžÏ@ñ½57R½41¡î°8V#É?>|êÏ×±rí¥ÖÈKl/;ž`’x㨘ÖÌF©-i”Ä¡\F¶¥«–¸¾ß\u°Ãlµ$51ã¤è*\,‹‚F ŒóòtÅîN±îÛíyµeÊ•¹!Eˆ!onF‘K…‰ËpÃÆ>>u]nNÝõ»Ÿc^éªè$¡Û”4R‰‹¤Ò yY‰T ÃÊ‘€[çùÓI·cx­Fà† 25WëÝp x…zYQ•ˆÑÛ:ÔŠ}«[nCÞ½lôÏIoŠ9e\'›×ý¼jïÇÿæ.œí.ˉw<ž¦Ÿ’cžÜö‰™1 ÈÿP1F ò½:þC?—Çõñ®wkfØÅøÏY‚ó±qDû±ˆ“[_RÜcê*êý¥öþ ÝÿÝ›_¨hcƒÔOˆ”(j[{~çëeÕ“êC‡·vîÞ;zm[¾ž¦ÝG* ÀÆñJÒ$ŠB°l—9 u–p £‰ycpr ‡roKݲ´ÚÞ¯d8Š) ŠV$S–'É9³çjåÓz—O\lkï£Ó²èTÏ.FbŸÅGty"<‘\Ž eõ?»ÑÆU¦¯ÈÿÎ&¸4H±rç2Fƒ ¶ëêýÚžÝx[•vç#Ý·nÌ¿ZiÊieI%BdU•ÄŒŒ¯†ñ|…ÆHÙ\)¿Vï»î—ë…º¢é¸-•”Á⧨Ë4Žz/AØ §ò?Ê~«ÓTÝÊ(CÛ´¡wÊT‰‘çóZ‹o¥Ô[„¸õù+„.özߤ­†ôÕtnÃ1´«(Wñž¤; ààã©ð¼×n=•o¥Ù|DZÿ]Û4Ó ¤«§‘áhúƒ匪¾#YHŠN®{Ä`j[IéëxÉÅ›>Zë\w(ï"å «HÐH¢!BÝ)Ác§àß#Ÿ¸ø£œoûVßµ/û%M¢ÙíŠJ~…}¿n3}ëc…$y'?¿9:·O¿—q®e[6Â`òW]šÛ®äý4>þkÛ…¦cþȦ‡†#´ V‰ìK,VÙ¨`’–N^(ŒjQ[Éòù?âu¸stTÞ ‰çÛ˜Œ`Ÿ×}(¨þËÛþ½?ëý5+á«FÜÙ–û=C™ 4TâB½{ô@½±“Œã8ÉÕê ƒ÷Íç—kwæÒ¾RRýbDTûÒÁ=;¤+ È­UÎAûˆÇŒŸ=è¶z^VnBf¸ Uø3ÓÈâLlêNô}÷RîUGæ¢ÝdµìoT›5ö•,vˆê… KÇF=¤ËÔÉ€«€¡‘B0NAÉËk¬’{|·Ë 'IÍ›„ùF³}YoÛ³p%Ì[ç‰ý٪穘$oÝc_qF¶|ÄàüÑM,VÊxÝpÊ€Kîü¬{©‰fÕñu­¤3 úëfµÇR9"MuX<ŒëN‰ŸÅ5K©5¹QŒ`Èð4hÑEc¶p3­]TŸ*?4QYEQð –¶Ñ£E£"çñÆ²Š£àþZ4h¢°È¹üGñ¬…^¸ê?4QYPð1¡€#ÈÎ( 05¡DÏâ¿Æ(­º¯\uƶ(£X`ÈFPÀ‚´™üWøÑ£E³*‘åAÿ-aAð£øÑ£E«“àÔ¢gñ_ãFVê40yÿŽ(­&þ5é£FŠ+ÿÙsmstools-3.1.15/doc/slideshow/statistic.gif000077500000000000000000000007111223712572200207350ustar00rootroot00000000000000GIF89a`@³ï!ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ,`@þÈI«½8ëÍ»ÿ`(Ž@žhš™jëŽì+Ï+mßRŒï­Îÿ0pˆX„Ä$A`<*ŸÌæù¤E¥µê-úPµ'®è ‰IäòæŒJ«-l•û½dÒ¼t7×îw}J°hRµRÓV 8Ÿ»À·´z°+`4>}Àࣖ[EïÄÃö7 ÛöºW¨mjj;¡kwr^o7e$ƒijulgsBRd}ŽLt/vJƒ„ab‡ˆ”›‹—FGCT¥¢I.]AQ§8©H«?U®°f©´µ­:¯°·¿µ1ÆÇÈ;smstools-3.1.15/doc/statusmonitor.html000077500000000000000000000076261223712572200200730ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Status monitor

This function is disabled by default. You need to install the OSSP mm Shared Memory Library, also called mm or libmm. Then you need to enable statistics in src/Makefile and recompile the source code.

The status monitor is a text that smsd writes every second to stdout. This is normally your console but you can redirect it to any device or file.

The status monitor shows you what your modems are actually doing. Each character represents one modem. The first one is the first modem in your config file.

The status may be:

s = sending
r = receiving (or checking received SM)
i = idle
b = blocked (after multiple errors)
- = not configured

Example:

iiiriisssisss---
iiiriisssisss---
rriiiiissisis---
rriiiiiiiiiii---
iiiiiiiiiiiir---

Run smsd with the option -s if you want the status monitor. This works only if you run the program in a shell window. Do not use -s if you run the program in background.

Statistics file

This function is disabled by default. You need to install the OSSP mm Shared Memory Library, also called mm or libmm. Then you need to enable this feature in src/Makefile and recompile the source code.

Smsd collects statisic data and writes them into files. The program generates one file every hour or whatever interval you configured. The filenames are simple timestamps in the format YYMMDD.hhmmss.

The statistic files contain two parts, delimited by an empty line. The first part has global counters and the second part has individual counters for each modem.

Global statistic data:

  • time since the last statistic file
  • counter of rejected SM

Statistic data for each modem:

  • counter of successful sent SM
  • counter of failed SM (sending)
  • counter of received SM
  • counter of multiple consecutive failures
  • time of modem usage by sending SM
  • time of modem usage by receiving SM

Example file with name "010811.150030" , taken from a system with 2 modems:

runtime,rejected
1200,1
 
name,succeeded,failed,received,multiple_failed,usage_s,usage_r
GSM1,20,0,1,0,80,0
GSM2,5,0,1,0,40,900

In the future versions, if the file format is changed, new counters are added to the right end.

The book describes, how to add a status monitor when the program runs in background and how to create a beautiful coloured statusmonitor that you can watch in a webbrowser.

The book is about the version 2.x and written, maintained and owned by Stefan Frings.


smstools-3.1.15/doc/udh.html000077500000000000000000000102151223712572200157040ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

User Data Header

NOTE: This text is about concatenated messages, as an example. However, with SMSTools 3 you do not have to care about concatenation (multipart messages) while using settings autosplit = 3 for outgoing messages and internal_combine = yes for incoming messages. These are default values.


User Data header was added to the SMS format specification to add new features. Originally SMS was made to send single small binary files or text messages, with a maximum of 140 bytes or 160 7-bit characters. But now mobile devices need to distinguish between different files, for example ringtones, operator logos and wap-push messages. Also users want to send larger messages which was impossible with the original SMS format specification.

Each short message has a flag to indicate if the message part includes a User Data Header or not. If this flag is set to 1 (or true), then the first few bytes of the message are the User Data Header, followed by the message text or data. Example:

From: 491721234567
...

@$F%&0_;Hello, this is my message to you...

When you receive a text message with User Data Header, you would normally see some scrambled characters at the beginning. Starting with version 2.1, smsd automatically removes these characters from the message text and shows them as a hex-dump in the sms file header. Example:

From: 491721234567
...
UDH: true
UDH-DATA: 05 00 03 5F 02 01

Hello, this is my message to you...

Now you have a more beautiful text-part and the User Data Header is dumped into human readable format which makes reading it much easier. You might use the script hex2dec to convert the hexadecimal numbers to decimal.

The most popular use of User Data Headers are concatenated text messages. If somebody sends a text message that is longer than 160 characters, most phones splits the text automatically into two or more short messages. Each messages contains a header that is used by the receiving mobile phone to combine them in correct order.

Referring to the UDH-DATA in the above example, the 6 bytes have the following meaning:

05 = 5 bytes follow
00 = indicator for concatenated message
03 = three bytes follow
5F = message identification. Each part has the same value here
02 = the concatenated message has 2 parts
01 = this is part 1

When you receive concatenated text messages, the parts might arrive in random order. You might first receive the last part and then the first part. The GSM specification does not force any device to transfer messages in the original order. So when you receive a concatenated message you need to check if all parts have been received before you can put all the parts together.

In case of binary messages, the header is part of the binary data and does not appear in the header, so you will not see any UDH-DATA header in binary message files.


smstools-3.1.15/doc/windows.html000077500000000000000000000121211223712572200166140ustar00rootroot00000000000000 SMS Server Tools 3

SMS Server Tools 3

Home

Step by Step instruction for Windows

You need about 150 MB free space on drive C or whereever you install it.

Cygwin is not a Unix emulator. It is a set of standard programs from the Unix world, that were translated to windows. Cygwin adds a lot "missing" functions to windows, so most Source codes for Unix can be translated to Windows with minor changes.

It is very useful to know the basic Unix commands but you can also continue with this instruction if you have no Unix knowledge.


Go to www.cygwin.com and click on Install Now.

Click on Next until you can select a download server. Select one that is near to you and click on Next

After the list of cygwin packages is received you can select the parts that you want to install. Do not deselect anything.

Open the Devel list and select gcc and make by clicking one time on them. If you want to use statusmonitor feature, a gcc-g++ package is also needed. After the version 3.2, libiconv is also needed if iconv is used. It's used by default. Then click on Next until you can see the progress bar. The default settings are Ok.

Download the SMS Server Tools 3 into the directory c:\cygwin\usr\src. Some windows versions store the file with a wrong filename (*.tar.tar) instead of *.tar.gz but this does not cause any problem.

Start a Cygwin Bash Shell from the Windows start menu. This shell is similar to the MS-DOS window but it accepts Unix commands.

Enter the following commands:

cd /usr/src
tar -xzf smstools3*
cd smstools3
make
make install

You should not see any error message. Warnings are acceptable.

Open the file c:\cygwin\etc\smsd.conf and modify it as described in configuring.
The name of the first serial port is /dev/com1. Use always / instead of \ when you write directory names. The whole config file is case-sensitive.

NOTE: Do not use a device name, like COM1, as a modem name (like "[COM1]"). While a message is received, a file starting with this name is created and Windows handles it as a device. This will cause a modem process to be freezed.

Run smsd by entering the command smsd -s (with status monitor) in a Cygwin Window, or use sms3 script.

Send short messages by creating SMS Files in the directory c:\cygwin\var\spool\sms\outgoing or by using the command sendsms in another Cygwin Window.

Running smsd as a Windows service

If you like to install smsd (version >= 3.0.3) as a service, then enter this command in a Cygwin Window:

cygrunsrv --install smsd --path /usr/local/bin/smsd.exe --type auto --shutdown --env "CYGWIN=server" --env "PATH=/usr/local/bin:/usr/bin:/bin" --desc "SMS Server Tools 3" --args "-t"

You can then start and stop this service using the control panel of windows or the windows commands net start smsd and net stop smsd.

NOTE: Version >= 3.0.3 should be run in terminal mode. Last argument --args "-t" enables this mode. If you have previously installed smsd as a service, remove it (cygrunsrv -R smsd) and reinstall with a new command.

NOTE: If you have had a previous version in use, there might be --neverexits option defined in the command. This option should not be used, because it causes an error message while the service is stopped.


The book describes the installation procedure of the SMS Server Tools much more in detail. It also explains how to install and use other useful programs like Apache webserver, MySQL, PHP script language, cronjobs, shell scripts, sendmail, fetchmail, formail, sed, cut, grep, awk.

The book is about the version 2.x and written, maintained and owned by Stefan Frings.


smstools-3.1.15/examples/000077500000000000000000000000001223712572200153055ustar00rootroot00000000000000smstools-3.1.15/examples/.procmailrc000077500000000000000000000002031223712572200174370ustar00rootroot00000000000000VERBOSE=off MAILDIR=/var/spool/mail DEFAULT=/var/spool/mail/sms LOGFILE=/var/log/procmail :0 * ^TOsms | /usr/local/bin/email2sms smstools-3.1.15/examples/.qmailrc000077500000000000000000000000341223712572200167360ustar00rootroot00000000000000|/usr/local/bin/email2sms smstools-3.1.15/examples/README000066400000000000000000000002231223712572200161620ustar00rootroot00000000000000This folder is not actively updated. More examples can be found in the program support website http://smstools3.kekekasvi.com/index.php?p=support smstools-3.1.15/examples/language-ISO-8859-15.fi000066400000000000000000000021201223712572200206710ustar00rootroot00000000000000# Translation of SMS Server Tools 3 message file headers for # finnish, created by Keijo Kasvi 30.07.2007. incoming = yes datetime = %d.%m.%Y %H:%M:%S yes_word = kyllä no_word = ei yes_chars = 'K','k' no_chars = 'E','e' To: = Vastaanottaja: From: = Lähettäjä: #Flash: = Flash: Provider: = Palveluntarjoaja: Queue: = Jono: Binary: = Binääri: Report: = Raportti: Autosplit: = Automaattipilkonta: Validity: = Voimassaolo: Voicecall: = Äänipuhelu: Replace: = Korvaa: Alphabet: = Merkistö: Include: = Liitä: Macro: = Makro: Hex: = Heksa: SMSC: = Viestikeskus: Sent: = Lähetetty: Modem: = Modeemi: From_TOA: = Osoitetyyppi: From_SMSC: = Viestikeskus: Name: = Nimi: Received: = Vastaanotettu: Subject: = Otsikko: UDH-Type: = UDH-Tyyppi: Length: = Pituus: Fail_reason: = Epäonnistumisen_syy: Failed: = Epäonnistunut: IMSI: = Liittymätunniste: Message_id: = Viestitunniste: Priority: = Etuoikeus: Original_filename: = Alkuperäinen_tiedostonimi: Call_type: = Puhelutyyppi: missed = vastaamaton CALL MISSED = VASTAAMATON PUHELU Result: = Tulos: Number: = Numero: Incomplete: = Epätäydellinen: smstools-3.1.15/examples/language-UTF-8.fi000066400000000000000000000021411223712572200202070ustar00rootroot00000000000000# Translation of SMS Server Tools 3 message file headers for # finnish, created by Keijo Kasvi 30.07.2007. incoming = yes datetime = %d.%m.%Y %H:%M:%S yes_word = kyllä no_word = ei yes_chars = 'K','k' no_chars = 'E','e' To: = Vastaanottaja: From: = Lähettäjä: #Flash: = Flash: Provider: = Palveluntarjoaja: Queue: = Jono: Binary: = Binääri: Report: = Raportti: Autosplit: = Automaattipilkonta: Validity: = Voimassaolo: Voicecall: = Äänipuhelu: Replace: = Korvaa: Alphabet: = Merkistö: Include: = Liitä: Macro: = Makro: Hex: = Heksa: SMSC: = Viestikeskus: Sent: = Lähetetty: Modem: = Modeemi: From_TOA: = Osoitetyyppi: From_SMSC: = Viestikeskus: Name: = Nimi: Received: = Vastaanotettu: Subject: = Otsikko: UDH-Type: = UDH-Tyyppi: Length: = Pituus: Fail_reason: = Epäonnistumisen_syy: Failed: = Epäonnistunut: IMSI: = Liittymätunniste: Message_id: = Viestitunniste: Priority: = Etuoikeus: Original_filename: = Alkuperäinen_tiedostonimi: Call_type: = Puhelutyyppi: missed = vastaamaton CALL MISSED = VASTAAMATON PUHELU Result: = Tulos: Number: = Numero: Incomplete: = Epätäydellinen: smstools-3.1.15/examples/operator_logo1.sms000066400000000000000000000002531223712572200207650ustar00rootroot00000000000000To: 491722056395 Binary: true ‚ébò NΤ£¨ Ê **€Î¤€ð€ @€@smstools-3.1.15/examples/operator_logo2.sms000066400000000000000000000001771223712572200207730ustar00rootroot00000000000000To: 491722056395 Binary: true ‚é9œ°A«`‰ !䲊@'Ñ脊$H¢J(ˆE"ˆA(¢„äz!¢y€ smstools-3.1.15/examples/received_report.sms000066400000000000000000000003461223712572200212150ustar00rootroot00000000000000From: 491721234567 From_SMSC: 491722270333 Sent: 02-01-21 22:26:23 Received: 02-01-21 22:26:25 Subject: GSM1 SMS STATUS REPORT Message_id: 117 Discharge_timestamp: 02-01-21 22:27:02 Status: 0,Ok,short message received by the SME smstools-3.1.15/examples/received_sms.sms000066400000000000000000000002651223712572200205040ustar00rootroot00000000000000From: 491721234567 From_SMSC: 491722270333 Sent: 02-01-21 22:26:23 Received: 02-01-21 22:26:25 Subject: GSM1 Alphabet: ISO8859-15 This is the Text that I sent with my mobile phone.smstools-3.1.15/examples/send_sms.sms000066400000000000000000000001201223712572200176350ustar00rootroot00000000000000To: 491721234567 Hello, this is a text that I can send with SMS Server Tools. smstools-3.1.15/examples/send_sms_unicode.sms000066400000000000000000000000521223712572200213470ustar00rootroot00000000000000To: 491721234567 Alphabet: UCS2 N-\1ê[¶ smstools-3.1.15/examples/smsd.conf.easy000066400000000000000000000002471223712572200200650ustar00rootroot00000000000000# Example smsd.conf. Read the manual for a description devices = GSM1 logfile = /var/log/smsd.log loglevel = 7 [GSM1] device = /dev/ttyS0 incoming = yes #pin = 1111 smstools-3.1.15/examples/smsd.conf.full000066400000000000000000000060671223712572200200740ustar00rootroot00000000000000# Example smsd.conf. Read the manual for a description # This sample configuration is no more updated. # See the manual for all possible configuration settings. devices = GSM1, GSM2 outgoing = /var/spool/sms/outgoing checked = /var/spool/sms/checked #failed = /var/spool/sms/failed incoming = /var/spool/sms/incoming #report = /var/spool/sms/report #sent = /var/spool/sms/sent #mypath = /usr/local/bin #logfile = /var/log/smsd.log loglevel = 7 #alarmhandler = /usr/local/bin/alarmevent alarmlevel = 4 delaytime = 10 errorsleeptime = 10 blocktime = 3600 #blockafter = 3 #eventhandler = /usr/local/bin/smsevent #stats = /var/log/smsd_stats #stats_interval = 3600 #stats_no_zeroes = no #blacklist = /etc/smsd.black #whitelist = /etc/smsd.white #checkhandler = /usr/local/bin/smscheck receive_before_send = no # autosplit 0=no 1=yes 2=with text numbers 3=concatenated autosplit = 3 # store_received_pdu 0=no, 1=unsupported, 2=unsupported and 8bit, 3=all #store_received_pdu = 1 #validity = 255 #decode_unicode_text = no #internal_combine = no #user = smsd #group = dialout #infofile = /var/run/sms/smsd.working #pidfile = /var/run/sms/smsd.pid #keep_filename = no #store_original_filename = yes #regular_run = /var/spool/sms/regular_run #regular_run_interval = 300 #admin_to = (number) #terminal = no #filename_preview = 80 #incoming_utf8 = yes [queues] # Commented lines are examples for germany # D1 = /var/spool/sms/D1 # D2 = /var/spool/sms/D2 # O2 = /var/spool/sms/O2 # EPLUS = /var/spool/sms/EPLUS # QUAM = /var/sppol/sms/QUAM # MOBILCOM = /var/spool/sms/MOBILCOM OTHER = /var/spool/sms/OTHER [provider] # Commented lines are examples for germany # D1 = 49160, 49170, 49171, 49175, 49151 # D2 = 491520, 49162, 49172, 49173, 49174 # O2 = 49176, 49179, 49159 # EPLUS = 49163, 49177, 49178, 49157 # QUAM = 49150 # MOBILCOM = 49156 OTHER = 0,1,2,3,4,5,6,7,8,9 [GSM1] init = ATE0+CPMS="SM"+CNMI=2,0,0,2,1 # Windows: /dev/com1, Solaris: /dev/cua/a, Linux /dev/ttyS0 device = /dev/ttyS0 incoming = yes queues = OTHER #You don't need a PIN for mobile phones #pin = 1111 mode = new #smsc = 491722270000 baudrate = 19200 rtscts = yes cs_convert = yes report = no memory_start = 1 #eventhandler = /usr/local/bin/smsevent #primary_memory = memory name #secondary_memory = memory name #secondary_memory_max = number #pdu_from_file = /var/spool/sms/GSM1-PDU #sending_disabled = no #decode_unicode_text = no #internal_combine = no #pinsleeptime = 5 #admin_to = (number) #message_limit = 20 #message_count_clear = 60 [GSM2] init = ATE0 # Windows: /dev/com2, Solaris: /dev/cua/b, Linux /dev/ttyS1 device = /dev/ttyS1 incoming = yes queues = OTHER #You don't need a PIN for mobile phones #pin = 2222 mode = new #smsc = 491710760000 baudrate = 19200 rtscts = yes cs_convert = yes report = no memory_start = 1 #eventhandler = /usr/local/bin/smsevent #primary_memory = memory name #secondary_memory = memory name #secondary_memory_max = number #pdu_from_file = /var/spool/sms/GSM2-PDU #sending_disabled = no #decode_unicode_text = no #internal_combine = no #pinsleeptime = 5 #admin_to = (number) #message_limit = 20 #message_count_clear = 60 smstools-3.1.15/examples/smsd.conf.net000066400000000000000000000003241223712572200177060ustar00rootroot00000000000000# Example smsd.conf. Read the manual for a description devices = GSM_Net1 logfile = /var/log/smsd.log loglevel = 7 [GSM_Net1] # This is a socket device = @10.1.1.1:5000 pin = 1234 keep_open = no incoming = yes smstools-3.1.15/examples/smsd.conf.non-root000066400000000000000000000006231223712572200206750ustar00rootroot00000000000000# Example smsd.conf. Read the manual for a description devices = GSM1 #logfile = /var/log/smsd.log loglevel = 7 # Settings to run smsd without root priviledges: user = user group = dialout logfile = /var/spool/sms/smsd.log infofile = /var/spool/sms/smsd.running # When infofile is changed, it must be changed to the /etc/init.d/sms3 script too. [GSM1] device = /dev/ttyS0 incoming = yes #pin = 1111 smstools-3.1.15/install.sh000077500000000000000000000044671223712572200155070ustar00rootroot00000000000000#!/bin/sh #Do not run directly. This is a helper script for make. BINDIR=$1 if [ -z "$BINDIR" ]; then BINDIR=/usr/local/bin fi makepath() { p="$1" ( # Absolut Unix. if echo $p | grep '^/' >/dev/null then cd / fi # This will break if $1 contains a space. for c in `echo $p | tr '/' ' '` do if [ -d "$c" ] || mkdir "$c" then cd "$c" || return $? else echo "failed to create $c" >&2; return $? fi done ) } copy() { if [ -f $2 ]; then echo " Skipped $2, file already exists" else echo " $2" cp $1 $2 fi } forcecopy() { if [ -f $2 ]; then echo " Overwriting $2" cp $1 $2 else echo " $2" cp $1 $2 fi } delete() { if [ -f $1 ]; then echo " Deleting $1" rm $1 fi } makedir() { if [ -d $1 ]; then echo " Skipped $1, directory already exists" else echo " Creating directory $1" mkdir $1 fi } echo "" if [ ! -f src/smsd ] && [ ! -f src/smsd.exe ]; then echo 'Please run "make -s install" instead.' exit 1 fi echo "Installing binary program files" makepath $BINDIR if [ -f src/smsd.exe ]; then forcecopy src/smsd.exe $BINDIR/smsd.exe else forcecopy src/smsd $BINDIR/smsd fi delete $BINDIR/getsms delete $BINDIR/putsms echo "Installing some scripts" copy scripts/sendsms $BINDIR/sendsms copy scripts/sms2html $BINDIR/sms2html copy scripts/sms2unicode $BINDIR/sms2unicode copy scripts/unicode2sms $BINDIR/unicode2sms echo "Installing config file" copy examples/smsd.conf.easy /etc/smsd.conf echo "Creating minimum spool directories" makedir /var/spool makedir /var/spool/sms makedir /var/spool/sms/incoming makedir /var/spool/sms/outgoing makedir /var/spool/sms/checked echo "Installing start-script" SMS3SCRIPT=scripts/sms3 if [ -d /etc/init.d ]; then copy scripts/sms3 /etc/init.d/sms3 SMS3SCRIPT=/etc/init.d/sms3 elif [ -d /sbin/init.d ]; then copy scripts/sms3 /sbin/init.d/sms3 SMS3SCRIPT=/sbin/init.d/sms3 else echo " I do not know where to copy scripts/sms3. Please find out yourself." fi echo "" echo "Example script files are not installed automatically." echo 'Please dont forget to edit /etc/smsd.conf.' if [ "$BINDIR" != "/usr/local/bin" ]; then echo "You have installed executables to $BINDIR," echo "you should manually edit $SMS3SCRIPT script." fi smstools-3.1.15/package.sh000077500000000000000000000005001223712572200154140ustar00rootroot00000000000000#!/bin/sh # This script is used by the author to make a tar.gz package VERSION=`cat src/version.h | tr -d '"' | awk '/smsd_version/ {print $3}'` PACKAGE=smstools3-$VERSION.tar.gz cd .. tar -chzf $PACKAGE --exclude='*~' --exclude='*.bak' --exclude='*.pdf' --exclude='*.tar.gz' smstools3 echo "Package $PACKAGE created" smstools-3.1.15/scripts/000077500000000000000000000000001223712572200151565ustar00rootroot00000000000000smstools-3.1.15/scripts/README000066400000000000000000000002231223712572200160330ustar00rootroot00000000000000This folder is not actively updated. More examples can be found in the program support website http://smstools3.kekekasvi.com/index.php?p=support smstools-3.1.15/scripts/callhandler000077500000000000000000000006561223712572200173640ustar00rootroot00000000000000#!/bin/sh # Incoming missed call sample (eventhandler). # In the modem section of smsd.conf define phonecalls = yes # and use this script as an eventhandler or part of it. if [ "$1" = "CALL" ]; then TO=`formail -zx From: <$2` FILE=`mktemp /tmp/send_XXXXXX` echo "To: $TO" > $FILE echo "" >> $FILE echo "This number only accepts SMS." >> $FILE FILE2=`mktemp /var/spool/sms/outgoing/send_XXXXXX` mv $FILE $FILE2 fi smstools-3.1.15/scripts/checkhandler-utf-8000077500000000000000000000004371223712572200204640ustar00rootroot00000000000000#!/bin/sh # This sample converts outgoing UTF-8 file to ISO character set # which is an internal format used in smsd. # After version 3.0.8 this is not needed, conversion is automatic. FILE=`mktemp /tmp/smsd_XXXXXX` iconv -t ISO-8859-15 -f UTF-8 < $1 > $FILE mv $FILE $1 chmod 644 $1 smstools-3.1.15/scripts/email2sms000077500000000000000000000021541223712572200170020ustar00rootroot00000000000000#!/bin/sh # Smsd can send eMails via SMS. You simply need to store the eMail as text # file in the outgoing queue directory with a unique filename. # The eMail must include the phone number in the To: field, for example: # To: "Herbert +491721234567" # This simple script creates a unique filename and copies the eMail from # stdin to that file. # If you use procmail to deliver local eMail. Create the user sms and create # the file /home/sms/.procmailrc with this content: # VERBOSE=off # MAILDIR=/var/spool/mail # DEFAULT=/var/spool/mail/sms # LOGFILE=/var/log/procmail # # :0 # * ^TOsms # | /usr/local/bin/email2sms # If you use QMail and vpopmail you need the file # /home/vpopmail/domains/your-domain/.qmail-sms with this content: # | /usr/local/bin/email2sms tmp=$(mktemp /tmp/smsgw.XXXXXX) cat >$tmp destinations=`formail -zx "To:" < $tmp` IFS=, for destination in $destinations; do destination=${destination## } OUTFILE=$(mktemp /var/spool/sms/outgoing/smsgw.out.XXXXXX) formail -f -I "To: $destination" < $tmp > $OUTFILE chmod 666 $OUTFILE echo "SMS queued to $OUTFILE" done rm $tmp smstools-3.1.15/scripts/eventhandler-utf-8000077500000000000000000000005371223712572200205310ustar00rootroot00000000000000#!/bin/sh # This sample converts a received message file from # ISO to UTF-8 character set. # After version 3.0.8 this is not needed. # Use incoming_utf8 = yes in the modem settings. case "$1" in SENT|RECEIVED|FAILED) FILE=`mktemp /tmp/smsd_XXXXXX` iconv -f ISO-8859-15 -t UTF-8 < $2 > $FILE mv $FILE $2 chmod 644 $2 ;; esac smstools-3.1.15/scripts/eventhandler_report000077500000000000000000000020361223712572200211570ustar00rootroot00000000000000#!/bin/bash # SMS Server Tools 3. # Sample eventhandler script for storing delivery timestamps. #-------------------------------------------------------------------------- # The following code stores delivery timestamp to the sent message if [ "$1" = "REPORT" ]; then SENTDIR=/var/spool/sms/sent if grep "Status: 0" $2 >/dev/null; then FROM=`formail -zx From: < $2` RECEIVED=`formail -zx Received: < $2` TMPFILE=`mktemp /tmp/smsd_XXXXXX` formail -I "" < $2 | sed -e"1,2d" > $TMPFILE MESSAGE_ID=`formail -zX Message_id: < $TMPFILE` grep -lx "$MESSAGE_ID" $SENTDIR/* > $TMPFILE cat $TMPFILE | while read FNAME; do OLDRECEIVED=`formail -zx Received: < ${FNAME}` if [ "$OLDRECEIVED" = "" ]; then TO=`formail -zx To: < ${FNAME}` if [ "$TO" = "$FROM" ]; then TMPFILE2=`mktemp /tmp/smsd_XXXXXX` cp ${FNAME} $TMPFILE2 formail -f -I "Received: $RECEIVED" < $TMPFILE2 > ${FNAME} unlink $TMPFILE2 fi fi done unlink $TMPFILE fi fi exit 0 smstools-3.1.15/scripts/forwardsms000077500000000000000000000005001223712572200172660ustar00rootroot00000000000000#!/bin/sh # SMS forwarding sample (eventhandler). FORWARD_TO="358401234567" if [ "$1" = "RECEIVED" ]; then TEXT=`formail -I "" <$2` FILE=`mktemp /tmp/send_XXXXXX` echo "To: $FORWARD_TO" >> $FILE echo "" >> $FILE echo "$TEXT" >> $FILE FILE2=`mktemp /var/spool/sms/outgoing/send_XXXXXX` mv $FILE $FILE2 fi smstools-3.1.15/scripts/hex2bin000077500000000000000000000006451223712572200164500ustar00rootroot00000000000000#!/bin/gawk -f # This script reads a hex-dump and converts it to a binary file. # The hex-dump must contain one or more hexadecimal numbers separated # by spaces, colon or 0x. The lest significant end of the hex values # is on the right side. Valid examples: # # echo "01 2 0x03 04:05 fa3B" | hex2bin > testfile.bin BEGIN { FS="((0x)|[ :])*"; } { for (i=1; $i!=""; i++) { printf "%c",strtonum("0x"$i); } } smstools-3.1.15/scripts/hex2dec000077500000000000000000000006501223712572200164270ustar00rootroot00000000000000#!/bin/gawk -f # This script reads a hex-dump and converts it to decimal numbers. # The hex-dump must contain one or more hexadecimal numbers separated # by spaces, colon or 0x. The lest significant end of the hex values # is on the right side. Valid examples: # # echo "01 2 0x03 04:05 fa3B" | hex2dec BEGIN { FS="((0x)|[ :])*"; } { for (i=1; $i!=""; i++) { printf "%d ",strtonum("0x"$i); } printf "\n"; } smstools-3.1.15/scripts/load_balancing.sh000077500000000000000000000045431223712572200204400ustar00rootroot00000000000000#!/bin/bash # --------------------------------------------------------------------------------------- # This example is for three modems, named GSM1, GSM2 and GSM3 using queues Q1, Q2 and Q3. # In the global part of smsd.conf, enable message counters: # stats = /var/spool/sms/stats # Use zero value for interval if statistics files are not used: # stats_interval = 0 # # Enable checkhandler (this script): # checkhandler = /usr/local/bin/load_balancing.sh # # Define queues: # [queues] # Q1 = /var/spool/sms/Q1 # Q2 = /var/spool/sms/Q2 # Q3 = /var/spool/sms/Q3 # # With smsd >= 3.1.7 providers are not needed to define, # with previous versions define the following: # [providers] # Q1 = 0,1,2,3,4,5,6,7,8,9,s # Q2 = 0,1,2,3,4,5,6,7,8,9,s # Q3 = 0,1,2,3,4,5,6,7,8,9,s # # Add queue definition for each modem: # [GSM1] # queues = Q1 # etc... # --------------------------------------------------------------------------------------- # Settings for this script: STATSDIR=/var/spool/sms/stats MODEMS=( GSM1 GSM2 GSM3 ) QUEUES=( Q1 Q2 Q3 ) # --------------------------------------------------------------------------------------- NUMBER_OF_MODEMS=${#MODEMS[@]} NUMBER_OF_QUEUES=${#QUEUES[@]} if [ $NUMBER_OF_MODEMS -ne $NUMBER_OF_QUEUES ]; then echo "ERROR: Number of queues does not match number of modems." exit 1 # Message is rejected. fi read_counter() { local RESULT=0 local FILE=$STATSDIR/$1.counter local COUNTER=0 if [[ -e $FILE ]] then COUNTER=`formail -zx $1: < $FILE` if [ "$COUNTER" != "" ]; then RESULT=$COUNTER fi fi return $RESULT } # If there is Queue (or Provider) defined, load balancing is ignored: QUEUE=`formail -zx Queue: < $1` if [ -z "$QUEUE" ]; then QUEUE=`formail -zx Provider: < $1` if [ -z "$QUEUE" ]; then # Read current counters: for ((i = 0; i < $NUMBER_OF_MODEMS; i++)); do read_counter ${MODEMS[${i}]} eval COUNTER_${MODEMS[${i}]}=$? done QUEUE=${QUEUES[0]} tmp=COUNTER_${MODEMS[0]} COUNTER=${!tmp} for ((i = 1; i < $NUMBER_OF_MODEMS; i++)); do tmp=COUNTER_${MODEMS[${i}]} tmp=${!tmp} if [ $tmp -lt $COUNTER ]; then QUEUE=${QUEUES[${i}]} tmp=COUNTER_${MODEMS[${i}]} COUNTER=${!tmp} fi done TMPFILE=`mktemp /tmp/smsd_XXXXXX` cp $1 $TMPFILE formail -f -I "Queue: $QUEUE" < $TMPFILE > $1 unlink $TMPFILE fi fi exit 0 smstools-3.1.15/scripts/mysmsd000077500000000000000000000035221223712572200164220ustar00rootroot00000000000000#!/bin/sh # This is an example script that logs all events into an SQL database # You need a MYSQL database as described in the documentation. # Please read the documentation before using this script. SQL_HOST=localhost SQL_USER=root SQL_PASSWORD="" SQL_DATABASE=smsd SQL_TABLE=sms_log DATE=`date +"%Y-%m-%d %H:%M:%S"` #Extract data from the SMS file FROM=`formail -zx From: < $2 | sed 's/"//g'` TO=`formail -zx To: < $2` #Remove plus sign, spaces, minus and short number prefix TO=`echo "$TO" | sed 's/ //g' | sed 's/+//g' | sed 's/s//g' | sed 's/-//g'` SUBJECT=`formail -zx Subject: < $2` SENT=`formail -zx Sent: < $2` #Text is not used but could be used #TEXT=`formail -I "" <$2` #Set some SQL parameters if [ "$SQL_PASSWORD" != "" ]; then SQL_ARGS="-p$SQL_PASSWORD"; else SQL_ARGS=""; fi SQL_ARGS="-h $SQL_HOST -u $SQL_USER $SQL_ARGS -D $SQL_DATABASE -s -e" #Insert a new entry into the SQL table if [ "$1" = "FAILED" ] || [ "$1" = "SENT" ]; then mysql $SQL_ARGS "insert into $SQL_TABLE (type,sent,sender,receiver,msgid) values (\"$1\",\"$DATE\",\"$FROM\",\"$TO\",\"$3\");"; elif [ "$1" = "RECEIVED" ]; then mysql $SQL_ARGS "insert into $SQL_TABLE (type,sent,received,sender,receiver) values (\"RECEIVED\",\"$SENT\",\"$DATE\",\"$FROM\",\"$SUBJECT\");"; elif [ "$1" = "REPORT" ]; then #Extract more data from the status report file DISCHARGE=`sed -e 1,/SMS\ STATUS/d < $2 | formail -zx Discharge_timestamp:` MSGID=`sed -e 1,/SMS\ STATUS/d < $2 | formail -zx Message_id:` STATUS=`sed -e 1,/SMS\ STATUS/d < $2 | formail -zx Status: | cut -f1 -d,` if [ "$MSGID" != "" ]; then ID=`mysql $SQL_ARGS "select id from $SQL_TABLE where receiver=\"$FROM\" and type=\"SENT\" and msgid=\"$MSGID\" order by id desc limit 1;"` mysql $SQL_ARGS "update $SQL_TABLE set received=\"$DISCHARGE\",status=\"$STATUS\" where id=\"$ID\";" fi fi smstools-3.1.15/scripts/pkill000077500000000000000000000004401223712572200162150ustar00rootroot00000000000000#! /bin/sh # This script can be used to kill a program by its name. # Please install this script only if your operating system # does not have such a command already. if [ "$1" = "" ]; then echo "Usage: pkill name" else kill `ps -e | grep $1 | awk '{print $1}'` exit $? fi smstools-3.1.15/scripts/regular_run000077500000000000000000000073401223712572200174350ustar00rootroot00000000000000#!/bin/bash # SMS Server Tools 3. # Sample script for using a regular run feature. # See run.html for more details. #-------------------------------------------------------------------------- # The first part is going to the eventhandler: # The following code stores delivery timestamp to the sent message if [ "$1" = "REPORT" ]; then SENTDIR=/var/spool/sms/sent if grep "Status: 0" $2 >/dev/null; then FROM=`formail -zx From: < $2` RECEIVED=`formail -zx Received: < $2` TMPFILE=`mktemp /tmp/smsd_XXXXXX` formail -I "" < $2 | sed -e"1,2d" > $TMPFILE MESSAGE_ID=`formail -zX Message_id: < $TMPFILE` grep -lx "$MESSAGE_ID" $SENTDIR/* > $TMPFILE cat $TMPFILE | while read FNAME; do OLDRECEIVED=`formail -zx Received: < ${FNAME}` if [ "$OLDRECEIVED" = "" ]; then TO=`formail -zx To: < ${FNAME}` if [ "$TO" = "$FROM" ]; then TMPFILE2=`mktemp /tmp/smsd_XXXXXX` cp ${FNAME} $TMPFILE2 formail -f -I "Received: $RECEIVED" < $TMPFILE2 > ${FNAME} unlink $TMPFILE2 fi fi done unlink $TMPFILE fi fi # The second part tries to find undelivered messages and sends them # to the alternate number if it is described. max_delay=600 #seconds, 1800 is 30 minutes. SENTDIR=/var/spool/sms/sent OUTGOINGDIR=/var/spool/sms/outgoing TMPFILE=`mktemp /tmp/smsd_XXXXXX` send2alternate=0 rename_alternate=0 grep -l ^Alternate_to: $SENTDIR/* > $TMPFILE cat $TMPFILE | while read FNAME; do if ! test -f "${FNAME}.LOCK" ; then ALT_TO=`formail -zx Alternate_to: < ${FNAME}` if [ "$ALT_TO" != "" ]; then MSG_ID=`formail -zx Message_id: < ${FNAME}` if [ "$MSG_ID" != "" ]; then RECEIVED=`formail -zx Received: < ${FNAME}` if [ "$RECEIVED" = "" ]; then #This message is not yet received SENT=`formail -zx Sent: < ${FNAME}` if [ "$SENT" = "" ]; then #Hmmm... No Sent: timestamp? Will resend now. send2alternate=1 else #How long this message has been waiting? cur_timestamp=`date +%s` msg_timestamp=`date +%s -d "$SENT"` time_diff=`expr $cur_timestamp - $msg_timestamp` if [ $time_diff -gt $max_delay ]; then send2alternate=1 fi fi else #This message is now received rename_alternate=1 fi fi if [ $send2alternate -gt 0 ]; then TMPFILE2=`mktemp /tmp/smsd_XXXXXX` formail -f -I Alternate_to: -i "To: $ALT_TO" < ${FNAME} > $TMPFILE2 TMPFILE3=`mktemp $OUTGOINGDIR/regr_XXXXXX` echo "$$" > $TMPFILE3.LOCK mv $TMPFILE2 $TMPFILE3 rm $TMPFILE3.LOCK rename_alternate=1 fi if [ $rename_alternate -gt 0 ]; then #Alternate number is not effective anymore: TMPFILE2=`mktemp /tmp/smsd_XXXXXX` cp ${FNAME} $TMPFILE2 formail -f -R Alternate_to: Old-Alternate_to: < $TMPFILE2 > ${FNAME} unlink $TMPFILE2 fi fi fi done unlink $TMPFILE ############################################ SCHEDULEDDIR=/var/spool/sms/scheduled OUTGOINGDIR=/var/spool/sms/outgoing if [ "$(ls -A $SCHEDULEDDIR)" ]; then TMPFILE=`mktemp /tmp/smsd_XXXXXX` #grep -l ^Send: $SCHEDULEDDIR/* > $TMPFILE grep -l ^To: $SCHEDULEDDIR/* > $TMPFILE cat $TMPFILE | while read FNAME; do schedule=`formail -zx Send: < ${FNAME}` if [ "x$schedule" != "x" ]; then cur_timestamp=`date +%s` schedule_timestamp=`date +%s -d "$schedule"` time_diff=$(($schedule_timestamp - $cur_timestamp)) if [ $time_diff -lt 0 ]; then mv ${FNAME} $OUTGOINGDIR fi continue fi # done unlink $TMPFILE fi ############################################ exit 0 smstools-3.1.15/scripts/sendsms000077500000000000000000000044101223712572200165570ustar00rootroot00000000000000#!/bin/bash # This script send a text sms at the command line by creating # a sms file in the outgoing queue. # $1 is the destination phone number. # $2 is the message text. # If you leave $2 or both empty, the script will ask you. # If you give more than 2 arguments, last is taken as a text and # all other are taken as destination numbers. # If a destination is asked, you can type multiple numbers # delimited with spaces. # Keys for example: "password" and "keke": # KEYS="5f4dcc3b5aa765d61d8327deb882cf99 4a5ea11b030ec1cfbc8b9947fdf2c872 " KEYS="" # When creating keys, remember to use -n for echo: # echo -n "key" | md5sum smsd_user="smsd" # Will need echo which accepts -n argument: ECHO=echo case `uname` in SunOS) ECHO=/usr/ucb/echo ;; esac if ! [ -z "$KEYS" ]; then printf "Key: " read KEY if [ -z "$KEY" ]; then echo "Key required, stopping." exit 1 fi KEY=`$ECHO -n "$KEY" | md5sum | awk '{print $1;}'` if ! echo "$KEYS" | grep "$KEY" >/dev/null; then echo "Incorrect key, stopping." exit 1 fi fi DEST=$1 TEXT=$2 if [ -z "$DEST" ]; then printf "Destination(s): " read DEST if [ -z "$DEST" ]; then echo "No destination, stopping." exit 1 fi fi if [ -z "$TEXT" ]; then printf "Text: " read TEXT if [ -z "$TEXT" ]; then echo "No text, stopping." exit 1 fi fi if [ $# -gt 2 ]; then n=$# while [ $n -gt 1 ]; do destinations="$destinations $1" shift n=`expr $n - 1` done TEXT=$1 else destinations=$DEST fi echo "-- " echo "Text: $TEXT" ALPHABET="" if which iconv > /dev/null 2>&1; then if ! $ECHO -n "$TEXT" | iconv -t ISO-8859-15 >/dev/null 2>&1; then ALPHABET="Alphabet: UCS" fi fi owner="" if [ -f /etc/passwd ]; then if grep $smsd_user: /etc/passwd >/dev/null; then owner=$smsd_user fi fi for destination in $destinations do echo "To: $destination" TMPFILE=`mktemp /tmp/smsd_XXXXXX` $ECHO "To: $destination" >> $TMPFILE [ -n "$ALPHABET" ] && $ECHO "$ALPHABET" >> $TMPFILE $ECHO "" >> $TMPFILE if [ -z "$ALPHABET" ]; then $ECHO -n "$TEXT" >> $TMPFILE else $ECHO -n "$TEXT" | iconv -t UNICODEBIG >> $TMPFILE fi if [ "x$owner" != x ]; then chown $owner $TMPFILE fi FILE=`mktemp /var/spool/sms/outgoing/send_XXXXXX` mv $TMPFILE $FILE done smstools-3.1.15/scripts/sms2html000077500000000000000000000022661223712572200166630ustar00rootroot00000000000000#!/bin/bash # This script converts a received sms file into a html file. if [ $# -ne 1 ]; then echo "Usage: sms2html filename" exit 1 fi if grep "Alphabet:.*UCS" $1 >/dev/null; then ucs2="true" else ucs2="false" fi # Write HTML header echo "" # Write Header of the SMS file echo "" while read line; do if [ -z "$line" ]; then break else echo "$line
" fi done < $1 echo "
" # Write message text echo "

" if [ "$ucs2" = "true" ]; then text=`od -t x1 $1 | cut -c8-99` position="first" foundstart="false" previous="" for character in $text; do # Search for the start of the 16 bit part. Starts after "0a 0a" if [ "$foundstart" = "false" ]; then if [ "$character" = "0a" ] && [ "$previous" = "0a" ]; then foundstart="true" fi else # Combine two bytes to one 16bit character in html syntax if [ "$position" = "first" ]; then echo -en "&#x$character" position="second" else echo -en "$character;" position="first" fi fi previous="$character" done else text=`formail -I "" < $1` echo "$text" fi # Write HTML footer echo "" echo "" smstools-3.1.15/scripts/sms2unicode000077500000000000000000000017471223712572200173500ustar00rootroot00000000000000#!/bin/bash # This script converts a received sms file into a pure unicode text file. if [ $# -ne 1 ]; then echo "Usage: sms2unicode filename" exit 1 fi if grep "Alphabet:.*UCS" $1 >/dev/null; then ucs2=true else ucs2=false fi echo -en "\xFE\xFF" text=`od -t x1 $1 | cut -c8-99` foundstart="false" previous="" for character in $text; do # Search for the start of the 16 bit part. Starts after "0a 0a" if [ "$foundstart" = "false" ]; then if [ "$character" = "0a" ] && [ "$previous" = "0a" ]; then foundstart="true" fi if [ "$character" = "0a" ] && [ "$previous" != "0d" ]; then echo -en "\x00\x0d\x00\x$character" else echo -en "\x00\x$character" fi else if [ "$ucs2" = "false" ]; then if [ "$character" = "0a" ] && [ "$previous" != "0d" ]; then echo -en "\x00\x0d\x00\x$character" else echo -en "\x00\x$character" fi else echo -en "\x$character" fi fi previous="$character" done smstools-3.1.15/scripts/sms2xml000077500000000000000000000013601223712572200165110ustar00rootroot00000000000000#!/bin/sh #This script converts a received text message to XML. #Written by Thomas Dolberg, October 2005 #run this script only when a message was received. if [ "$1" != "RECEIVED" ]; then exit; fi; #Extract data from the SMS file FROM=`formail -zx From: < $2` TEXT=`formail -I "" <$2 | sed -e"1d"` #Save as XML #for some reason the mktemp-command creates two instances of the file if I add the .xml-extension to the FILENAME-variable. FILENAME=`mktemp /var/spool/sms/XML/answerXXXXXX` echo "" >$FILENAME.xml echo "" >>$FILENAME.xml echo " $FROM" >>$FILENAME.xml echo " $TEXT" >>$FILENAME.xml echo "" >>$FILENAME.xml #Delete the original file without the .xml-extension rm $FILENAMEsmstools-3.1.15/scripts/sms3000077500000000000000000000073171223712572200160010ustar00rootroot00000000000000#! /bin/sh # This script can be used to start/stop smsd # as a daemon in Linux, Solaris, Cygwin, FreeBSD # and MAC OS X Terminal window (Darwin). # This script is to be used with smsd version >= 3.0.3. ### BEGIN INIT INFO # Provides: smstools # Required-Start: $syslog # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: starts smstools ### END INIT INFO # Set USER and GROUP, if necessary: USER="" GROUP="" # If an unpriviledged user is selected, make sure that next two # files are writable by that user: PIDFILE="/var/run/smsd.pid" INFOFILE="/var/run/smsd.working" # Logfile can also be defined in here: LOGFILE="/var/log/smsd.log" DAEMON=/usr/local/bin/smsd # A program which turns power off for couple of seconds: RESETMODEMS=/usr/local/bin/smsd_resetmodems NAME=smsd PSOPT="-e" ECHO=echo case `uname` in *BSD|Darwin) PSOPT="axc" ;; SunOS) ECHO=/usr/ucb/echo ;; esac # Maximum time to stop smsd, after that it gets killed hardly: MAXWAIT=45 case "$1" in start) test -x $DAEMON || exit 0 $ECHO -n "Starting SMS Daemon: " MSG="." ARGS="-n MAINPROCESS -p$PIDFILE -i$INFOFILE" [ "x$USER" != x ] && ARGS="$ARGS -u$USER" [ "x$GROUP" != x ] && ARGS="$ARGS -g$GROUP" [ "x$LOGFILE" != x ] && ARGS="$ARGS -l$LOGFILE" PID=`cat $PIDFILE 2>/dev/null` if [ "x$PID" != x ]; then if kill -0 $PID 2>/dev/null; then MSG=" already running ($PID)." else PID="" fi fi if [ "x$PID" = x ]; then if ps $PSOPT | grep $NAME | grep -v grep >/dev/null; then MSG=" already running." else $DAEMON $ARGS sleep 1 PIDS=`ps $PSOPT | grep $NAME | grep -v grep` [ "x$PIDS" = x ] && MSG=" failed." fi fi echo "$NAME$MSG" ;; stop) if ps $PSOPT | grep $NAME | grep -v grep >/dev/null; then PID=`cat $PIDFILE 2>/dev/null` if [ "x$PID" != x ]; then P=`kill -0 $PID 2>/dev/null` [ "x$P" != x ] && PID="" fi if [ "x$PID" != x ]; then kill $PID else kill `ps $PSOPT | grep $NAME | grep -v grep | awk '{print $1}'` >/dev/null 2>&1 fi sleep 1 if ps $PSOPT | grep $NAME | grep -v grep >/dev/null; then echo "Allowing $NAME to terminate gracefully within $MAXWAIT seconds" infofound=0 dots=0 seconds=0 while ps $PSOPT | grep $NAME | grep -v grep >/dev/null; do if [ $infofound -lt 1 ]; then if [ -f $INFOFILE ]; then infofound=1 if [ $dots -gt 0 ]; then echo "" dots=0 fi $ECHO -n "$NAME is currently " cat $INFOFILE echo "Time counting is now disabled and we will wait until this job is complete." echo "If you are very hasty, use \"$0 force-stop\" to kill $NAME hardly (not recommended)." fi fi [ $infofound -lt 1 ] && seconds=`expr $seconds + 1` $ECHO -n "." dots=`expr $dots + 1` if [ "$seconds" -ge $MAXWAIT ]; then if [ $dots -gt 0 ]; then echo "" dots=0 fi echo "Timeout occured, killing $NAME hardly." kill -9 `ps $PSOPT | grep $NAME | grep -v grep | awk '{print $1}'` >/dev/null 2>&1 [ -f $PIDFILE ] && rm $PIDFILE seconds=0 fi sleep 1 done [ $dots -gt 0 ] && echo "" #echo "$NAME is stopped." fi fi ;; restart|reload) $0 stop $0 start ;; force-stop) if ps $PSOPT | grep $NAME | grep -v grep >/dev/null; then echo "Killing $NAME." kill -9 `ps $PSOPT | grep $NAME | grep -v grep | awk '{print $1}'` >/dev/null 2>&1 fi [ -f $PIDFILE ] && rm $PIDFILE ;; reset) $0 stop [ -f "$RESETMODEMS" ] && "$RESETMODEMS" sleep 30 $0 start ;; *) echo "Usage: $0 {start|stop|restart|force-stop|reset}" exit 1 esac smstools-3.1.15/scripts/smsevent000077500000000000000000000044231223712572200167530ustar00rootroot00000000000000#!/bin/sh # This is an example how to use an eventhandler with smsd. # $1 is the type of the event wich can be SENT, RECEIVED, FAILED or REPORT. # $2 is the filename of the sms. # $3 is the message id. Only used for SENT messages with status report. #The next line changes the file attributes so that everybody can read #received SM #if [ "$1" = "RECEIVED" ]; then # chmod a+r $2 #fi #This sends all received SM to an eMail receiver: #if [ "$1" = "RECEIVED" ]; then # /usr/sbin/sendmail username@localhost <$2 #fi #This sends all received SM to eMail receiver. The recipient address #must be the first word of the SM. #if [ "$1" = "RECEIVED" ]; then # receiver=`cat $2 | grep '^.*@.*' | sed -n 1p | cut -f1 -d' '` # if [ $receiver ]; then # /usr/sbin/sendmail $receiver < $2 # fi #fi #This forwards all received SM to another mobile phone: #if [ "$1" = "RECEIVED" ]; then # FROM=`formail -zx From: <$2` # formail -f -I "To: 491721234567" <$2 >$2.forward # echo "from $FROM" >> $2.forward # mv $2.forward /var/spool/sms/outgoing #fi #The following code concatenates multipart text messages if [ "$1" = "RECEIVED" ]; then if grep "UDH-DATA: 05 00 03" $2 >/dev/null; then if grep "Alphabet: ISO" $2 >/dev/null || grep "Alphabet: GSM" $2 >/dev/null; then # This is a multipart text message FROM=`formail -zx From: <$2` UDHDATA=`formail -zx UDH-DATA: <$2` # Extract information from UDH using awk to convert hex to dec MSGID=`echo "$UDHDATA" | awk '{printf "%d",strtonum("0x"$4)}'` PARTS=`echo "$UDHDATA" | awk '{printf "%d",strtonum("0x"$5)}'` PART=`echo "$UDHDATA" | awk '{printf "%d",strtonum("0x"$6)}'` # Rename the file mv $2 "$FROM.$MSGID.$PART" # Check if all parts have been received received=`ls -1 "$FROM.$MSGID."* | wc -l` if [ "$PARTS" -eq "$received" ]; then # Concatenate all parts # copy header from last part into a new file formail -X "" <$FROM.$MSGID.$PART >$2.concatenated echo "" >>$2.concatenated # add the text of each part counter=1 while [ "$counter" -le "$PARTS" ]; do sed -e '1,/^$/ d' <$FROM.$MSGID.$counter >>$2.concatenated rm $FROM.$MSGID.$counter counter=`expr $counter + 1` done fi fi fi fi smstools-3.1.15/scripts/smsresend000077500000000000000000000021401223712572200171040ustar00rootroot00000000000000#!/bin/sh # This is an example script that you can use to resent # failed messages. The script inserts a counter in the message # file that is used to ensure that the number of retries # is limited. # The script does not need any command line arguments. smsd_user="smsd" owner="" if [ -f /etc/passwd ]; then if grep $smsd_user: /etc/passwd >/dev/null; then owner=$smsd_user fi fi failed=/var/spool/sms/failed outgoing=/var/spool/sms/outgoing max=5 used=0 notused=0 cd $failed for file in *; do if [ "$file" = "*" ]; then echo "No failed files found" exit 0 fi retry=`formail -zx Retry: < $file` if [ "$retry" ]; then retry=`expr $retry + 1` else retry=1 fi if [ $retry -gt $max ]; then notused=`expr $notused + 1` else used=`expr $used + 1` mv $file $file.old formail -f -I "Retry: $retry" < $file.old > $file if [ "x$owner" != x ]; then chown $owner $file fi mv $file $outgoing rm $file.old fi done echo "$used messages moved again into outgoing spool directory" echo "$notused messages ignored because of to many retries" exit 0 smstools-3.1.15/scripts/smstest.php000077500000000000000000000063511223712572200174010ustar00rootroot00000000000000 SMS Test "; if ($use_utf) print "Raw header used to set charset to UTF-8

\n"; $charset = $_POST['charset']; $text = $_POST['text']; if ($charset != "" && $text != "") { print " charset: $charset
text: $text
"; $filename = "/var/spool/sms/outgoing/smstest-" .date("siH"); if (($handle = fopen($filename .".LOCK", "w")) != false) { $l = strlen($st = "To: $to\n"); fwrite($handle, $st); if ($charset == "UNICODE") { $l += strlen($st = "Alphabet: UCS\n"); fwrite($handle, $st); $text = mb_convert_encoding($text, "UCS-2BE", "UTF-8"); } else if ($charset == "ISO") $text = mb_convert_encoding($text, "ISO-8859-15", "UTF-8"); if ($_POST['flash'] != "") { $l += strlen($st = "Flash: yes\n"); fwrite($handle, $st); } $l += strlen($st = "Adjustment: +"); fwrite($handle, $st); $pad = 14 - $l % 16 + 16; while ($pad-- > 0) fwrite($handle, "+"); fwrite($handle, "\n\n$text"); fclose($handle); if (($handle = fopen($filename .".LOCK", "r")) == false) print "Unable to read message file.
"; else { print "


\n";
      while (!feof($handle))
        echo fgets($handle, 1024);
      print "
\n"; fclose($handle); } $tmpfilename = tempnam("/tmp", "smstest-"); exec("/usr/bin/hexdump -C < $filename.LOCK > $tmpfilename"); if (($handle = fopen($tmpfilename, "r")) == false) print "Unable to create dump.
"; else { print "
\n";
      while (!feof($handle))
        echo fgets($handle, 1024);
      print "
\n"; fclose($handle); unlink($tmpfilename); } if ($_POST['showonly'] == "") { if (rename($filename .".LOCK", $filename) == true) print "Message placed to the spooler,
filename: $filename
\n"; else print "FAILED!
\n"; } else unlink($filename .".LOCK"); } else print "FAILED!
\n"; if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') $result = 'https://'; else $result = 'http://'; $result .= $_SERVER['HTTP_HOST'] .$_SERVER['PHP_SELF']; print "
Back\n"; } else { print "
Send a message using:
ISO
UNICODE
No charset conversion here

Flash
Show only the message file dump

Text:

"; } print " "; ?> smstools-3.1.15/scripts/sql_demo000077500000000000000000000015621223712572200167130ustar00rootroot00000000000000#!/bin/sh # Please read the description in the manual of SMS Server Tools. #run this script only when a message was received. if [ "$1" != "RECEIVED" ]; then exit; fi; #Define the database parameters SQL_HOST=localhost SQL_USER=root SQL_PASSWORD= SQL_DATABASE=smsd SQL_TABLE=demo #Extract data from the SMS file FROM=`formail -zx From: < $2` TEXT=`formail -I "" <$2 | sed -e"1d"` #Set some SQL parameters if [ "$SQL_PASSWORD" != "" ]; then SQL_ARGS="-p $SQL_PASSWORD"; else SQL_ARGS=""; fi SQL_ARGS="-h $SQL_HOST -u $SQL_USER $SQL_ARGS -D $SQL_DATABASE -s -e" #Do the SQL Query AMOUNT=`mysql $SQL_ARGS "select amount from $SQL_TABLE where msisdn=\"$FROM\" and password=\"$TEXT\" ;"` #Create an answer SM with the amount FILENAME=`mktemp /var/spool/sms/outgoing/answerXXXXXX` echo "To: $FROM" >$FILENAME echo "" >> $FILENAME echo "Your amount is $AMOUNT" >>$FILENAME smstools-3.1.15/scripts/unicode2sms000077500000000000000000000013621223712572200173410ustar00rootroot00000000000000#!/bin/bash # This script converts a pure unicode text file into an sms file for sending. if [ $# -ne 1 ]; then echo "Usage: unicode2sms filename" exit 1 fi if grep ".A.l.p.h.a.b.e.t.:.*U.C.S" $1 >/dev/null; then ucs2=true else ucs2=false fi text=`od -t x1 $1 | cut -c8-99` foundstart="false" position="first" for character in $text; do if [ "$position" = "first" ]; then if [ "$foundstart" = "true" ]; then echo -en "\x$character" fi position="second" else if [ "$character" != "ff" ]; then echo -en "\x$character" fi if [ "$foundstart" = "false" ] && [ "$character" = "0a" ] && [ "$previous" = "0a" ]; then foundstart="true" fi previous="$character" position="first" fi done smstools-3.1.15/src/000077500000000000000000000000001223712572200142565ustar00rootroot00000000000000smstools-3.1.15/src/Makefile000077500000000000000000000027501223712572200157250ustar00rootroot00000000000000# In case of windows, use os_cygwin=yes setting in the configuration file (smsd.conf). # Select your setup size: CFLAGS = -D NUMBER_OF_MODEMS=64 # Uncomment for Solaris # CFLAGS += -D SOLARIS # This might be also needed for Solaris: # CC=gcc # Comment this out, to enable statistics CFLAGS += -D NOSTATS # Comment this out if iconv is not available on the system #CFLAGS += -D USE_ICONV # Uncomment this is iconv is used and it's not included in libc: #LFLAGS += -liconv # Uncomment this to force smsd to run in terminal mode #CFLAGS += -D TERMINAL # Uncomment this to disable usage of inet socket #CFLAGS += -D DISABLE_INET_SOCKET # Uncomment to add debug info # CFLAGS += -ggdb -O0 # The following option is only used by developers # CFLAGS += -D DEBUGMSG CFLAGS += -W -Wall # The following enables logfile size more than 2GB CFLAGS += -D_FILE_OFFSET_BITS=64 # Use the following only on GNU/Linux and only if you need ps listing like "smsd: MAINPROCESS" and "smsd: GSM1" # CFLAGS += -D USE_LINUX_PS_TRICK all: smsd smsd: smsd.c extras.o locking.o cfgfile.o logging.o alarm.o smsd_cfg.o charset.o stats.o blacklist.o whitelist.o modeminit.o pdu.o ifneq (,$(findstring SOLARIS,$(CFLAGS))) ifeq (,$(findstring DISABLE_INET_SOCKET,$(CFLAGS))) override LFLAGS += -lsocket -lnsl endif endif ifneq (,$(findstring NOSTATS,$(CFLAGS))) $(CC) $(CFLAGS) -o $@ $^ $(LFLAGS) else $(CC) `mm-config --cflags` $(CFLAGS) -o $@ $^ `mm-config --ldflags --libs` $(LFLAGS) endif clean: rm -f *.o smsd *.exe *~ smstools-3.1.15/src/alarm.c000077500000000000000000000030101223712572200155130ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include "alarm.h" #include "extras.h" #include "smsd_cfg.h" char* _alarmhandler={0}; int _alarmlevel=LOG_WARNING; void set_alarmhandler(char* handler,int level) { _alarmhandler=handler; _alarmlevel=level; } void alarm_handler0(int severity, char *text) { alarm_handler(severity, "%s", text); } void alarm_handler(int severity, char* format, ...) { va_list argp; char text[1024]; char cmdline[PATH_MAX+1024]; char timestamp[40]; if (_alarmhandler[0]) { va_start(argp,format); vsnprintf(text,sizeof(text),format,argp); va_end(argp); if (severity<=_alarmlevel) { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(cmdline,sizeof(cmdline),"%s ALARM %s %i %s \"%s\"",_alarmhandler,timestamp,severity, process_title, text); my_system(cmdline, "alarmhandler"); } } } smstools-3.1.15/src/alarm.h000077500000000000000000000016571223712572200155370ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef ALARM_H #define ALARM_H // Note: Use either the devicename in set_alarmhandler OR alarm_handler but not in both. // Set the unused parameter to "". // Initialize some variables before using alarm_handler void set_alarmhandler(char* handler,int level); // calls the alarm handler void alarm_handler0(int severity, char *text); void alarm_handler(int severity, char* format, ...); #endif smstools-3.1.15/src/blacklist.c000077500000000000000000000031571223712572200164030ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include "blacklist.h" #include "extras.h" #include "logging.h" #include "alarm.h" #include "smsd_cfg.h" int inblacklist(char* msisdn) { FILE* file; char line[256]; char* posi; if (blacklist[0]) // is a blacklist file specified? { file=fopen(blacklist,"r"); if (file) { while (fgets(line,sizeof(line),file)) { posi=strchr(line,'#'); // remove comment if (posi) *posi=0; cutspaces(line); if (strlen(line)>0) { if (strncmp(msisdn,line,strlen(line))==0) { fclose(file); return 1; } else if (msisdn[0]=='s' && strncmp(msisdn+1,line,strlen(line))==0) { fclose(file); return 1; } } } fclose(file); } else { writelogfile0(LOG_CRIT, 0, tb_sprintf("Stopping. Cannot read blacklist file %s.", blacklist)); alarm_handler0(LOG_CRIT, tb); abnormal_termination(1); } } return 0; } smstools-3.1.15/src/blacklist.h000077500000000000000000000012341223712572200164020ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef BLACK_H #define BLACK_H char blacklist[256]; // Filename of the black-list int inblacklist(char* msisdn); #endif smstools-3.1.15/src/cfgfile.c000077500000000000000000000066321223712572200160330ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include "cfgfile.h" #include "extras.h" #include #include #include void cutcomment(char* text) { int laenge; // 3.1.5: Only whole line comments are allowed: //cp=strchr(text,'#'); //if (cp!=0) // *cp=0; while (is_blank(*text)) strcpyo(text, text +1); if (*text == '#') *text = 0; laenge=strlen(text); // 3.1beta7: this was dropping scandinavic characters, unsigned test added: while (laenge > 0 && ((unsigned char)text[laenge -1] <= (unsigned char)' ')) { text[laenge-1]=0; laenge--; } } int getsubparam_delim(char* parameter, int n, char* subparam, int size_subparam, char delim) { int j; char* cp; char* cp2; int len; cp=(char*)parameter; subparam[0]=0; for (j=1; j= size_subparam) return 0; strncpy(subparam,cp,len); subparam[len]=0; cutspaces(subparam); // 3.1.7: if (!(*subparam)) return 0; return 1; } int getsubparam(char* parameter, int n, char* subparam, int size_subparam) { return getsubparam_delim(parameter, n, subparam, size_subparam, ','); } int splitline( char* source, char* name, int size_name, char* value, int size_value) { char* equalchar; int n; equalchar=strchr(source,'='); value[0]=0; name[0]=0; if (equalchar) { strncpy(value,equalchar+1,size_value); value[size_value -1]=0; cutspaces(value); n=equalchar-source; if (n>0) { if (n>size_name-1) n=size_name-1; strncpy(name,source,n); name[n]=0; cutspaces(name); return 1; } } return 0; } int gotosection(FILE* file, char* name) { char line[4096 +32]; char* posi; fseek(file,0,SEEK_SET); while (fgets(line,sizeof(line),file)) { cutcomment(line); if (*line) { posi=strchr(line,']'); if ((line[0]=='[') && posi) {// 3.1beta7: added brackets, should be a block, otherwise name is still tested. *posi=0; if (strcmp(line+1,name)==0) return 1; } } } return 0; } int my_getline(FILE* file, char* name, int size_name, char* value, int size_value) { char line[4096 +32]; while (fgets(line,sizeof(line),file)) { cutcomment(line); // 3.1beta7: lines with one or two illegal characters were not reported: //if (Length>2) if (*line) { if (line[0]=='[') return 0; if (splitline(line,name,size_name,value,size_value)==0) { strncpy(value,line,size_value); value[size_value -1]=0; return -1; } return 1; } } return 0; } smstools-3.1.15/src/cfgfile.h000077500000000000000000000027621223712572200160400ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef CFGFILE_H #define CFGFILE_H #include /* Gets a single parameter from a list of parameters wich uses colons to separate them. Returns 1 if successful. */ int getsubparam_delim(char* parameter, int n, char* subparam, int size_subparam, char delim); int getsubparam(char* parameter, int n, char* subparam, int size_subparam); /* Searches for a section [name] in a config file and goes to the next line. Return 1 if successful. */ int gotosection(FILE* file, char* name); /* Reads the next line from a config file beginning at the actual position. Returns 1 if successful. If the next section or eof is encountered it returns 0. If the file contains syntax error it returns -1 and the wrong line in value.*/ int my_getline(FILE* file, char* name, int size_name, char* value, int size_value); #endif smstools-3.1.15/src/charset.c000077500000000000000000000720451223712572200160660ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #ifdef USE_ICONV #include #include #endif #include "charset.h" #include "logging.h" #include "smsd_cfg.h" #include "pdu.h" #include "extras.h" // For incoming character 0x24 conversion: // Change this if other than Euro character is wanted, like '?' or '$'. #define GSM_CURRENCY_SYMBOL_TO_ISO 0xA4 // For incoming character 0x09 conversion: // (some reference: ftp://www.unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT) // Uncomment this if you want that C-CEDILLA is represented as small c-cedilla: //#define INCOMING_SMALL_C_CEDILLA // iso = ISO8859-15 (you might change the table to any other 8-bit character set) // sms = sms character set used by mobile phones // iso sms char charset[] = { '@' , 0x00, // COMMERCIAL AT 0xA3, 0x01, // POUND SIGN '$' , 0x02, // DOLLAR SIGN 0xA5, 0x03, // YEN SIGN 0xE8, 0x04, // LATIN SMALL LETTER E WITH GRAVE 0xE9, 0x05, // LATIN SMALL LETTER E WITH ACUTE 0xF9, 0x06, // LATIN SMALL LETTER U WITH GRAVE 0xEC, 0x07, // LATIN SMALL LETTER I WITH GRAVE 0xF2, 0x08, // LATIN SMALL LETTER O WITH GRAVE #ifdef INCOMING_SMALL_C_CEDILLA 0xE7, 0x09, // LATIN SMALL LETTER C WITH CEDILLA #else 0xC7, 0x09, // LATIN CAPITAL LETTER C WITH CEDILLA #endif 0x0A, 0x0A, // LF 0xD8, 0x0B, // LATIN CAPITAL LETTER O WITH STROKE 0xF8, 0x0C, // LATIN SMALL LETTER O WITH STROKE 0x0D, 0x0D, // CR 0xC5, 0x0E, // LATIN CAPITAL LETTER A WITH RING ABOVE 0xE5, 0x0F, // LATIN SMALL LETTER A WITH RING ABOVE // ISO8859-7, Capital greek characters // 0xC4, 0x10, // 0x5F, 0x11, // 0xD6, 0x12, // 0xC3, 0x13, // 0xCB, 0x14, // 0xD9, 0x15, // 0xD0, 0x16, // 0xD8, 0x17, // 0xD3, 0x18, // 0xC8, 0x19, // 0xCE, 0x1A, // ISO8859-1, ISO8859-15 0x81, 0x10, // GREEK CAPITAL LETTER DELTA 0x5F, 0x11, // LOW LINE 0x82, 0x12, // GREEK CAPITAL LETTER PHI 0x83, 0x13, // GREEK CAPITAL LETTER GAMMA 0x84, 0x14, // GREEK CAPITAL LETTER LAMDA 0x85, 0x15, // GREEK CAPITAL LETTER OMEGA 0x86, 0x16, // GREEK CAPITAL LETTER PI 0x87, 0x17, // GREEK CAPITAL LETTER PSI 0x88, 0x18, // GREEK CAPITAL LETTER SIGMA 0x89, 0x19, // GREEK CAPITAL LETTER THETA 0x8A, 0x1A, // GREEK CAPITAL LETTER XI 0x1B, 0x1B, // ESC 0xC6, 0x1C, // LATIN CAPITAL LETTER AE 0xE6, 0x1D, // LATIN SMALL LETTER AE 0xDF, 0x1E, // LATIN SMALL LETTER SHARP S 0xC9, 0x1F, // LATIN CAPITAL LETTER E WITH ACUTE ' ' , 0x20, // SPACE '!' , 0x21, // EXCLAMATION MARK 0x22, 0x22, // QUOTATION MARK '#' , 0x23, // NUMBER SIGN // GSM character 0x24 is a "currency symbol". // This character is never sent. Incoming character is converted without conversion tables. '%' , 0x25, // PERSENT SIGN '&' , 0x26, // AMPERSAND 0x27, 0x27, // APOSTROPHE '(' , 0x28, // LEFT PARENTHESIS ')' , 0x29, // RIGHT PARENTHESIS '*' , 0x2A, // ASTERISK '+' , 0x2B, // PLUS SIGN ',' , 0x2C, // COMMA '-' , 0x2D, // HYPHEN-MINUS '.' , 0x2E, // FULL STOP '/' , 0x2F, // SOLIDUS '0' , 0x30, // DIGIT 0...9 '1' , 0x31, '2' , 0x32, '3' , 0x33, '4' , 0x34, '5' , 0x35, '6' , 0x36, '7' , 0x37, '8' , 0x38, '9' , 0x39, ':' , 0x3A, // COLON ';' , 0x3B, // SEMICOLON '<' , 0x3C, // LESS-THAN SIGN '=' , 0x3D, // EQUALS SIGN '>' , 0x3E, // GREATER-THAN SIGN '?' , 0x3F, // QUESTION MARK 0xA1, 0x40, // INVERTED EXCLAMATION MARK 'A' , 0x41, // LATIN CAPITAL LETTER A...Z 'B' , 0x42, 'C' , 0x43, 'D' , 0x44, 'E' , 0x45, 'F' , 0x46, 'G' , 0x47, 'H' , 0x48, 'I' , 0x49, 'J' , 0x4A, 'K' , 0x4B, 'L' , 0x4C, 'M' , 0x4D, 'N' , 0x4E, 'O' , 0x4F, 'P' , 0x50, 'Q' , 0x51, 'R' , 0x52, 'S' , 0x53, 'T' , 0x54, 'U' , 0x55, 'V' , 0x56, 'W' , 0x57, 'X' , 0x58, 'Y' , 0x59, 'Z' , 0x5A, 0xC4, 0x5B, // LATIN CAPITAL LETTER A WITH DIAERESIS 0xD6, 0x5C, // LATIN CAPITAL LETTER O WITH DIAERESIS 0xD1, 0x5D, // LATIN CAPITAL LETTER N WITH TILDE 0xDC, 0x5E, // LATIN CAPITAL LETTER U WITH DIAERESIS 0xA7, 0x5F, // SECTION SIGN 0xBF, 0x60, // INVERTED QUESTION MARK 'a' , 0x61, // LATIN SMALL LETTER A...Z 'b' , 0x62, 'c' , 0x63, 'd' , 0x64, 'e' , 0x65, 'f' , 0x66, 'g' , 0x67, 'h' , 0x68, 'i' , 0x69, 'j' , 0x6A, 'k' , 0x6B, 'l' , 0x6C, 'm' , 0x6D, 'n' , 0x6E, 'o' , 0x6F, 'p' , 0x70, 'q' , 0x71, 'r' , 0x72, 's' , 0x73, 't' , 0x74, 'u' , 0x75, 'v' , 0x76, 'w' , 0x77, 'x' , 0x78, 'y' , 0x79, 'z' , 0x7A, 0xE4, 0x7B, // LATIN SMALL LETTER A WITH DIAERESIS 0xF6, 0x7C, // LATIN SMALL LETTER O WITH DIAERESIS 0xF1, 0x7D, // LATIN SMALL LETTER N WITH TILDE 0xFC, 0x7E, // LATIN SMALL LETTER U WITH DIAERESIS 0xE0, 0x7F, // LATIN SMALL LETTER A WITH GRAVE // Moved to the special char handling: // 0x60, 0x27, // GRAVE ACCENT // 0xE1, 0x61, // replacement for accented a // 0xED, 0x69, // replacement for accented i // 0xF3, 0x6F, // replacement for accented o // 0xFA, 0x75, // replacement for accented u 0 , 0 // End marker }; // Extended characters. In GSM they are preceeded by 0x1B. char ext_charset[] = { 0x0C, 0x0A, // '^' , 0x14, // CIRCUMFLEX ACCENT '{' , 0x28, // LEFT CURLY BRACKET '}' , 0x29, // RIGHT CURLY BRACKET '\\', 0x2F, // REVERSE SOLIDUS '[' , 0x3C, // LEFT SQUARE BRACKET '~' , 0x3D, // TILDE ']' , 0x3E, // RIGHT SQUARE BRACKET 0x7C, 0x40, // VERTICAL LINE 0xA4, 0x65, // EURO SIGN 0 , 0 // End marker }; // This table is used for outgoing (to GSM) conversion only: char iso_8859_15_chars[] = { 0x60, 0x27, // GRAVE ACCENT --> APOSTROPHE 0xA0, 0x20, // NO-BREAK SPACE --> SPACE 0xA2, 0x63, // CENT SIGN --> c 0xA6, 0x53, // LATIN CAPITAL LETTER S WITH CARON --> S 0xA8, 0x73, // LATIN SMALL LETTER S WITH CARON --> s 0xA9, 0x43, // COPYRIGHT SIGN --> C 0xAA, 0x61, // FEMININE ORDINAL INDICATOR --> a 0xAB, 0x3C, // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK --> < 0xAC, 0x2D, // NOT SIGN --> - 0xAD, 0x2D, // SOFT HYPHEN --> - 0xAE, 0x52, // REGISTERED SIGN --> R 0xAF, 0x2D, // MACRON --> - 0xB0, 0x6F, // DEGREE SIGN --> o 0xB1, 0x2B, // PLUS-MINUS SIGN --> + 0xB2, 0x32, // SUPERSCRIPT TWO --> 2 0xB3, 0x33, // SUPERSCRIPT THREE --> 3 0xB4, 0x5A, // LATIN CAPITAL LETTER Z WITH CARON --> Z 0xB5, 0x75, // MICRO SIGN --> u 0xB6, 0x49, // PILCROW SIGN --> I 0xB7, 0x2E, // MIDDLE DOT --> . 0xB8, 0x7A, // LATIN SMALL LETTER Z WITH CARON --> z 0xB9, 0x31, // SUPERSCRIPT ONE --> 1 0xBA, 0x6F, // MASCULINE ORDINAL INDICATOR --> o 0xBB, 0x3E, // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK --> > 0xBC, 0x4F, // LATIN CAPITAL LIGATURE OE --> O 0xBD, 0x6F, // LATIN SMALL LIGATURE OE --> o 0xBE, 0x59, // LATIN CAPITAL LETTER Y WITH DIAERESIS --> Y 0xC0, 0x41, // LATIN CAPITAL LETTER A WITH GRAVE --> A 0xC1, 0x41, // LATIN CAPITAL LETTER A WITH ACUTE --> A 0xC2, 0x41, // LATIN CAPITAL LETTER A WITH CIRCUMFLEX --> A 0xC3, 0x41, // LATIN CAPITAL LETTER A WITH TILDE --> A 0xC7, 0x09, // LATIN CAPITAL LETTER C WITH CEDILLA --> 0x09 (LATIN CAPITAL LETTER C WITH CEDILLA) 0xC8, 0x45, // LATIN CAPITAL LETTER E WITH GRAVE --> E 0xCA, 0x45, // LATIN CAPITAL LETTER E WITH CIRCUMFLEX --> E 0xCB, 0x45, // LATIN CAPITAL LETTER E WITH DIAERESIS --> E 0xCC, 0x49, // LATIN CAPITAL LETTER I WITH GRAVE --> I 0xCD, 0x49, // LATIN CAPITAL LETTER I WITH ACUTE --> I 0xCE, 0x49, // LATIN CAPITAL LETTER I WITH CIRCUMFLEX --> I 0xCF, 0x49, // LATIN CAPITAL LETTER I WITH DIAERESIS --> I 0xD0, 0x44, // LATIN CAPITAL LETTER ETH --> D 0xD2, 0x4F, // LATIN CAPITAL LETTER O WITH GRAVE --> O 0xD3, 0x4F, // LATIN CAPITAL LETTER O WITH ACUTE --> O 0xD4, 0x4F, // LATIN CAPITAL LETTER O WITH CIRCUMFLEX --> O 0xD5, 0x4F, // LATIN CAPITAL LETTER O WITH TILDE --> O 0xD7, 0x78, // MULTIPLICATION SIGN --> x 0xD9, 0x55, // LATIN CAPITAL LETTER U WITH GRAVE --> U 0xDA, 0x55, // LATIN CAPITAL LETTER U WITH ACUTE --> U 0xDB, 0x55, // LATIN CAPITAL LETTER U WITH CIRCUMFLEX --> U 0xDD, 0x59, // LATIN CAPITAL LETTER Y WITH ACUTE --> Y 0xDE, 0x62, // LATIN CAPITAL LETTER THORN --> b 0xE1, 0x61, // LATIN SMALL LETTER A WITH ACUTE --> a 0xE2, 0x61, // LATIN SMALL LETTER A WITH CIRCUMFLEX --> a 0xE3, 0x61, // LATIN SMALL LETTER A WITH TILDE --> a 0xE7, 0x09, // LATIN SMALL LETTER C WITH CEDILLA --> LATIN CAPITAL LETTER C WITH CEDILLA 0xEA, 0x65, // LATIN SMALL LETTER E WITH CIRCUMFLEX --> e 0xEB, 0x65, // LATIN SMALL LETTER E WITH DIAERESIS --> e 0xED, 0x69, // LATIN SMALL LETTER I WITH ACUTE --> i 0xEE, 0x69, // LATIN SMALL LETTER I WITH CIRCUMFLEX --> i 0xEF, 0x69, // LATIN SMALL LETTER I WITH DIAERESIS --> i 0xF0, 0x6F, // LATIN SMALL LETTER ETH --> o 0xF3, 0x6F, // LATIN SMALL LETTER O WITH ACUTE --> o 0xF4, 0x6F, // LATIN SMALL LETTER O WITH CIRCUMFLEX --> o 0xF5, 0x6F, // LATIN SMALL LETTER O WITH TILDE --> o 0xF7, 0x2F, // DIVISION SIGN --> / (SOLIDUS) 0xFA, 0x75, // LATIN SMALL LETTER U WITH ACUTE --> u 0xFB, 0x75, // LATIN SMALL LETTER U WITH CIRCUMFLEX --> u 0xFD, 0x79, // LATIN SMALL LETTER Y WITH ACUTE --> y 0xFE, 0x62, // LATIN SMALL LETTER THORN --> b 0xFF, 0x79, // LATIN SMALL LETTER Y WITH DIAERESIS --> y 0 , 0 }; #ifdef USE_ICONV static iconv_t iconv4ucs; // UCS2->UTF8 descriptor static iconv_t iconv2ucs; // UTF8->UCS2 descriptor #endif int special_char2gsm(char ch, char *newch) { int table_row = 0; char *table = iso_8859_15_chars; while (table[table_row *2]) { if (table[table_row *2] == ch) { if (newch) *newch = table[table_row *2 +1]; return 1; } table_row++; } return 0; } // Return value: // 0 = ch not found. // 1 = ch found from normal table // 2 = ch found from extended table int char2gsm(char ch, char *newch) { int result = 0; int table_row; // search in normal translation table table_row=0; while (charset[table_row*2]) { if (charset[table_row*2] == ch) { if (newch) *newch = charset[table_row*2+1]; result = 1; break; } table_row++; } // if not found in normal table, then search in the extended table if (result == 0) { table_row=0; while (ext_charset[table_row*2]) { if (ext_charset[table_row*2] == ch) { if (newch) *newch = ext_charset[table_row*2+1]; result = 2; break; } table_row++; } } return result; } int gsm2char(char ch, char *newch, int which_table) { int table_row = 0; char *table; if (which_table == 1) table = charset; else if (which_table == 2) table = ext_charset; else return 0; while (table[table_row *2]) { if (table[table_row *2 +1] == ch) { *newch = table[table_row *2]; return 1; } table_row++; } return 0; } int iso_utf8_2gsm(char* source, int size, char* destination, int max) { int source_count=0; int dest_count=0; int found=0; char newch; char logtmp[51]; char tmpch; destination[dest_count]=0; if (source==0 || size <= 0) return 0; #ifdef DEBUGMSG log_charconv = 1; #endif if (log_charconv) { *logch_buffer = 0; logch("!! iso_utf8_2gsm(source=%.*s, size=%i)", size, source, size); logch(NULL); } // Convert each character until end of string while (source_count= max -2) break; destination[dest_count++] = 0x1B; } if (found >= 1) { destination[dest_count++] = newch; if (log_charconv) { sprintf(logtmp, "%02X[%c]", (unsigned char)source[source_count], prch(source[source_count])); if (found > 1 || source[source_count] != newch) { sprintf(strchr(logtmp, 0), "->%s%02X", (found == 2)? "Esc-" : "", (unsigned char)newch); if (gsm2char(newch, &tmpch, found)) sprintf(strchr(logtmp, 0), "[%c]", tmpch); } logch("%s ", logtmp); } } if (found == 0 && outgoing_utf8) { // ASCII and UTF-8 table: http://members.dslextreme.com/users/kkj/webtools/ascii_utf8_table.html // Good converter: http://www.macchiato.com/unicode/convert.html unsigned int c; int iterations = 0; // 3.1beta7: If UTF-8 decoded character is not found from tables, decoding is ignored: int saved_source_count = source_count; char sourcechars[51]; c = source[source_count]; if (log_charconv) sprintf(sourcechars, "%02X", (unsigned char)source[source_count]); // 3.1beta7: Check if there is enough characters left. // Following bytes in UTF-8 should begin with 10xx xxxx // which means 0x80 ... 0xBF if (((c & 0xFF) >= 0xC2 && (c & 0xFF) <= 0xC7) || ((c & 0xFF) >= 0xD0 && (c & 0xFF) <= 0xD7)) { if (source_count < size -1 && (source[source_count +1] & 0xC0) == 0x80) { // 110xxxxx c &= 0x1F; iterations = 1; } } else if ((c & 0xFF) >= 0xE0 && (c & 0xFF) <= 0xE7) { if (source_count < size -2 && (source[source_count +1] & 0xC0) == 0x80 && (source[source_count +2] & 0xC0) == 0x80) { // 1110xxxx c &= 0x0F; iterations = 2; } } else if ((c & 0xFF) >= 0xF0 && (c & 0xFF) <= 0xF4) { if (source_count < size -3 && (source[source_count +1] & 0xC0) == 0x80 && (source[source_count +2] & 0xC0) == 0x80 && (source[source_count +3] & 0xC0) == 0x80) { // 11110xxx c &= 0x07; iterations = 3; } } if (iterations > 0) { int i; for (i = 0; i < iterations; i++) { c = (c << 6) | (source[++source_count] -0x80); if (log_charconv) sprintf(strchr(sourcechars, 0), "%02X", (unsigned char)source[source_count]); } // Euro character is 20AC in UTF-8, but A4 in ISO-8859-15: if ((c & 0xFF) == 0xAC) c = 0xA4; found = char2gsm((char)c, &newch); if (found == 2) { if (dest_count >= max -2) break; destination[dest_count++] = 0x1B; } if (found >= 1) { destination[dest_count++] = newch; if (log_charconv) { sprintf(logtmp, "%s(%02X[%c])->%s%02X", sourcechars, (unsigned char)c, prch(c), (found == 2)? "Esc-" : "", (unsigned char)newch); if (gsm2char(newch, &tmpch, found)) sprintf(strchr(logtmp, 0), "[%c]", tmpch); logch("%s ", logtmp); } } else { found = special_char2gsm((char)c, &newch); if (found) { destination[dest_count++] = newch; if (log_charconv) { sprintf(logtmp, "%s(%02X[%c])~>%02X", sourcechars, (unsigned char)c, prch(c), (unsigned char)newch); if (gsm2char(newch, &tmpch, 1)) sprintf(strchr(logtmp, 0), "[%c]", tmpch); logch("%s ", logtmp); } } else source_count = saved_source_count; } } } // 3.1beta7: Try additional table: if (found == 0) { found = special_char2gsm(source[source_count], &newch); if (found) { destination[dest_count++] = newch; if (log_charconv) { sprintf(logtmp, "%02X[%c]~>%02X", (unsigned char)source[source_count], prch(source[source_count]), (unsigned char)newch); if (gsm2char(newch, &tmpch, 1)) sprintf(strchr(logtmp, 0), "[%c]", tmpch); logch("%s ", logtmp); } } } if (found==0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Cannot convert %i. character %c 0x%2X to GSM, you might need to update the translation tables.", source_count +1, source[source_count], source[source_count])); #ifdef DEBUGMSG printf("%s\n", tb); #endif } source_count++; } if (log_charconv) logch(NULL); // Terminate destination string with 0, however 0x00 are also allowed within the string. destination[dest_count]=0; return dest_count; } // Outputs to the file. Return value: 0 = ok, -1 = error. int iso2utf8_file(FILE *fp, char *ascii, int userdatalength) { int result = 0; int idx; unsigned int c; char tmp[10]; int len; char logtmp[51]; int i; if (!fp || userdatalength < 0) return -1; #ifdef DEBUGMSG log_charconv = 1; #endif if (log_charconv) { *logch_buffer = 0; logch("!! iso2utf8_file(..., userdatalength=%i)", userdatalength); logch(NULL); } for (idx = 0; idx < userdatalength; idx++) { len = 0; c = ascii[idx] & 0xFF; // Euro character is 20AC in UTF-8, but A4 in ISO-8859-15: if (c == 0xA4) c = 0x20AC; if (c <= 0x7F) tmp[len++] = (char)c; else if (c <= 0x7FF) { tmp[len++] = (char)( 0xC0 | ((c >> 6) & 0x1F) ); tmp[len++] = (char)( 0x80 | (c & 0x3F) ); } else if (c <= 0x7FFF) // or <= 0xFFFF ? { tmp[len++] = (char)( 0xE0 | ((c >> 12) & 0x0F) ); tmp[len++] = (char)( 0x80 | ((c >> 6) & 0x3F) ); tmp[len++] = (char)( 0x80 | (c & 0x3F) ); } if (len == 0) { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("UTF-8 conversion error with %i. ch 0x%2X %c.", idx +1, c, (char)c)); #ifdef DEBUGMSG printf("%s\n", tb); #endif } else { if (log_charconv) { sprintf(logtmp, "%02X[%c]", (unsigned char)ascii[idx], prch(ascii[idx])); if (len > 1 || ascii[idx] != tmp[0]) { strcat(logtmp, "->"); for (i = 0; i < len; i++) sprintf(strchr(logtmp, 0), "%02X", (unsigned char)tmp[i]); } logch("%s ", logtmp); } if (fwrite(tmp, 1, len, fp) != (size_t)len) { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("Fatal file write error in UTF-8 conversion")); #ifdef DEBUGMSG printf("%s\n", tb); #endif result = -1; break; } } } if (log_charconv) logch(NULL); return result; } int gsm2iso(char* source, int size, char* destination, int max) { int source_count=0; int dest_count=0; char newch; if (source==0 || size==0) { destination[0]=0; return 0; } // Convert each character untl end of string while (source_count= 3) if (strcmp(text +strlen(text) -3, "20 ") == 0) flush = 1; // Incoming conversion: if (!flush) if (strlen(text) >= 6) if (strcmp(text +strlen(text) -6, "20[ ] ") == 0) flush = 1; // Line wrap after a reasonable length reached: if (!flush) if (strlen(logch_buffer) > 80) flush = 1; } #ifdef DEBUGMSG printf("%s", text); #endif } else flush = 1; if (flush) { if (*logch_buffer) writelogfile(LOG_DEBUG, 0, "charconv: %s", logch_buffer); *logch_buffer = 0; #ifdef DEBUGMSG printf("\n"); #endif } } char prch(char ch) { if ((unsigned char)ch >= ' ') return ch; return '.'; } //hdr -------------------------------------------------------------------------------- int iso2utf8( // // Converts to the buffer. Returns -1 in case of error, >= 0 = length of dest. // char *ascii, int userdatalength, size_t ascii_size ) { int result = 0; int idx; unsigned int c; char tmp[10]; int len; char logtmp[51]; int i; char *buffer; if (userdatalength < 0) return -1; if (!(buffer = (char *) malloc(ascii_size))) return -1; #ifdef DEBUGMSG log_charconv = 1; #endif if (log_charconv) { *logch_buffer = 0; logch("!! iso2utf8(..., userdatalength=%i)", userdatalength); logch(NULL); } for (idx = 0; idx < userdatalength; idx++) { len = 0; c = ascii[idx] & 0xFF; // Euro character is 20AC in UTF-8, but A4 in ISO-8859-15: if (c == 0xA4) c = 0x20AC; if (c <= 0x7F) tmp[len++] = (char) c; else if (c <= 0x7FF) { tmp[len++] = (char) (0xC0 | ((c >> 6) & 0x1F)); tmp[len++] = (char) (0x80 | (c & 0x3F)); } else if (c <= 0x7FFF) // or <= 0xFFFF ? { tmp[len++] = (char) (0xE0 | ((c >> 12) & 0x0F)); tmp[len++] = (char) (0x80 | ((c >> 6) & 0x3F)); tmp[len++] = (char) (0x80 | (c & 0x3F)); } if (len == 0) { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("UTF-8 conversion error with %i. ch 0x%2X %c.", idx + 1, c, (char) c)); #ifdef DEBUGMSG printf("%s\n", tb); #endif } else { if (log_charconv) { sprintf(logtmp, "%02X[%c]", (unsigned char) ascii[idx], prch(ascii[idx])); if (len > 1 || ascii[idx] != tmp[0]) { strcat(logtmp, "->"); for (i = 0; i < len; i++) sprintf(strchr(logtmp, 0), "%02X", (unsigned char) tmp[i]); } logch("%s ", logtmp); } if ((size_t) (result + len) < ascii_size - 1) { strncpy(buffer + result, tmp, len); result += len; } else { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("Fatal error (buffer too small) in UTF-8 conversion")); #ifdef DEBUGMSG printf("%s\n", tb); #endif result = -1; break; } } } if (log_charconv) logch(NULL); if (result >= 0) { memcpy(ascii, buffer, result); ascii[result] = 0; } free(buffer); return result; } //hdr -------------------------------------------------------------------------------- int encode_7bit_packed( // // Encodes a string to GSM 7bit (USSD) packed format. // Returns number of septets. // Handles padding as defined on GSM 03.38 version 5.6.1 (ETS 300 900) page 17. // char *text, char *dest, size_t size_dest ) { int len; int i; char buffer[512]; char buffer2[512]; char padding = '\r'; //len = iso_utf8_2gsm(text, strlen(text), buffer2, sizeof(buffer2), ALPHABET_AUTO, 0); len = iso_utf8_2gsm(text, strlen(text), buffer2, sizeof(buffer2)); #ifdef DEBUGMSG printf("characters: %i\n", len); printf("characters %% 8: %i\n", len % 8); #endif if ((len % 8 == 7) || (len % 8 == 0 && len && buffer2[len - 1] == padding)) { if ((size_t) len < sizeof(buffer2) - 1) { buffer2[len++] = padding; #ifdef DEBUGMSG printf("adding padding, characters: %i\n", len); #endif } } i = text2pdu(buffer2, len, buffer, 0); snprintf(dest, size_dest, "%s", buffer); #ifdef DEBUGMSG printf("octets: %i\n", strlen(buffer) / 2); for (len = 0; buffer[len]; len += 2) printf("%.2s ", buffer + len); printf("\n"); #endif return i; } //hdr -------------------------------------------------------------------------------- int decode_7bit_packed( // // Decodes GSM 7bit (USSD) packed string. // Returns length of dest. -1 in the case or error and "ERROR" in dest. // Handles padding as defined on GSM 03.38 version 5.6.1 (ETS 300 900) page 17. // char *text, char *dest, size_t size_dest ) { int len; int i; char buffer[512]; char buffer2[512]; char *p; int septets; int padding = '\r'; snprintf(buffer, sizeof(buffer), "%s", text); while ((p = strchr(buffer, ' '))) strcpyo(p, p + 1); for (i = 0; buffer[i]; i++) buffer[i] = toupper((int) buffer[i]); i = strlen(buffer); if (i % 2) { snprintf(dest, size_dest, "ERROR"); return -1; } septets = i / 2 * 8 / 7; snprintf(buffer2, sizeof(buffer2), "%02X%s", septets, buffer); #ifdef DEBUGMSG printf("septets: %i (0x%02X)\n", septets, septets); printf("septets %% 8: %i\n", septets % 8); printf("%s\n", buffer2); #endif memset(buffer, 0, sizeof(buffer)); pdu2text(buffer2, buffer, &len, 0, 0, 0, 0, 0); if ((septets % 8 == 0 && len && buffer[len - 1] == padding) || (septets % 8 == 1 && len > 1 && buffer[len - 1] == padding && buffer[len - 2] == padding)) { len--; #ifdef DEBUGMSG printf("removing padding, characters: %i\n", len); #endif } i = gsm2iso(buffer, len, buffer2, sizeof(buffer2)); if (incoming_utf8) i = iso2utf8(buffer2, i, sizeof(buffer2)); snprintf(dest, size_dest, "%s", buffer2); return i; } // ****************************************************************************** #ifdef USE_ICONV int iconv_init(void) { // do noy use 'UTF8' alias - it not supported in cygwin/mingw return (iconv4ucs = iconv_open("UTF-8", "UCS-2BE")) != (iconv_t)-1 && (iconv2ucs = iconv_open("UCS-2BE", "UTF-8")) != (iconv_t)-1; } static size_t iconv_convert(iconv_t cd, char *buf, size_t* ilen, size_t maxlen) { char tmp[MAXTEXT], *out, *in; size_t olen, rc; const char* err; if (!maxlen || !ilen || !*ilen || !buf) return 0; // reset conversion descriptor iconv(cd, NULL, NULL, NULL, NULL); err = NULL; in = buf; out = tmp; olen = sizeof(tmp); rc = iconv(cd, &in, ilen, &out, &olen); if (rc == (size_t)-1) { switch (errno) { case E2BIG: err = "Buffer to small"; break; case EILSEQ: err = "Invalid sequnce"; break; case EINVAL: err = "Incomplete sequence"; break; default: err = strerror(errno); break; } } olen = sizeof(tmp) - olen; if (olen >= maxlen) { err = "output buffer too small"; olen = maxlen; } memcpy(buf, tmp, olen); if (err != NULL) writelogfile(LOG_NOTICE, 0, "Unicode conversion error: %s", err); return olen; } size_t iconv_utf2ucs(char *buf, size_t len, size_t maxlen) { return iconv_convert(iconv2ucs, buf, &len, maxlen); } size_t iconv_ucs2utf(char *buf, size_t len, size_t maxlen) { return iconv_convert(iconv4ucs, buf, &len, maxlen); } size_t iconv_ucs2utf_chk(char *buf, size_t len, size_t maxlen) { size_t olen, ilen = len; olen = iconv_convert(iconv4ucs, buf, &ilen, maxlen); buf[olen] = 0; if (ilen != 0) ilen = (len - ilen + 1) / 2; return ilen; } int is_ascii_gsm(char* buf, size_t len) { char tmp; size_t i; for (i = 0; i < len; i++) if (buf[i] < ' ' || char2gsm(buf[i], &tmp) != 1) return 0; return 1; } #endif // USE_ICONV smstools-3.1.15/src/charset.h000077500000000000000000000034361223712572200160710ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef CHARSET_H #define CHARSET_H char logch_buffer[8192]; // Logging is not used externally, but it's placed to the end of source file. void logch(char* format, ...); char prch(char ch); // Both functions return the size of the converted string // max limits the number of characters to be written into // destination // size is the size of the source string // max is the maximum size of the destination string // The GSM character set contains 0x00 as a valid character int gsm2iso(char* source, int size, char* destination, int max); int iso_utf8_2gsm(char* source, int size, char* destination, int max); int iso2utf8_file(FILE *fp, char *ascii, int userdatalength); //int iso2gsm(char* source, int size, char* destination, int max); //int unicode2sms(char* source, int size, char* destination, int max); int decode_7bit_packed(char *text, char *dest, size_t size_dest); int encode_7bit_packed(char *text, char *dest, size_t size_dest); #ifndef USE_ICONV int decode_ucs2(char *buffer, int len); #else int iconv_init(void); size_t iconv_utf2ucs(char* buf, size_t len, size_t maxlen); size_t iconv_ucs2utf(char* buf, size_t len, size_t maxlen); size_t iconv_ucs2utf_chk(char *buf, size_t len, size_t maxlen); int is_ascii_gsm(char* buf, size_t len); #endif #endif smstools-3.1.15/src/extras.c000077500000000000000000001070141223712572200157360ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "extras.h" #include "locking.h" #include "smsd_cfg.h" #include "logging.h" #include "alarm.h" int yesno(char *value) { extern char yes_chars[]; extern char no_chars[]; char *p, *p2; if (*yes_chars) { p = yes_chars; while (*p) { if (!(p2 = strchr(p, '\''))) break; if (!strncmp(value, p, (int)(p2 -p))) return 1; p = p2 +1; } } if (*no_chars) { p = no_chars; while (*p) { if (!(p2 = strchr(p, '\''))) break; if (!strncmp(value, p, (int)(p2 -p))) return 0; p = p2 +1; } } if ((value[0]=='1') || (value[0]=='y') || (value[0]=='Y') || (value[0]=='t') || (value[0]=='T') || ((value[1]=='n') && ( (value[0]=='o') || (value[0]=='O')) )) return 1; else return 0; } int yesno_check(char* value) { // This version is used to check config file values. int result = -1; if ((value[0]=='1') || (value[0]=='y') || (value[0]=='Y') || (value[0]=='t') || (value[0]=='T') || ((value[1]=='n') && ( (value[0]=='o') || (value[0]=='O')) )) result = 1; else if ((value[0]=='0') || (value[0]=='n') || (value[0]=='N') || (value[0]=='f') || (value[0]=='F') || ((value[1]=='f') && ( (value[0]=='o') || (value[0]=='O')) )) result = 0; return result; } char *cut_ctrl(char* message) /* removes all ctrl chars */ { // 3.0.9: use dynamic buffer to avoid overflow: //char tmp[500]; char *tmp; int posdest=0; int possource; int count; count=strlen(message); if ((tmp = (char *)malloc(count +1))) { for (possource=0; possource<=count; possource++) { // 3.1beta7: added unsigned test: if (((unsigned char)message[possource] >= (unsigned char)' ') || (message[possource]==0)) tmp[posdest++]=message[possource]; } strcpy(message,tmp); free(tmp); } return message; } char *cut_crlf(char *st) { while (*st && strchr("\r\n", st[strlen(st) -1])) st[strlen(st) -1] = 0; return st; } int is_blank(char c) { return (c==9) || (c==32); } int line_is_blank(char *line) { int i = 0; while (line[i]) if (strchr("\t \r\n", line[i])) i++; else break; return(line[i] == 0); } int movefile( char* filename, char* directory) { char newname[PATH_MAX]; char storage[1024]; int source,dest; int readcount; char* cp; struct stat statbuf; if (stat(filename,&statbuf)<0) statbuf.st_mode=0640; statbuf.st_mode&=07777; cp=strrchr(filename,'/'); if (cp) sprintf(newname,"%s%s",directory,cp); else sprintf(newname,"%s/%s",directory,filename); source=open(filename,O_RDONLY); if (source>=0) { dest=open(newname,O_WRONLY|O_CREAT|O_TRUNC,statbuf.st_mode); if (dest>=0) { //while ((readcount=read(source,&storage,sizeof(storage)))>0) // if (write(dest,&storage,readcount) 0) if (write(dest, storage, readcount) < readcount) { close(dest); close(source); return 0; } close(dest); close(source); unlink(filename); return 1; } else { close(source); return 0; } } else return 0; } // 3.1beta7: Return values: // 0 = OK. // 1 = lockfile cannot be created. It exists. // 2 = file copying failed. // 3 = lockfile removing failed. int movefilewithdestlock_new(char* filename, char* directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename) { if (newfilename) *newfilename = 0; if (keep_fname) { char lockfilename[PATH_MAX]; char* cp; //create lockfilename in destination cp=strrchr(filename,'/'); if (cp) sprintf(lockfilename,"%s%s",directory,cp); else sprintf(lockfilename,"%s/%s",directory,filename); //create lock and move file if (!lockfile(lockfilename)) return 1; if (!movefile(filename,directory)) { unlockfile(lockfilename); return 2; } if (!unlockfile(lockfilename)) return 3; if (newfilename) strcpy(newfilename, lockfilename); return 0; } else { // A new unique name is created to the destination directory. char newname[PATH_MAX]; int result = 0; char line[1024]; int in_headers = 1; FILE *fp; FILE *fpnew; size_t n; char *p; extern const char *HDR_OriginalFilename; extern char HDR_OriginalFilename2[]; strcpy(line, prefix); if (*line) strcat(line, "."); sprintf(newname,"%s/%sXXXXXX", directory, line); close(mkstemp(newname)); if (!lockfile(newname)) result = 1; unlink(newname); if (!result) { if (!(fpnew = fopen(newname, "w"))) result = 2; else { if (!(fp = fopen(filename, "r"))) { fclose(fpnew); unlink(newname); result = 2; } else { while (in_headers && fgets(line, sizeof(line), fp)) { if (line_is_blank(line)) { if (store_original_fname && *HDR_OriginalFilename2 != '-') { p = strrchr(filename, '/'); fprintf(fpnew, "%s %s\n", (*HDR_OriginalFilename2)? HDR_OriginalFilename2 : HDR_OriginalFilename, (p)? p +1 : filename); } in_headers = 0; } fwrite(line, 1, strlen(line), fpnew); } while ((n = fread(line, 1, sizeof(line), fp)) > 0) fwrite(line, 1, n, fpnew); fclose(fpnew); fclose(fp); } } } if (!unlockfile(newname)) { unlink(newname); if (!result) result = 3; } else { unlink(filename); if (newfilename) strcpy(newfilename, newname); } return result; } } char *cutspaces(char *text) { int count; int Length; int i; int omitted; /* count ctrl chars and spaces at the beginning */ count=0; while ((text[count]!=0) && ((is_blank(text[count])) || (iscntrl((int)text[count]))) ) count++; /* remove ctrl chars at the beginning and \r within the text */ omitted=0; Length=strlen(text); for (i=0; i<=(Length-count); i++) if (text[i+count]=='\r') omitted++; else text[i-omitted]=text[i+count]; Length=strlen(text); while ((Length>0) && ((is_blank(text[Length-1])) || (iscntrl((int)text[Length-1])))) { text[Length-1]=0; Length--; } return text; } char *cut_emptylines(char *text) { char* posi; char* found; posi=text; while (posi[0] && (found=strchr(posi,'\n'))) { if ((found[1]=='\n') || (found==text)) memmove(found,found+1,strlen(found)); else posi++; } return text; } int is_number( char* text) { int i; int Length; Length=strlen(text); for (i=0; i'9') || (text[i]<'0')) && (text[i]!='-')) return 0; return 1; } int is_highpriority(char *filename) { FILE *fp; char line[256]; int result = 0; // 3.1beta7: language settings used: extern const char *HDR_Priority; extern char HDR_Priority2[]; int hlen; int hlen2 = 0; char *compare; char *compare2 = 0; if (ignore_outgoing_priority || spool_directory_order) return 0; // get_header() and test_header() can be moved to this file, // but this is faster: if (*HDR_Priority2 && strcmp(HDR_Priority2, "-")) { if (*HDR_Priority2 == '-') compare2 = HDR_Priority2 +1; else compare2 = HDR_Priority2; hlen2 = strlen(compare2); } compare = (char *)HDR_Priority; hlen = strlen(compare); if ((fp = fopen(filename, "r"))) { while (!result && fgets(line, sizeof(line), fp)) { if (line_is_blank(line)) break; if ((compare2 && strncmp(line, compare2, hlen2) == 0) || strncmp(line, compare, hlen) == 0) { cutspaces(strcpyo(line, line + hlen)); if (!strcasecmp(line,"HIGH")) result = 1; else if (yesno(line) == 1) result = 1; } } fclose(fp); } return result; } int file_is_writable(char *filename) { int result = 0; FILE *fp; struct stat statbuf; // 3.1.12: First check that the file exists: if (stat(filename, &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode) == 0) { if ((fp = fopen(filename, "a"))) { result = 1; fclose(fp); } } } return result; } int getpdufile(char *filename) { int result = 0; struct stat statbuf; DIR* dirdata; struct dirent* ent; char tmpname[PATH_MAX]; if (*filename) { if (filename[strlen(filename) -1] != '/') { if (file_is_writable(filename)) result = 1; } else if (!strchr(filename, '.')) { if (stat(filename, &statbuf) == 0) { if (S_ISDIR(statbuf.st_mode)) { if ((dirdata = opendir(filename))) { while ((ent = readdir(dirdata))) { if (ent->d_name[0] != '.') { sprintf(tmpname, "%s%s", filename, ent->d_name); if (file_is_writable(tmpname)) { strcpy(filename, tmpname); result = 1; break; } } } // 3.1.1: //close the directory. added by Callan Fox to fix open handles problem closedir(dirdata); } } } } } return result; } int getfile(int trust_directory, char *dir, char *filename, int lock) { DIR* dirdata; struct dirent* ent; struct stat statbuf; int found=0; time_t mtime; char fname[PATH_MAX]; char tmpname[PATH_MAX]; int found_highpriority; int i; char storage_key[PATH_MAX +3]; unsigned long long start_time; // 3.1.12: Collect filenames: typedef struct { char fname[NAME_MAX + 1]; time_t mtime; } _candidate; _candidate candidates[NUMBER_OF_MODEMS]; int lost_count = 0; int files_count; int locked_count; #ifdef DEBUGMSG printf("!! getfile(dir=%s, ...)\n", dir); #endif start_time = time_usec(); // 3.1.12: if a file is lost, try a new search immediately. while (1) { if (terminate) break; // Oldest file is searched. With heavy traffic the first file found is not necesssary the oldest one. if (!(dirdata = opendir(dir))) { // Something has happened to dir after startup check was done successfully. writelogfile0(LOG_CRIT, 0, tb_sprintf("Stopping. Cannot open dir %s %s", dir, strerror(errno))); alarm_handler0(LOG_CRIT, tb); abnormal_termination(1); } mtime = 0; files_count = 0; locked_count = 0; found_highpriority = 0; memset(candidates, 0, sizeof(candidates)); while ((ent = readdir(dirdata))) { #ifdef DEBUGMSG printf("**readdir(): %s\n", ent->d_name); #endif sprintf(tmpname, "%s/%s", dir, ent->d_name); // 3.1.12: //stat(tmpname, &statbuf); if (stat(tmpname, &statbuf) != 0) continue; if (S_ISDIR(statbuf.st_mode) != 0) /* Is this a directory? */ continue; // 3.1.7: //if (strcmp(tmpname + strlen(tmpname) - 5, ".LOCK") != 0) i = 1; if (strlen(tmpname) >= 5 && !strcmp(tmpname + strlen(tmpname) - 5, ".LOCK")) i = 0; else if (!strncmp(tmpname, "LOCKED", 6)) i = 0; if (!i) { locked_count++; continue; } files_count++; // 3.1.12: //if (trust_directory || !islocked(tmpname)) {... if (islocked(tmpname)) continue; sprintf(storage_key, "*%s*\n", tmpname); // 3.1beta7, 3.0.10: if (os_cygwin) if (!check_access(tmpname)) chmod(tmpname, 0766); if (!trust_directory && !os_cygwin && !file_is_writable(tmpname)) { // Try to fix permissions. int result = 1; char tmp_filename[PATH_MAX +7]; FILE *fp; FILE *fptmp; char buffer[1024]; size_t n; snprintf(tmp_filename, sizeof(tmp_filename), "%s.XXXXXX", tmpname); close(mkstemp(tmp_filename)); unlink(tmp_filename); if ((fptmp = fopen(tmp_filename, "w")) == NULL) result = 0; else { if ((fp = fopen(tmpname, "r")) == NULL) result = 0; else { while ((n = fread(buffer, 1, sizeof(buffer), fp)) > 0) fwrite(buffer, 1, n, fptmp); fclose(fp); } fclose(fptmp); if (result) { unlink(tmpname); rename(tmp_filename, tmpname); } else unlink(tmp_filename); } } if (!trust_directory && !file_is_writable(tmpname)) { int report = 1; char reason[100]; if (!check_access(tmpname)) { snprintf(reason, sizeof(reason), "%s", "Access denied. Check the file and directory permissions."); if (getfile_err_store) if (strstr(getfile_err_store, storage_key)) report = 0; if (report) { strcat_realloc(&getfile_err_store, storage_key, 0); writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot handle %s: %s", tmpname, reason)); alarm_handler0(LOG_ERR, tb); } } else { // 3.1.5: This error is repeated: snprintf(reason, sizeof(reason), "%s", "Dont know why. Check the file and directory permissions."); writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot handle %s: %s", tmpname, reason)); alarm_handler0(LOG_ERR, tb); } } else { // Forget previous error with this file: if (getfile_err_store) { char *p; int l = strlen(storage_key); if ((p = strstr(getfile_err_store, storage_key))) memmove(p, p +l, strlen(p) -l +1); if (!(*getfile_err_store)) { free(getfile_err_store); getfile_err_store = NULL; } } i = is_highpriority(tmpname); if (found_highpriority && !i) { #ifdef DEBUGMSG printf("**%s %s not highpriority, already have one.\n", dir, ent->d_name); #endif continue; } if (i && !found_highpriority) { // Forget possible previous found normal priority file: mtime = 0; found_highpriority = 1; memset(candidates, 0, sizeof(candidates)); } #ifdef DEBUGMSG printf("**%s %s %i ", dir, ent->d_name, (int)(statbuf.st_mtime)); #endif // 3.1.6: Files with the same timestamp: compare names: if (found && statbuf.st_mtime == mtime) if (strcmp(fname, tmpname) > 0) mtime = 0; if (mtime == 0 || statbuf.st_mtime < mtime) { #ifdef DEBUGMSG printf("taken\n"); #endif strcpy(fname, tmpname); mtime = statbuf.st_mtime; found = 1; if (spool_directory_order) break; #if NUMBER_OF_MODEMS > 1 for (i = NUMBER_OF_MODEMS - 1; i > 0; i--) { strcpy(candidates[i].fname, candidates[i - 1].fname); candidates[i].mtime = candidates[i - 1].mtime; } snprintf(candidates[0].fname, sizeof(candidates[0].fname), "%s", ent->d_name); candidates[0].mtime = statbuf.st_mtime; #endif } else { #ifdef DEBUGMSG printf("leaved\n"); #endif #if NUMBER_OF_MODEMS > 1 for (i = 1; i < NUMBER_OF_MODEMS; i++) { if (candidates[i].fname[0] == 0) break; if (candidates[i].mtime > statbuf.st_mtime) break; if (candidates[i].mtime == statbuf.st_mtime) if (strcmp(candidates[i].fname, tmpname) > 0) break; } if (i < NUMBER_OF_MODEMS) { int j; for (j = NUMBER_OF_MODEMS - 1; j > i; j--) { strcpy(candidates[j].fname, candidates[j - 1].fname); candidates[j].mtime = candidates[j - 1].mtime; } snprintf(candidates[i].fname, sizeof(candidates[i].fname), "%s", ent->d_name); candidates[i].mtime = statbuf.st_mtime; } #endif } } } #ifdef DEBUGMSG if (getfile_err_store) printf("!! process: %i, getfile_err_store:\n%s", process_id, getfile_err_store); #endif // Each process has it's own error storage. // Mainspooler handles only the outgoing folder. // Modem processes handle all queue directories which are defined to the modem. // If some problematic file is deleted (outside of smsd), it's name remains in the storage. // To avoid missing error messages with the same filename later, storage is checked and cleaned. if (getfile_err_store) { char *p1; char *p2; char tmp[PATH_MAX]; struct stat statbuf; p1 = getfile_err_store; while ((p2 = strchr(p1, '\n'))) { memcpy(tmp, p1 +1, p2 -p1 -2); tmp[p2 -p1 -2] = 0; //if (access(tmp, F_OK) != 0) if (stat(tmp, &statbuf)) memmove(p1, p2 +1, strlen(p2)); else p1 = p2 +1; } if (!(*getfile_err_store)) { free(getfile_err_store); getfile_err_store = NULL; } } #ifdef DEBUGMSG if (getfile_err_store) printf("!! process: %i, getfile_err_store:\n%s", process_id, getfile_err_store); #endif // 3.1.9: Lock the file before waiting. if (found && lock) { // 3.1.12: check if a file still exists: if (stat(fname, &statbuf) || !lockfile(fname)) { found = 0; #if NUMBER_OF_MODEMS > 1 // Try to take the next best file: for (i = 1; i < NUMBER_OF_MODEMS && candidates[i].fname[0] && !found; i++) { sprintf(fname, "%s/%s", dir, candidates[i].fname); if (stat(fname, &statbuf) == 0 && lockfile(fname)) { mtime = candidates[i].mtime; found = 1; //writelogfile(LOG_DEBUG, 0, "Got the next best file from candidates (%i), %i SMS files and %i LOCK files seen.", i, files_count, locked_count); } } #endif if (found == 0) { lost_count++; // 3.1.12: continue immediately, or do other tasks after trying enough if (max_continuous_sending == 0 || time_usec() < start_time + max_continuous_sending * 1000000) { closedir(dirdata); continue; } else if (max_continuous_sending > 0) writelogfile(LOG_DEBUG, 0, "Tried to get a file for %i seconds, will do other tasks and then continue.", (int)(time_usec() - start_time) / 1000000); } } } if (!trust_directory && found) { /* check if the file grows at the moment (another program writes to it) */ int groesse1; int groesse2; // 3.1.9: check if the file is deleted, or deleted while waiting... if (stat(fname, &statbuf)) found = 0; else { groesse1 = statbuf.st_size; // 3.1.12: sleep less: //sleep(1); usleep_until(time_usec() + 500000); if (stat(fname, &statbuf)) groesse2 = -1; else groesse2 = statbuf.st_size; if (groesse1 != groesse2) found = 0; } if (!found && lock) unlockfile(fname); } closedir(dirdata); break; } if (!found) *filename = 0; else { strcpy(filename, fname); // 3.1.12: i = (int)(time_usec() - start_time) / 100000; if (i > 10) writelogfile((i >= 50)? LOG_NOTICE : LOG_DEBUG, 0, "Took %.1f seconds to get a file %s, lost %i times, %i SMS files and %i LOCK files seen.", (double)i / 10, fname, lost_count, files_count, locked_count); } #ifdef DEBUGMSG printf("## result for dir %s: %s\n\n", dir, filename); #endif return found; } int my_system( // // Executes an external process. // char *command, char *info ) { int pid; int status; time_t start_time; char *p; char tmp1[PATH_MAX]; char tmp2[PATH_MAX]; // Cannot contain "(" when passed as an argument snprintf(tmp1, sizeof(tmp1), ">%s/smsd_%s_1.XXXXXX", "/tmp", info); while ((p = strchr(tmp1, '('))) *p = '.'; while ((p = strchr(tmp1, ')'))) *p = '.'; while ((p = strchr(tmp1, ' '))) *p = '-'; snprintf(tmp2, sizeof(tmp2), "2>%s/smsd_%s_2.XXXXXX", "/tmp", info); while ((p = strchr(tmp2, '('))) *p = '.'; while ((p = strchr(tmp2, ')'))) *p = '.'; while ((p = strchr(tmp2, ' '))) *p = '-'; if (!ignore_exec_output) // 3.1.7 { close(mkstemp(tmp1 + 1)); close(mkstemp(tmp2 + 2)); } start_time = time(0); #ifdef DEBUGMSG printf("!! my_system(%s, %s)\n", command, info); #endif writelogfile0(LOG_DEBUG, 0, tb_sprintf("Running %s: %s", info, command)); pid = fork(); if (pid == -1) { // 3.1.12: //writelogfile0(LOG_CRIT, 0, tb_sprintf("Fatal error: fork failed.")); writelogfile0(LOG_CRIT, 0, tb_sprintf("Fatal error: fork failed. %i, %s", errno, strerror(errno))); return -1; } if (pid == 0) // only executed in the child { char *argv[4]; char *cmd = 0; #ifdef DEBUGMSG printf("!! pid=%i, child running external command\n", pid); #endif // TODO: sh still ok? argv[0] = "sh"; if ((p = strrchr(shell, '/'))) argv[0] = p + 1; argv[1] = "-c"; argv[2] = command; //(char*) command; if (!ignore_exec_output) { if ((cmd = (char *) malloc(strlen(command) + strlen(tmp1) + strlen(tmp2) + 3))) { sprintf(cmd, "%s %s %s", command, tmp1, tmp2); argv[2] = cmd; } } argv[3] = 0; // 3.1.5: //execv("/bin/sh",argv); // replace child with the external command execv(shell, argv); // replace child with the external command writelogfile0(LOG_CRIT, 1, tb_sprintf("Fatal error: execv( %s ) returned: %i, %s", shell, errno, strerror(errno))); free(cmd); #ifdef DEBUGMSG printf("!! pid=%i, execv() failed, %i, %s\n", pid, errno, strerror(errno)); printf("!! child exits now\n"); #endif exit((errno) ? errno : 1); // exit with error when the execv call failed } errno = 0; #ifdef DEBUGMSG printf("!! father waiting for child %i\n", pid); #endif snprintf(run_info, sizeof(run_info), "%s", info); while (1) { if (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { *run_info = 0; writelogfile0(LOG_ERR, 0, tb_sprintf("Done: %s, execution time %i sec., errno: %i, %s", info, time(0) - start_time, errno, strerror(errno))); return -1; } } else { int level = LOG_DEBUG; int trouble = 0; *run_info = 0; // 3.1.6: When running checkhandler and it spooled a message, return value 2 SHOULD NOT activate trouble logging: //writelogfile0((status == 0) ? LOG_DEBUG : LOG_ERR, (status == 0) ? 0 : 1, tb_sprintf("Done: %s, execution time %i sec., status: %i", info, time(0) - start_time, status)); if (!strcmp(info, "checkhandler")) { if (status != 0 && WEXITSTATUS(status) != 2) { level = LOG_ERR; trouble = 1; } } else if (status != 0) { level = LOG_ERR; trouble = 1; } writelogfile0(level, trouble, tb_sprintf("Done: %s, execution time %i sec., status: %i (%i)", info, time(0) - start_time, status, WEXITSTATUS(status))); if (!ignore_exec_output) { struct stat statbuf; FILE *fp; char line[2048]; int i; char *p; for (i = 1; i <= 2; i++) { p = (i == 1) ? tmp1 + 1 : tmp2 + 2; if (stat(p, &statbuf) == 0) { if (statbuf.st_size) { writelogfile0(LOG_ERR, 1, tb_sprintf("Exec: %s %s:", info, (i == 1) ? "said something" : "encountered errors")); if ((fp = fopen(p, "r"))) { while (fgets(line, sizeof(line), fp)) { while (strlen(line) > 1 && strchr("\r\n", line[strlen(line) - 1])) line[strlen(line) - 1] = 0; writelogfile0(LOG_ERR, 1, tb_sprintf("! %s", line)); } fclose(fp); } } unlink(p); } } } return WEXITSTATUS(status); } } } int write_pid( char* filename) { char pid[20]; int pidfile; sprintf(pid,"%i\n", (int)getpid()); pidfile = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0644); if (pidfile >= 0) { write(pidfile, pid, strlen(pid)); close(pidfile); return 1; } return 0; } int check_pid(char *filename) { int result = 0; char pid[20]; FILE *fp; char buffer[256]; sprintf(pid,"%i\n", (int)getpid()); if ((fp = fopen(filename, "r"))) { if (fgets(buffer, sizeof(buffer), fp)) if (strcmp(pid, buffer)) result = atoi(buffer); fclose(fp); } return result; } void remove_pid( char* filename) { if (*filename) unlink(filename); } int parse_validity(char *value, int defaultvalue) { int result = defaultvalue; char buffer[100]; int i; char tmp[100]; int got_numbers = 0; int got_letters = 0; int idx; char *p; if (value && *value) { // n min, hour, day, week, month, year // 3.0.9: if only keyword is given, insert number 1. // Fixed number without keyword handling. // Convert to lowercase so upcase is also accepted. *buffer = 0; snprintf(tmp, sizeof(tmp), "%s", value); cutspaces(tmp); for (idx = 0; tmp[idx]; idx++) { tmp[idx] = tolower((int)tmp[idx]); if (tmp[idx] == '\t') tmp[idx] = ' '; if (isdigitc(tmp[idx])) got_numbers = 1; else got_letters = 1; } if (got_numbers && !got_letters) { i = atoi(tmp); if (i >= 0 && i <= 255) result = i; return result; } if ((p = strchr(tmp, ' '))) *p = 0; if (strstr("min hour day week month year", tmp)) sprintf(buffer, "1 %.*s", (int)sizeof(buffer) -3, tmp); else sprintf(buffer, "%.*s", (int)sizeof(buffer) -1, value); while ((i = atoi(buffer)) > 0) { // 0 ... 143 (value + 1) * 5 minutes (i.e. 5 minutes intervals up to 12 hours) if (strstr(buffer, "min")) { if (i <= 720) { result = (i < 5)? 0 : i /5 -1; break; } sprintf(buffer, "%i hour", i /= 60); } // 144 ... 167 12 hours + ((value - 143) * 30 minutes) (i.e. 30 min intervals up to 24 hours) if (strstr(buffer, "hour")) { if (i <= 12) { sprintf(buffer, "%i min", i *60); continue; } if (i <= 24) { result = (i -12) *2 +143; break; } sprintf(buffer, "%i day", i /= 24); } // 168 ... 196 (value - 166) * 1 day (i.e. 1 day intervals up to 30 days) if (strstr(buffer, "day")) { if (i < 2) { sprintf(buffer, "24 hour"); continue; } if (i <= 34) { result = (i <= 30)? i +166 : 30 +166; break; } sprintf(buffer, "%i week", i /= 7); } // 197 ... 255 (value - 192) * 1 week (i.e. 1 week intervals up to 63 weeks) if (strstr(buffer, "week")) { if (i < 5) { sprintf(buffer, "%i day", i *7); continue; } result = (i <= 63)? i +192 : 255; break; } if (strstr(buffer, "month")) { sprintf(buffer, "%i day", (i == 12)? 365 : i *30); continue; } if (strstr(buffer, "year")) { if (i == 1) { sprintf(buffer, "52 week"); continue; } result = 255; } break; } } return result; } // 0=invalid, 1=valid int report_validity(char *buffer, int validity_period) { int result = 0; int n; char *p; if (validity_period < 0 || validity_period > 255) sprintf(buffer, "invalid (%i)", validity_period); else { if (validity_period <= 143) { // 0 ... 143 (value + 1) * 5 minutes (i.e. 5 minutes intervals up to 12 hours) n = (validity_period +1) *5; p = "min"; } else if (validity_period <= 167) { // 144 ... 167 12 hours + ((value - 143) * 30 minutes) (i.e. 30 min intervals up to 24 hours) n = 12 +(validity_period -143) /2; p = "hour"; } else if (validity_period <= 196) { // 168 ... 196 (value - 166) * 1 day (i.e. 1 day intervals up to 30 days) n = validity_period -166; p = "day"; } else { // 197 ... 255 (value - 192) * 1 week (i.e. 1 week intervals up to 63 weeks) n = validity_period -192; p = "week"; } sprintf(buffer, "%i %s%s (%i)", n, p, (n > 1)? "s" : "", validity_period); result = 1; } return result; } int getrand(int toprange) { srand((int)(time(NULL) * getpid())); return (rand() % toprange) +1; } int is_executable(char *filename) { // access() migth do this easier, but in Gygwin it returns 0 even when requested permissions are NOT granted. int result = 0; struct stat statbuf; mode_t mode; int n, i; gid_t *g; if (stat(filename, &statbuf) >= 0) { mode = statbuf.st_mode & 0755; if (getuid()) { if (statbuf.st_uid != getuid()) { if ((n = getgroups(0, NULL)) > 0) { if ((g = (gid_t *)malloc(n * sizeof(gid_t)))) { if ((n = getgroups(n, g)) > 0) { for (i = 0; (i < n) & (!result); i++) if (g[i] == statbuf.st_gid) result = 1; } free(g); } } if (result) { if ((mode & 050) != 050) result = 0; } else if ((mode & 05) == 05) result = 1; } else if ((mode & 0500) == 0500) result = 1; } else if ((mode & 0100) || (mode & 010) || (mode & 01)) result = 1; } return result; } int check_access(char *filename) { // access() migth do this easier, but in Gygwin it returns 0 even when requested permissions are NOT granted. int result = 0; struct stat statbuf; mode_t mode; int n, i; gid_t *g; if (stat(filename, &statbuf) >= 0) { mode = statbuf.st_mode; // & 0777; if (getuid()) { if (statbuf.st_uid != getuid()) { if ((n = getgroups(0, NULL)) > 0) { if ((g = (gid_t *)malloc(n * sizeof(gid_t)))) { if ((n = getgroups(n, g)) > 0) { for (i = 0; (i < n) & (!result); i++) if (g[i] == statbuf.st_gid) result = 1; } free(g); } } if (result) { if ((mode & 060) != 060) result = 0; } else if ((mode & 06) == 06) result = 1; } else if ((mode & 0600) == 0600) result = 1; } else if ((mode & 0200) || (mode & 020) || (mode & 02)) result = 1; } return result; } int value_in(int value, int arg_count, ...) { int result = 0; va_list ap; va_start(ap, arg_count); for (; arg_count > 0; arg_count--) if (value == va_arg(ap, int)) result = 1; va_end(ap); return result; } int t_sleep(int seconds) { // 3.1.12: When a signal handler is installed, receiving of any singal causes // that functions sleep() and usleep() will return immediately. //int i; time_t t; t = time(0); //for (i = 0; i < seconds; i++) while (time(0) - t < seconds) { if (terminate) return 1; sleep(1); } return 0; } int usleep_until(unsigned long long target_time) { struct timeval tv; struct timezone tz; unsigned long long now; do { gettimeofday(&tv, &tz); now = (unsigned long long)tv.tv_sec *1000000 +tv.tv_usec; if (terminate == 1) return 1; if (now < target_time) usleep(100); } while (now < target_time); return 0; } unsigned long long time_usec() { struct timeval tv; struct timezone tz; //struct tm *tm; gettimeofday(&tv, &tz); /*tm =*/ //localtime(&tv.tv_sec); return (unsigned long long)tv.tv_sec *1000000 +tv.tv_usec; } int make_datetime_string(char *dest, size_t dest_size, char *a_date, char *a_time, char *a_format) { int result = 0; time_t rawtime; struct tm *timeinfo; //time(&rawtime); // 3.1.14: //if (!a_date && !a_time) // return strftime(dest, dest_size, (a_format)? a_format : datetime_format, localtime(&rawtime)); if (!a_date && !a_time) { struct timeval tv; struct timezone tz; char *p; char buffer[7]; gettimeofday(&tv, &tz); rawtime = tv.tv_sec; timeinfo = localtime(&rawtime); result = strftime(dest, dest_size, (a_format)? a_format : datetime_format, timeinfo); if ((p = strstr(dest, "timeus"))) { snprintf(buffer, sizeof(buffer), "%06d", (int)tv.tv_usec); strncpy(p, buffer, strlen(buffer)); } else if ((p = strstr(dest, "timems"))) { snprintf(buffer, sizeof(buffer), "%03d", (int)tv.tv_usec / 1000); strncpy(p, buffer, strlen(buffer)); memmove(p + 3, p + 6, strlen(p + 6) + 1); } return result; } if (a_date && strlen(a_date) >= 8 && a_time && strlen(a_time) >= 8) { time(&rawtime); timeinfo = localtime(&rawtime); timeinfo->tm_year = atoi(a_date) + 100; timeinfo->tm_mon = atoi(a_date + 3) - 1; timeinfo->tm_mday = atoi(a_date + 6); timeinfo->tm_hour = atoi(a_time); timeinfo->tm_min = atoi(a_time + 3); timeinfo->tm_sec = atoi(a_time + 6); // ?? mktime(timeinfo); result = strftime(dest, dest_size, (a_format)? a_format : datetime_format, timeinfo); } return result; } void strcat_realloc(char **buffer, char *str, char *delimiter) { int delimiter_length = 0; if (delimiter) delimiter_length = strlen(delimiter); if (*buffer == 0) { if ((*buffer = (char *) malloc(strlen(str) + delimiter_length + 1))) **buffer = 0; } else *buffer = (char *) realloc((void *) *buffer, strlen(*buffer) + strlen(str) + delimiter_length + 1); if (*buffer) sprintf(strchr(*buffer, 0), "%s%s", str, (delimiter) ? delimiter : ""); } char *strcpyo(char *dest, const char *src) { size_t i; for (i = 0; src[i] != '\0'; i++) dest[i] = src[i]; dest[i] = '\0'; return dest; } void getfield(char* line, int field, char* result, int size) { char* start; char* end; int i; int length; #ifdef DEBUGMSG printf("!! getfield(line=%s, field=%i, ...)\n",line,field); #endif if (size < 1) return; *result=0; start=strstr(line,":"); if (start==0) return; for (i=1; i=start)) end--; length=end-start+1; if (length >= size) return; strncpy(result,start,length); result[length]=0; #ifdef DEBUGMSG printf("!! result=%s\n",result); #endif } smstools-3.1.15/src/extras.h000077500000000000000000000062451223712572200157470ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef EXTRAS_H #define EXTRAS_H #include /* Converts a string to a boolean value. The string can be: 1= true, yes, on, 1 0 = all other strings Only the first character is significant. */ int yesno(char *value); /* Like yesno, but defaults to -1. 0 = false, no, off, 0 */ int yesno_check(char *value); /* removes all ctrl chars */ char *cut_ctrl(char* message); char *cut_crlf(char *st); /* Is a character a space or tab? */ int is_blank(char c); int line_is_blank(char *line); /* Moves a file into another directory. Returns 1 if success. */ int movefile(char* filename, char* directory); /* Moves a file into another directory. Destination file is protected with a lock file during the operation. Returns 1 if success. */ //int movefilewithdestlock(char* filename, char* directory); int movefilewithdestlock_new(char* filename, char* directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename); /* removes ctrl chars at the beginning and the end of the text and removes */ /* \r in the text. Returns text.*/ char *cutspaces(char *text); /* removes all empty lines */ char *cut_emptylines(char *text); /* Checks if the text contains only numbers. */ int is_number(char* text); int getpdufile(char *filename); /* Gets the first file that is not locked in the directory. Returns 0 if there is no file. Filename is the filename including the path. Additionally it cheks if the file grows at the moment to prevent that two programs acces the file at the same time. */ int getfile(int trust_directory, char* dir, char* filename, int lock); /* Replacement for system() wich can be breaked. See man page of system() */ int my_system(char *command, char *info); /* Create and remove a PID file */ int write_pid(char* filename); int check_pid(char *filename); void remove_pid(char* filename); /* Parse validity value string */ int parse_validity(char *value, int defaultvalue); int report_validity(char *buffer, int validity_period); /* Return a random number between 1 and toprange */ int getrand(int toprange); /* Check permissions of filename */ int is_executable(char *filename); int check_access(char *filename); int value_in(int value, int arg_count, ...); // t_sleep returns 1 if terminate is set to 1 while sleeping: int t_sleep(int seconds); int usleep_until(unsigned long long target_time); unsigned long long time_usec(); int make_datetime_string(char *dest, size_t dest_size, char *a_date, char *a_time, char *a_format); void strcat_realloc(char **buffer, char *str, char *delimiter); char *strcpyo(char *dest, const char *src); void getfield(char* line, int field, char* result, int size); #endif smstools-3.1.15/src/locking.c000077500000000000000000000040511223712572200160530ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include "locking.h" #include "smsd_cfg.h" #include #include #include #include #include #include #include int lockfile( char* filename) { char lockfilename[PATH_MAX +5]; int lockfile; struct stat statbuf; char pid[64]; if (!filename) return 0; if (strlen(filename) + 5 >= sizeof(lockfilename)) return 0; strcpy(lockfilename,filename); strcat(lockfilename,".LOCK"); if (stat(lockfilename,&statbuf)) { lockfile=open(lockfilename,O_CREAT|O_EXCL|O_WRONLY,0644); if (lockfile>=0) { // 3.1.15: //snprintf(pid, sizeof(pid), "%i %s\n", (int)getpid(), DEVICE.name); snprintf(pid, sizeof(pid), "%i %s\n", (int)getpid(), (process_id == -1) ? "MAINPROCESS" : DEVICE.name); write(lockfile, pid, strlen(pid)); close(lockfile); sync(); return 1; } } return 0; } int islocked( char* filename) { char lockfilename[PATH_MAX +5]; struct stat statbuf; if (!filename) return 0; if (strlen(filename) + 5 >= sizeof(lockfilename)) return 0; strcpy(lockfilename,filename); strcat(lockfilename,".LOCK"); if (stat(lockfilename,&statbuf)) return 0; return 1; } int unlockfile( char* filename) { char lockfilename[PATH_MAX +5]; if (!filename) return 0; if (strlen(filename) + 5 >= sizeof(lockfilename)) return 0; strcpy(lockfilename,filename); strcat(lockfilename,".LOCK"); if (unlink(lockfilename)) return 0; return 1; } smstools-3.1.15/src/locking.h000077500000000000000000000014341223712572200160620ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef LOCKING_H #define LOCKING_H /* Locks a file and returns 1 if successful */ int lockfile( char* filename); /* Checks, if a file is locked */ int islocked( char* filename); /* Unlocks a file */ int unlockfile( char* filename); #endif smstools-3.1.15/src/logging.c000077500000000000000000000144771223712572200160700ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include "logging.h" #include "extras.h" #include #include #include #include #include #include #include #include #include "smsd_cfg.h" #include "stats.h" int Filehandle = -1; int Level; int SavedLevel; int Filehandle_trouble = -1; char *trouble_logging_buffer = 0; int last_flush_was_clear = 1; int change_loglevel(int new_level) { SavedLevel = Level; Level = new_level; return SavedLevel; } void restore_loglevel() { Level = SavedLevel; } // 3.1.14: int get_loglevel() { return Level; } int openlogfile(char *filename, int facility, int level) { int result = 0; closelogfile(); Level = level; if (filename==0 || filename[0]==0 || strcmp(filename,"syslog")==0 || strcmp(filename,"0")==0) { openlog("smsd", LOG_CONS, facility); Filehandle = -1; Filehandle_trouble = -1; } else if (strcmp(filename, "1") == 0 || strcmp(filename, "2") == 0) //(is_number(filename)) { int oldfilehandle; oldfilehandle=atoi(filename); Filehandle=dup(oldfilehandle); if (Filehandle<0) { fprintf(stderr, "Cannot duplicate logfile handle\n"); exit(1); } else result = Filehandle; } else { Filehandle=open(filename,O_APPEND|O_WRONLY|O_CREAT,0640); if (Filehandle<0) { fprintf(stderr, "Cannot open logfile\n"); exit(1); } else { result = Filehandle; if (smart_logging && level < 7) { char filename2[PATH_MAX]; int error = 0; int i; if ((size_t)snprintf(filename2, sizeof(filename2), "%s", filename) >= sizeof(filename2)) error = 1; else { if (strlen(filename2) > 4 && !strcmp(filename2 +strlen(filename2) -4, ".log")) { filename2[strlen(filename2) -4] = 0; i = sizeof(filename2) - strlen(filename2); if (snprintf(strchr(filename2, 0), i, "_trouble.log") >= i) error = 2; } else { i = sizeof(filename2) - strlen(filename2); if (snprintf(strchr(filename2, 0), i, ".trouble") >= i) error = 3; } } if (!error) { Filehandle_trouble = open(filename2, O_APPEND | O_WRONLY | O_CREAT, 0640); if (Filehandle_trouble < 0) error = 4; } if (error) { closelogfile(); fprintf(stderr, "Cannot open logfile for smart logging (error: %i)\n", error); exit(1); } } } } return result; } void closelogfile() { if (Filehandle>=0) { close(Filehandle); Filehandle = -1; } if (Filehandle_trouble >= 0) { close(Filehandle_trouble); Filehandle_trouble = -1; } if (trouble_logging_buffer) { free(trouble_logging_buffer); trouble_logging_buffer = 0; } trouble_logging_started = 0; } void writelogfile0(int severity, int trouble, char *text) { writelogfile(severity, trouble, "%s", text); } void writelogfile(int severity, int trouble, char* format, ...) { va_list argp; char text[SIZE_LOG_LINE]; char text2[SIZE_LOG_LINE]; char timestamp[40]; // make a string of the arguments va_start(argp,format); vsnprintf(text,sizeof(text),format,argp); va_end(argp); // 3.1.6: Remove \r from the end: while (strlen(text) > 0 && text[strlen(text) - 1] == '\r') text[strlen(text) - 1] = 0; if (severity<=Level) { if (Filehandle<0) { if (strcmp(process_title, "smsd") == 0) syslog(severity, "MAINPROCESS: %s", text); else syslog(severity, "%s: %s", process_title, text); } else { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2),"%s,%i, %s: %s\n", timestamp, severity, process_title, text); // 3.1.5: if (text2[strlen(text2) -1] != '\n') strcpy(text2 +sizeof(text2) -5, "...\n"); write(Filehandle,text2,strlen(text2)); } } if (smart_logging && Level < 7) { if (trouble) { if (!trouble_logging_started) { trouble_logging_started = 1; // Global process_id is the same as int device in many functions calls. if (process_id >= 0) statistics[process_id]->status = 't'; } } // Any message is stored: make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2),"%s,%i, %s: %s\n", timestamp, severity, process_title, text); if (text2[strlen(text2) -1] != '\n') strcpy(text2 +sizeof(text2) -5, "...\n"); if (!trouble_logging_buffer) { if ((trouble_logging_buffer = (char *)malloc(strlen(text2) +1))) *trouble_logging_buffer = 0; } else trouble_logging_buffer = (char *)realloc((void *)trouble_logging_buffer, strlen(trouble_logging_buffer) +strlen(text2) +1); if (trouble_logging_buffer) strcat(trouble_logging_buffer, text2); } } void flush_smart_logging() { if (trouble_logging_started && trouble_logging_buffer) { write(Filehandle_trouble, trouble_logging_buffer, strlen(trouble_logging_buffer)); last_flush_was_clear = 0; } else { // 3.1.6: If some errors were printed and now all is ok, print it to the log: if (!last_flush_was_clear && Filehandle_trouble >= 0) { char text2[SIZE_LOG_LINE]; char timestamp[40]; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2), "%s,%i, %s: %s\n", timestamp, LOG_NOTICE, process_title, "Everything ok now."); write(Filehandle_trouble, text2, strlen(text2)); } last_flush_was_clear = 1; } trouble_logging_started = 0; if (trouble_logging_buffer) { free(trouble_logging_buffer); trouble_logging_buffer = 0; } } smstools-3.1.15/src/logging.h000077500000000000000000000023541223712572200160640ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef LOGGING_H #define LOGGING_H int trouble_logging_started; int change_loglevel(int new_level); void restore_loglevel(); int get_loglevel(); int openlogfile(char *filename, int facility, int level); // if filename if 0, "" or "syslog": opens syslog. Level is ignored. // else: opens a log file. Facility is not used. Level specifies the verbosity (9=highest). // If the filename is a number it is interpreted as the file handle and // duplicated. The file must be already open. // Returns the file handle to the log file. void closelogfile(); void writelogfile0(int severity, int trouble, char *text); void writelogfile(int severity, int trouble, char* format, ...); void flush_smart_logging(); #endif smstools-3.1.15/src/modeminit.c000077500000000000000000001714661223712572200164310ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include "logging.h" #include "alarm.h" #ifdef SOLARIS #include #include // for bzero(). #endif #include #include #include #include #include #include #include #ifndef DISABLE_INET_SOCKET // HG #include #include #include #endif #include "extras.h" #include "modeminit.h" #include "smsd_cfg.h" #include "version.h" #include "pdu.h" #include "stats.h" // Define a dummy if the OS does not support hardware handshake #ifndef CRTSCTS #define CRTSCTS 0 #endif typedef struct { int code; char *text; } _gsm_general_error; _gsm_general_error gsm_cme_errors[] = { // 3GPP TS 07.07 version 7.8.0 Release 1998 (page 90) ETSI TS 100 916 V7.8.0 (2003-03) {0, "phone failure"}, {1, "no connection to phone"}, {2, "phone-adaptor link reserved"}, {3, "operation not allowed"}, {4, "operation not supported"}, {5, "PH-SIM PIN required"}, {6, "PH-FSIM PIN required"}, {7, "PH-FSIM PUK required"}, {10, "SIM not inserted"}, {11, "SIM PIN required"}, {12, "SIM PUK required"}, {13, "SIM failure"}, {14, "SIM busy"}, {15, "SIM wrong"}, {16, "incorrect password"}, {17, "SIM PIN2 required"}, {18, "SIM PUK2 required"}, {20, "memory full"}, {21, "invalid index"}, {22, "not found"}, {23, "memory failure"}, {24, "text string too long"}, {25, "invalid characters in text string"}, {26, "dial string too long"}, {27, "invalid characters in dial string"}, {30, "no network service"}, {31, "network timeout"}, {32, "network not allowed - emergency calls only"}, {40, "network personalisation PIN required"}, {41, "network personalisation PUK required"}, {42, "network subset personalisation PIN required"}, {43, "network subset personalisation PUK required"}, {44, "service provider personalisation PIN required"}, {45, "service provider personalisation PUK required"}, {46, "corporate personalisation PIN required"}, {47, "corporate personalisation PUK required"}, // 3.1.5: {515, "device busy"}, {100, "unknown"} // Some other possible errors (source document?): //CME ERROR: 48 PH-SIM PUK required //CME ERROR: 256 Operation temporarily not allowed //CME ERROR: 257 Call barred //CME ERROR: 258 Phone is busy //CME ERROR: 259 User abort //CME ERROR: 260 Invalid dial string //CME ERROR: 261 SS not executed //CME ERROR: 262 SIM Blocked }; _gsm_general_error gsm_cms_errors[] = { // Table 8.4/GSM 04.11 (part 1): {1, "Unassigned (unallocated) number"}, {8, "Operator determined barring"}, {10, "Call barred"}, {21, "Short message transfer rejected"}, {27, "Destination out of order"}, {28, "Unindentified subscriber"}, {29, "Facility rejected"}, {30, "Unknown subscriber"}, {38, "Network out of order"}, {41, "Temporary failure"}, {42, "Congestion"}, {47, "Recources unavailable, unspecified"}, {50, "Requested facility not subscribed"}, {69, "Requested facility not implemented"}, {81, "Invalid short message transfer reference value"}, {95, "Semantically incorrect message"}, {96, "Invalid mandatory information"}, {97, "Message type non-existent or not implemented"}, {98, "Message not compatible with short message protocol state"}, {99, "Information element non-existent or not implemented"}, {111, "Protocol error, unspecified"}, {127, "Internetworking , unspecified"}, // Table 8.4/GSM 04.11 (part 2): {22, "Memory capacity exceeded"}, // GSM 03.40 subclause 9.2.3.22 values. {128, "Telematic internetworking not supported"}, {129, "Short message type 0 not supported"}, {130, "Cannot replace short message"}, {143, "Unspecified TP-PID error"}, {144, "Data code scheme (alphabet) not supported"}, {145, "Message class not supported"}, {159, "Unspecified TP-DCS error"}, {160, "Command cannot be actioned"}, {161, "Command unsupported"}, {175, "Unspecified TP-Command error"}, {176, "Transfer Protocol Data Unit (TPDU) not supported"}, {192, "Service Center (SC) busy"}, {193, "No SC subscription"}, {194, "SC System failure"}, {195, "Invalid Short Message Entity (SME) address"}, {196, "Destination SME barred"}, {197, "SM Rejected-Duplicate SM"}, {198, "Validity Period Format (TP-VPF) not supported"}, {199, "Validity Period) TP-VP not supported"}, {208, "SIM SMS Storage full"}, {209, "No SMS Storage capability in SIM"}, {210, "Error in MS"}, {211, "Memory capacity exceeded"}, {212, "Sim Application Toolkit busy"}, {213, "SIM data download error"}, {255, "Unspecified error cause"}, // 3GPP TS 27.005 subclause 3.2.5 values /3/. {300, "ME Failure"}, {301, "SMS service of ME reserved"}, {302, "Operation not allowed"}, {303, "Operation not supported"}, {304, "Invalid PDU mode parameter"}, {305, "Invalid Text mode parameter"}, {310, "(U)SIM not inserted"}, {311, "(U)SIM PIN required"}, {312, "PH-(U)SIM PIN required"}, {313, "(U)SIM failure"}, {314, "(U)SIM busy"}, {315, "(U)SIM wrong"}, {316, "(U)SIM PUK required"}, {317, "(U)SIM PIN2 required"}, {318, "(U)SIM PUK2 required"}, {320, "Memory failure"}, {321, "Invalid memory index"}, {322, "Memory full"}, {330, "SMSC address unknown"}, {331, "No network service"}, {332, "Network timeout"}, {340, "No +CNMA acknowledgement expected"}, {500, "Unknown error"}, // 3.1.5: This error occurs when you try to send a message and the module is receiving another one at the same time. // This causes a collision in the message transfer protocol resulting in failure in sending the SMS. // Sometimes, +CMS ERROR: 512 may also occur when the module is receiving weak signal and is loosing connection. {512, "MM establishment failure / weak signal, loosing connection"}, // 3.1.5: ack for 28s after transmission or 42s after channel establishment {513, "Lower layer failure: receiving of an acknowledgement timed out or lost the radio link."}, // 3.1.5: {514, "Network error. Congestion in the network."}, {515, "Please wait, service is not available, init in progress"} }; char *get_gsm_cme_error(int code) { int i; int m = sizeof gsm_cme_errors / sizeof *gsm_cme_errors; for (i = 0; i < m; i++) if (code == gsm_cme_errors[i].code) return gsm_cme_errors[i].text; return ""; } char *get_gsm_cms_error(int code) { int i; int m = sizeof gsm_cms_errors / sizeof *gsm_cms_errors; for (i = 0; i < m; i++) if (code == gsm_cms_errors[i].code) return gsm_cms_errors[i].text; return ""; } char *get_gsm_error(char *answer) { char *p; if (answer && *answer) { if ((p = strstr(answer, "+CME ERROR: "))) return get_gsm_cme_error(atoi(p +12)); if ((p = strstr(answer, "+CMS ERROR: "))) return get_gsm_cms_error(atoi(p +12)); } return ""; } char *explain_csq_buffer(char *buffer, int short_form, int ssi, int ber, int signal_quality_ber_ignore) { strcpy(buffer, (short_form)? "ssi: " : "Signal Strength Indicator: "); if (ssi == 99 || ssi > 31 || ssi < 0) strcat(buffer, (short_form)? "??" : "not present of not measurable"); else { // 3.1.12: explain level: int dbm; char *level = ""; dbm = -113 + 2 * ssi; if (dbm <= -95) level = " (Marginal)"; // Marginal - Levels of -95dBm or lower. else if (dbm <= -85) level = " (Workable)"; // Workable under most conditions - Levels of -85dBm to -95dBm. else if (dbm <= -75) level = " (Good)"; // Good - Levels between -75dBm and -85dBm. else level = " (Excellent)"; // Excellent - levels above -75dBm. if (short_form) sprintf(strchr(buffer, 0), "%i dBm%s", dbm, level); else sprintf(strchr(buffer, 0), "(%d,%d) %i dBm%s%s", ssi, ber, dbm, level, (ssi == 0)? " or less" : ""); } if (!signal_quality_ber_ignore) { strcat(buffer, (short_form)? ", ber: " : ", Bit Error Rate: "); switch (ber) { case 0: strcat(buffer, (short_form)? "< 0.2 %" : "less than 0.2 %"); break; case 1: strcat(buffer, "0.2 - 0.4 %"); break; case 2: strcat(buffer, "0.4 - 0.8 %"); break; case 3: strcat(buffer, "0.8 - 1.6 %"); break; case 4: strcat(buffer, "1.6 - 3.2 %"); break; case 5: strcat(buffer, "3.2 - 6.4 %"); break; case 6: strcat(buffer, "6.4 - 12.8 %"); break; case 7: strcat(buffer, (short_form)? "> 12.8 %" : "more than 12.8 %"); break; default: strcat(buffer, (short_form)? "??" : "not known or not detectable"); break; } } return buffer; } void explain_csq(int loglevel, int short_form, char *answer, int signal_quality_ber_ignore) { int ssi; int ber = 99; char *p; char buffer[1024]; if (strstr(answer, "ERROR")) return; // 3.1.12: Allow "echo on": //if (strncmp(answer, "+CSQ:", 5)) // return; if (!(p = strstr(answer, "+CSQ:"))) return; //ssi = atoi(answer +5); ssi = atoi(p +5); //if ((p = strchr(answer, ','))) if ((p = strchr(p, ','))) ber = atoi(p +1); explain_csq_buffer(buffer, short_form, ssi, ber, signal_quality_ber_ignore); writelogfile0(loglevel, 0, buffer); } int write_to_modem(char *command, int timeout, int log_command, int print_error) { int status=0; int timeoutcounter=0; int x=0; struct termios tio; if (command && command[0]) { if (log_command) writelogfile(LOG_DEBUG, 0, "-> %s",command); // 3.1.9: if (DEVICE.send_handshake_select) { size_t r = 0, bs, n; ssize_t got; fd_set writefds; n = strlen(command); while (n > 0) { bs = (DEVICE.send_delay < 1) ? n : 1; got = write(modem_handle, command + r, bs); if (got < 0) { if (errno == EINTR) continue; if (errno != EAGAIN) { writelogfile0(LOG_ERR, 1, tb_sprintf("write_to_modem: error %d: %s", errno, strerror(errno))); alarm_handler0(LOG_ERR, tb); return 0; } writelogfile0(LOG_DEBUG, 0, tb_sprintf("write_to_modem: device busy, waiting")); alarm_handler0(LOG_DEBUG, tb); FD_ZERO(&writefds); FD_SET(modem_handle, &writefds); select(modem_handle + 1, NULL, &writefds, NULL, NULL); continue; } n -= got; r += got; if (DEVICE.send_delay > 0) usleep_until(time_usec() + DEVICE.send_delay * 1000); tcdrain(modem_handle); } } else { tcgetattr(modem_handle, &tio); if (!DEVICE_IS_SOCKET && tio.c_cflag & CRTSCTS) { ioctl(modem_handle, TIOCMGET, &status); while (!(status & TIOCM_CTS)) { usleep_until(time_usec() + 100000); timeoutcounter++; ioctl(modem_handle, TIOCMGET, &status); if (timeoutcounter>timeout) { if (print_error) printf("\nModem is not clear to send.\n"); else { writelogfile0(LOG_ERR, 1, tb_sprintf("Modem is not clear to send")); alarm_handler0(LOG_ERR, tb); } return 0; } } } // 3.1.5: if (DEVICE.send_delay < 1) { if ((size_t)write(modem_handle, command, strlen(command)) != strlen(command)) { writelogfile0(LOG_ERR, 1, tb_sprintf("Could not send string, cause: %s", strerror(errno))); alarm_handler0(LOG_ERR, tb); return 0; } if (DEVICE.send_delay < 0) tcdrain(modem_handle); } else { for(x=0;(size_t)x= sizeof(tmp) - 6) { strcpy(tmp, "ERROR: too much data"); break; } sprintf(strchr(tmp, 0), " %02X[%c]", (unsigned char) answer[i], ((unsigned char) answer[i] >= ' ') ? answer[i] : '.'); } writelogfile(LOG_CRIT, 0, tmp); } // restart timout counter timeoutcounter=0; // append a string termination character answer[count+got]=0; success=1; // 3.1.12: With Multitech network modem (telnet) there can be 0x00 inside the string: if (strlen(answer) < (size_t)count + got) { int i, j, len; len = count + got; j = 0; for (i = 0; i < count + got; i++) { if (answer[i] == '\0') { len--; continue; } if (i > j) answer[j] = answer[i]; j++; } answer[len] = 0; } } } while (timeoutcounter < timeout); // 3.1.12: if (success && DEVICE_IS_SOCKET) { count += got; negotiate_with_telnet(answer, &count); } return success; } // 3.1.1: char *change_crlf(char *str, char ch) { char *p; while ((p = strchr(str, '\r'))) *p = ch; while ((p = strchr(str, '\n'))) *p = ch; return str; } int detect_routed_message(char *answer) { int result = 0; // keywords must have same length: int keyword_length = 5; char *keyword = "+CMT:"; char *keyword_sr = "+CDS:"; char *p; char *term; int pdu_length; int can_handle; int is_sr; int send_ack = 0; char *p1; char *p2; if (*answer) { // We can have answer which contains only routed message, or there can be answer like: // +CPMS: "SM",0,20,"SM",0,20,"SM",0,20 OK ... +CMT: ,59 07915348150110... is_sr = 0; if (!(p1 = strstr(answer, keyword))) if ((p1 = strstr(answer, keyword_sr))) is_sr = 1; if (p1) { if (!is_sr || !DEVICE.using_routed_status_report) writelogfile(LOG_ERR, DEVICE.unexpected_input_is_trouble, "Routed %s detected:\n%s", (is_sr) ? "status report" : "message", p1); if (DEVICE.routed_status_report_cnma) send_ack = 1; while (p1) { result++; can_handle = 0; if ((term = strchr(p1, '\r'))) { p = term +1; if (*p == '\n') p++; if (octet2bin(p) >= 0) { if ((term = strchr(p, '\r'))) { // Original answer remains intact. Count the length and check that PDU does not contain delimiter character: pdu_length = (int)(term - p); p2 = strchr(p, ','); if (!p2 || p2 > term) { // PDU is handled later. If it has some errors, explanation will be printed to the message file. if (!routed_pdu_store) { if ((routed_pdu_store = (char *)malloc(pdu_length +2))) *routed_pdu_store = 0; } else routed_pdu_store = (char *)realloc((void *)routed_pdu_store, strlen(routed_pdu_store) +pdu_length +2); if (routed_pdu_store) { // For easier handling, delimiter in routed_pdu_store is '\n'. sprintf(strchr(routed_pdu_store, 0), "%.*s\n", pdu_length, p); can_handle = 1; } } } } } p = (is_sr)? "status report" : "message"; if (can_handle) { if (is_sr && DEVICE.using_routed_status_report) writelogfile0(LOG_INFO, 0, tb_sprintf("Saved routed %s for later handling.", p)); else { writelogfile0(LOG_ERR, 0, tb_sprintf("Saved routed %s for later handling. However, you MUST DISABLE %s routing with modem settings.", p, p)); alarm_handler0(LOG_ERR, tb); } } else { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot handle this routed %s. You MUST DISABLE %s routing with modem settings.", p, p)); alarm_handler0(LOG_ERR, tb); } is_sr = 0; p = p1 +keyword_length; if (!(p1 = strstr(p, keyword))) if ((p1 = strstr(p, keyword_sr))) is_sr = 1; } } // TODO: more than one ack if more than one messsage received? if (send_ack) { char loganswer[1024]; writelogfile(LOG_DEBUG, 0, "Sending acknowledgement"); write_to_modem("AT+CNMA\r", 30, 1, 0); *loganswer = 0; read_from_modem(loganswer, sizeof(loganswer), 2); if (log_single_lines) change_crlf(loganswer, ' '); writelogfile(LOG_DEBUG, 0, "<- %s", loganswer); } } return result; } void do_hangup(char *answer) { if (DEVICE.hangup_incoming_call == 1 || (DEVICE.hangup_incoming_call == -1 && hangup_incoming_call == 1) || DEVICE.phonecalls == 2) { char *command = "AT+CHUP\r"; char tmpanswer[1024]; int timeoutcounter; if (DEVICE.voicecall_hangup_ath == 1 || (DEVICE.voicecall_hangup_ath == -1 && voicecall_hangup_ath == 1)) command = "ATH\r"; writelogfile(LOG_NOTICE, 0, "Ending incoming call: %s", answer); write_to_modem(command, 30, 1, 0); timeoutcounter = 0; *tmpanswer = 0; do { read_from_modem(tmpanswer, sizeof(tmpanswer), 2); // One read attempt is 200ms // Any answer is ok: if (*tmpanswer) break; timeoutcounter++;; } while (timeoutcounter < 5); if (!log_unmodified) { cutspaces(tmpanswer); cut_emptylines(tmpanswer); if (log_single_lines) change_crlf(tmpanswer, ' '); } writelogfile(LOG_DEBUG, 0, "<- %s", tmpanswer); if (DEVICE.communication_delay > 0) usleep_until(time_usec() + DEVICE.communication_delay * 1000); } } int handlephonecall_clip(char *answer) { int result = 0; char *p, *e_start, *e_end; int len; char entry_number[SIZE_PB_ENTRY]; int entry_type; if (DEVICE.phonecalls != 2) return 0; *entry_number = 0; entry_type = 0; if ((p = strstr(answer, "+CLIP:"))) { do_hangup(answer); result = -1; if ((e_start = strchr(p, '"'))) { e_start++; if ((e_end = strchr(e_start, '"'))) { if ((len = e_end -e_start) < SIZE_PB_ENTRY) { sprintf(entry_number, "%.*s", len, e_start); cutspaces(entry_number); if (*entry_number == '+') strcpyo(entry_number, entry_number +1); if (strlen(e_end) >= 3) { e_end += 2; writelogfile(LOG_INFO, 0, "Got phonecall from %s", entry_number); savephonecall(entry_number, atoi(e_end), ""); result = 1; } } } } if (result == -1) { writelogfile0(LOG_ERR, 1, tb_sprintf("Error while trying to handle +CLIP.")); alarm_handler0(LOG_ERR, tb); } } return result; } // 3.1beta7: Not waiting any answer if answer is NULL. Return value is then 1/0. // 3.1.5: In case of timeout return value is -2. int put_command(char *command, char *answer, int max, int timeout_count, char *expect) { return put_command0(command, answer, max, timeout_count, expect, 0); } int put_command0(char *command, char *answer, int max, int timeout_count, char *expect, int silent) { char loganswer[SIZE_LOG_LINE]; int timeoutcounter = 0; regex_t re; int got_timeout = 0; int regex_allocated = 0; int timeout; int i; static unsigned long long last_command_ended = 0; int last_length; if (DEVICE.communication_delay > 0) if (last_command_ended) usleep_until(last_command_ended +DEVICE.communication_delay *1000); // read_timeout is in seconds. timeout = DEVICE.read_timeout *10 *timeout_count; // compile regular expressions if (expect && expect[0]) { if (regcomp(&re, expect, REG_EXTENDED|REG_NOSUB) != 0) { fprintf(stderr, "Programming error: Expected answer %s is not a valid regepr\n", expect); writelogfile(LOG_CRIT, 1, "Programming error: Expected answer %s is not a valid regepr", expect); exit(1); } regex_allocated = 1; } // 3.1.5: Detect and handle routed message. Detect unexpected input. if ((DEVICE.incoming && DEVICE.detect_message_routing) || DEVICE.detect_unexpected_input) { *loganswer = 0; do { i = strlen(loganswer); read_from_modem(loganswer, sizeof(loganswer), 2); // One read attempt is 200ms } while (strlen(loganswer) > (size_t)i); i = 0; if (DEVICE.incoming && DEVICE.detect_message_routing) i = detect_routed_message(loganswer); if (!i && *loganswer && DEVICE.detect_unexpected_input) { if (!log_unmodified) { cutspaces(loganswer); cut_emptylines(loganswer); if (log_single_lines) change_crlf(loganswer, ' '); } if (*loganswer) { // Some modems send unsolicited result code even when status report is stored for future // reading. This and SMS-DELIVER indication is not logged. if (!strstr(loganswer, "+CDSI:") && !strstr(loganswer, "+CMTI:")) if (!(strstr(loganswer, "+CLIP:") && DEVICE.phonecalls == 2)) if (!(strstr(loganswer, "+CREG:") && get_loglevel() >= DEVICE.loglevel_lac_ci)) // 3.1.14. writelogfile(LOG_ERR, DEVICE.unexpected_input_is_trouble, "Unexpected input: %s", loganswer); if (handlephonecall_clip(loganswer) != 1) if (strstr(loganswer, "RING") && DEVICE.phonecalls != 2) do_hangup(loganswer); } } } // clean input buffer // It seems that this command does not do anything because actually it // does not clear the input buffer. tcflush(modem_handle, TCIFLUSH); // send command if (write_to_modem(command, 30, 1, 0) == 0) { t_sleep(errorsleeptime); // Free memory used by regexp if (regex_allocated) regfree(&re); last_command_ended = time_usec(); return 0; } if (!answer) { if (!silent) writelogfile(LOG_DEBUG, 0, "Command is sent"); } else { if (!silent) writelogfile(LOG_DEBUG, 0, "Command is sent, waiting for the answer"); // wait for the modem-answer answer[0] = 0; timeoutcounter = 0; got_timeout = 1; last_length = 0; do { read_from_modem(answer, max, 2); // One read attempt is 200ms // check if it's the expected answer if (expect && expect[0] && (regexec(&re, answer, (size_t) 0, NULL, 0) == 0)) { got_timeout = 0; put_command_timeouts = 0; break; } // 3.1.1: Some modem does not give "OK" in the answer for CPMS: // +CPMS: "SM",0,30,"SM",0,30,"SM",0,30 if (strstr(answer, "+CPMS:")) { int i = 0; char *p = answer; while ((p = strchr(p +1, ','))) i++; if (i >= 8) { // 8 commas is enough got_timeout = 0; put_command_timeouts = 0; break; } } // ------------------------------------------------------------ // 3.1.12: If got something from the modem, do not count timeout: //timeoutcounter += 2; i = strlen(answer); if (i == last_length) timeoutcounter += 2; else { last_length = i; timeoutcounter = 0; } } // repeat until timeout while (timeoutcounter < timeout); if (got_timeout) { put_command_timeouts++; if (expect && expect[0]) writelogfile(LOG_DEBUG, 1, "put_command expected %s, timeout occurred. %i.", expect, put_command_timeouts); } if (DEVICE.incoming && DEVICE.detect_message_routing) detect_routed_message(answer); // 3.1.5: Some modems (like Westermo GDW-11) start answer with extra , check and remove it: while (!got_timeout && !strncmp(answer, "\r\n", 2)) strcpyo(answer, answer +2); // 3.1.4: error explanation should be included in the answer. if (!got_timeout && strstr(answer, "ERROR")) { char *p; p = get_gsm_error(answer); if (*p) if (max /*sizeof(answer)*/ -strlen(answer) > strlen(p) +3) sprintf(strchr(answer, 0), " (%s)", p); } snprintf(loganswer, sizeof(loganswer), "%s", answer); if (!log_unmodified) { cutspaces(loganswer); cut_emptylines(loganswer); if (log_single_lines) change_crlf(loganswer, ' '); } writelogfile(LOG_DEBUG, 0, "<- %s", loganswer); // 3.1.12: Check if the answer contains a phonecall: if (DEVICE.detect_unexpected_input) { if (handlephonecall_clip(loganswer) != 1) if (strstr(loganswer, "RING") && DEVICE.phonecalls != 2) do_hangup(loganswer); } } // Free memory used by regexp if (regex_allocated) regfree(&re); last_command_ended = time_usec(); if (got_timeout) return -2; if (answer) return strlen(answer); return 1; } void setmodemparams() { struct termios newtio; int baudrate; if (DEVICE_IS_SOCKET) return; bzero(&newtio, sizeof(newtio)); newtio.c_cflag = CS8 | CLOCAL | CREAD | O_NDELAY | O_NONBLOCK; if (DEVICE.rtscts) newtio.c_cflag |= CRTSCTS; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; newtio.c_cc[VMIN] = 0; baudrate = DEVICE.baudrate; switch (baudrate) { #ifdef B50 case 50: baudrate = B50; break; #endif #ifdef B75 case 75: baudrate = B75; break; #endif #ifdef B110 case 110: baudrate = B110; break; #endif #ifdef B134 case 134: baudrate = B134; break; #endif #ifdef B150 case 150: baudrate = B150; break; #endif #ifdef B200 case 200: baudrate = B200; break; #endif case 300: baudrate = B300; break; #ifdef B600 case 600: baudrate = B600; break; #endif case 1200: baudrate = B1200; break; #ifdef B1800 case 1800: baudrate = B1800; break; #endif case 2400: baudrate = B2400; break; #ifdef B4800 case 4800: baudrate = B4800; break; #endif case 9600: baudrate = B9600; break; case 19200: baudrate = B19200; break; case 38400: baudrate = B38400; break; #ifdef B57600 case 57600: baudrate = B57600; break; #endif #ifdef B115200 case 115200: baudrate = B115200; break; #endif #ifdef B230400 case 230400: baudrate = B230400; break; #endif #ifdef B460800 case 460800: baudrate = B460800; break; #endif #ifdef B500000 case 500000: baudrate = B500000; break; #endif #ifdef B576000 case 576000: baudrate = B576000; break; #endif #ifdef B921600 case 921600: baudrate = B921600; break; #endif #ifdef B1000000 case 1000000: baudrate = B1000000; break; #endif #ifdef B1152000 case 1152000: baudrate = B1152000; break; #endif #ifdef B1500000 case 1500000: baudrate = B1500000; break; #endif #ifdef B2000000 case 2000000: baudrate = B2000000; break; #endif #ifdef B2500000 case 2500000: baudrate = B2500000; break; #endif #ifdef B3000000 case 3000000: baudrate = B3000000; break; #endif #ifdef B3500000 case 3500000: baudrate = B3500000; break; #endif #ifdef B4000000 case 4000000: baudrate = B4000000; break; #endif default: writelogfile(LOG_ERR, 0, "Baudrate %d not supported, using 19200", baudrate); baudrate = B19200; DEVICE.baudrate = 19200; } cfsetispeed(&newtio, baudrate); cfsetospeed(&newtio, baudrate); tcsetattr(modem_handle, TCSANOW, &newtio); } int initmodem(char *new_smsc, int receiving) { char command[100]; char answer[500]; int retries=0; char *p; char *pre_initstring = "ATE0+CMEE=1\r"; char *pre_initstring_clip = "ATE0+CMEE=1;+CLIP=1\r"; static int reading_checked = 0; STATISTICS->last_init = time(0); // 3.1beta7: terminating is only checked in case of errors. // ----------------------------------------------------------------------------------------------- writelogfile(LOG_INFO, 0, "Checking if modem is ready"); // 3.1.7: After being idle, some modems do not answer to the first AT command. // With BenQ M32, there can be OK answer, but in many times there is not. // To avoid error messages, first send AT and read the answer if it's available. if (DEVICE.needs_wakeup_at) { put_command("AT\r", 0, 0, 1, 0); usleep_until(time_usec() + 100000); read_from_modem(answer, sizeof(answer), 2); } retries=0; do { flush_smart_logging(); retries++; put_command("AT\r", answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (!strstr(answer, "OK") && !strstr(answer, "ERROR")) { if (terminate) return 7; // if Modem does not answer, try to send a PDU termination character put_command("\x1A\r", answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (terminate) return 7; } // 3.1.7: If it looks like modem does not respond, try to close and open the port: if (retries >= 5 && !strstr(answer, "OK")) { try_closemodem(1); t_sleep(1); // If open fails, nothing can be done. Error is already logged. Will return 1. if (!try_openmodem()) break; } } while (retries <= 10 && !strstr(answer,"OK")); if (!strstr(answer,"OK")) { // 3.1: more detailed error message: p = get_gsm_error(answer); // 3.1.5: writelogfile0(LOG_ERR, 1, tb_sprintf("Modem is not ready to answer commands%s%s (Timeouts: %i)", (*p)? ", " : "", p, put_command_timeouts)); alarm_handler0(LOG_ERR, tb); return 1; } // ----------------------------------------------------------------------------------------------- if (DEVICE.pre_init > 0) { writelogfile(LOG_INFO, 0, "Pre-initializing modem"); // 3.1.14: //put_command((DEVICE.phonecalls == 2)? pre_initstring_clip : pre_initstring, answer, sizeof(answer), 2, EXPECT_OK_ERROR); snprintf(command, sizeof(command), "%s", (DEVICE.phonecalls == 2)? pre_initstring_clip : pre_initstring); if (get_loglevel() >= DEVICE.loglevel_lac_ci) if (sizeof(command) > strlen(command) +8) strcpy(command +strlen(command) -1, ";+CREG=2\r"); put_command(command, answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (!strstr(answer,"OK")) writelogfile(LOG_ERR, 1, "Modem did not accept the pre-init string"); } // ----------------------------------------------------------------------------------------------- // 3.1.1: //if (pin[0]) if (strcasecmp(DEVICE.pin, "ignore")) { char *cpin_expect = "(READY)|( PIN)|( PUK)|(ERROR)"; // Previously: "(\\+CPIN:.*OK)|(ERROR)" writelogfile(LOG_INFO, 0, "Checking if modem needs PIN"); // 3.1.5: timeout from 50 to 100: put_command("AT+CPIN?\r", answer, sizeof(answer), 2, cpin_expect); // 3.1.7: Some modems include quotation marks in the answer, like +CPIN: "READY". while ((p = strchr(answer, '"'))) strcpyo(p, p +1); // 3.1.12: Allow "echo on": if ((p = strstr(answer, "+CPIN:"))) if (p > answer) strcpyo(answer, p); // 3.1.7: Some modems may leave a space away after +CPIN: if (!strncmp(answer, "+CPIN:", 6) && strncmp(answer, "+CPIN: ", 7)) { if ((p = strdup(answer))) { snprintf(answer, sizeof(answer), "+CPIN: %s", p + 6); free(p); } } if (strstr(answer,"+CPIN: SIM PIN") && !strstr(answer, "PIN2")) { // 3.1.1: if (DEVICE.pin[0] == 0) writelogfile(LOG_NOTICE, 1, "Modem needs PIN, but it's not defined for this modem"); else { writelogfile(LOG_NOTICE, 0, "Modem needs PIN, entering PIN..."); sprintf(command, "AT+CPIN=\"%s\"\r", DEVICE.pin); put_command(command, answer, sizeof(answer), 6, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) { p = get_gsm_error(answer); writelogfile(LOG_NOTICE, 1, "PIN entering: modem answered %s%s%s", change_crlf(cut_emptylines(cutspaces(answer)), ' '), (*p)? ", " : "", p); } else // After a PIN is entered, some modems need some time before next commands are processed. if (DEVICE.pinsleeptime > 0) { writelogfile(LOG_INFO, 0, "Spending sleep time after PIN entering (%i sec)", DEVICE.pinsleeptime); t_sleep(DEVICE.pinsleeptime); } put_command("AT+CPIN?\r", answer, sizeof(answer), 1, cpin_expect); if (strstr(answer,"+CPIN: SIM PIN") && !strstr(answer, "PIN2")) { writelogfile0(LOG_ERR, 1, tb_sprintf("Modem did not accept this PIN")); alarm_handler0(LOG_ERR, tb); abnormal_termination(0); } if (strstr(answer,"+CPIN: READY")) writelogfile(LOG_INFO, 0, "PIN Ready"); } } if (strstr(answer,"+CPIN: SIM PUK")) { writelogfile0(LOG_CRIT, 1, tb_sprintf("Your SIM is locked. Unlock it manually")); alarm_handler0(LOG_CRIT, tb); abnormal_termination(0); } if (!strstr(answer, "+CPIN: READY")) { p = get_gsm_error(answer); writelogfile0(LOG_CRIT, 1, tb_sprintf("PIN handling: expected READY, modem answered %s%s%s", change_crlf(cut_emptylines(cutspaces(answer)), ' '), (*p)? ", " : "", p)); alarm_handler0(LOG_CRIT, tb); abnormal_termination(0); } // 3.1.1: if (DEVICE.pin[0] == 0) strcpy(DEVICE.pin, "ignore"); } // ----------------------------------------------------------------------------------------------- if (DEVICE.initstring[0] || DEVICE.initstring2[0]) writelogfile(LOG_INFO, 0, "Initializing modem"); if (DEVICE.initstring[0]) { retries=0; do { retries++; put_command(DEVICE.initstring, answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) if (t_sleep(errorsleeptime)) return 7; } while (retries < 2 && !strstr(answer,"OK")); if (strstr(answer,"OK")==0) { // 3.1: more detailed error message: p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Modem did not accept the init string%s%s", (*p)? ", " : "", p)); alarm_handler0(LOG_ERR, tb); return 3; } } if (DEVICE.initstring2[0]) { retries=0; do { retries++; put_command(DEVICE.initstring2, answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) if (t_sleep(errorsleeptime)) return 7; } while (retries < 2 && !strstr(answer,"OK")); if (!strstr(answer,"OK")) { // 3.1: more detailed error message: p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Modem did not accept the second init string%s%s", (*p)? ", " : "", p)); alarm_handler0(LOG_ERR, tb); return 3; } // 3.1.5: else explain_csq(LOG_INFO, 0, answer, DEVICE.signal_quality_ber_ignore); } // ----------------------------------------------------------------------------------------------- if (DEVICE.status_signal_quality == 1 || (DEVICE.status_signal_quality == -1 && status_signal_quality == 1)) { retries=0; do { retries++; put_command("AT+CSQ\r", answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) if (t_sleep(errorsleeptime)) return 7; } while (retries < 2 && !strstr(answer,"OK")); // 3.1.12: Allow "echo on": //if (!strncmp(answer, "+CSQ:", 5)) if ((p = strstr(answer, "+CSQ:"))) { //STATISTICS->ssi = atoi(answer +5); STATISTICS->ssi = atoi(p +5); //if ((p = strchr(answer, ','))) if ((p = strchr(p, ','))) STATISTICS->ber = atoi(p +1); else STATISTICS->ber = -2; // 3.1.7: Explain signal quality to the log: explain_csq(LOG_INFO, 0, answer, DEVICE.signal_quality_ber_ignore); } else { STATISTICS->ssi = -2; STATISTICS->ber = -2; } } // ----------------------------------------------------------------------------------------------- // With check_network value 2 network is NOT checked when initializing for receiving: if (DEVICE.check_network == 1 || (DEVICE.check_network == 2 && !receiving)) { switch (wait_network_registration(1, 100)) { case -1: return 4; case -2: return 7; } } // ----------------------------------------------------------------------------------------------- writelogfile(LOG_INFO, 0, "Selecting PDU mode"); strcpy(command,"AT+CMGF=0\r"); retries=0; do { retries++; put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) if (t_sleep(errorsleeptime)) return 7; } while (retries < 2 && !strstr(answer,"OK")); if (strstr(answer,"ERROR")) { // 3.1: more detailed error message: p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Error: Modem did not accept mode selection%s%s", (*p)? ", " : "", p)); alarm_handler0(LOG_ERR, tb); return 5; } // ----------------------------------------------------------------------------------------------- if (!DEVICE.smsc_pdu && (new_smsc[0] || DEVICE.smsc[0])) { writelogfile(LOG_INFO, 0, "Changing SMSC"); // 3.1.7: clean + character(s) from the setting: //sprintf(command, "AT+CSCA=\"+%s\"\r", (new_smsc[0]) ? new_smsc : DEVICE.smsc); snprintf(answer, sizeof(answer), "%s", (new_smsc[0]) ? new_smsc : DEVICE.smsc); while (*answer == '+') strcpyo(answer, answer + 1); sprintf(command, "AT+CSCA=\"+%s\"\r", answer); retries=0; do { retries++; put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) if (t_sleep(errorsleeptime)) return 7; } while (retries < 2 && !strstr(answer,"OK")); if (strstr(answer,"ERROR")) { // 3.1: more detailed error message: p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Error: Modem did not accept SMSC%s%s", (*p)? ", " : "", p)); alarm_handler0(LOG_ERR, tb); return 6; } } // ----------------------------------------------------------------------------------------------- // 3.1beta7, 3.0.9: International Mobile Subscriber Identity is asked once. if (DEVICE.identity[0] == 0) { //writelogfile(LOG_INFO,m odemname,"Querying IMSI"); sprintf(command,"AT+CIMI\r"); put_command(command, DEVICE.identity, SIZE_IDENTITY, 1, EXPECT_OK_ERROR); // 3.1.5: do not remove ERROR text: if (!strstr(DEVICE.identity, "ERROR")) while (DEVICE.identity[0] && !isdigitc(DEVICE.identity[0])) strcpyo(DEVICE.identity, DEVICE.identity +1); // 3.1beta7: If CIMI is not supported, try CGSN (Product Serial Number) // TODO: is IMSI title still good? if (strstr(DEVICE.identity, "ERROR")) { sprintf(command,"AT+CGSN\r"); put_command(command, DEVICE.identity, SIZE_IDENTITY, 1, EXPECT_OK_ERROR); // 3.1: while (DEVICE.identity[0] && !isdigitc(DEVICE.identity[0])) strcpyo(DEVICE.identity, DEVICE.identity +1); } else { // 3.1.5: IMSI worked. Log CGSN for informative purposes: sprintf(command,"AT+CGSN\r"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (!strstr(answer, "ERROR")) while (answer[0] && !isdigitc(answer[0])) strcpyo(answer, answer +1); if ((p = strstr(answer, "OK"))) *p = 0; cut_ctrl(answer); cutspaces(answer); writelogfile(LOG_NOTICE, 0, "CGSN: %s", answer); } if (!strstr(DEVICE.identity, "ERROR")) { if ((p = strstr(DEVICE.identity, "OK"))) *p = 0; cut_ctrl(DEVICE.identity); cutspaces(DEVICE.identity); writelogfile(LOG_NOTICE, 0, "IMSI: %s", DEVICE.identity); } else writelogfile(LOG_NOTICE, 1, "IMSI/CGSN not supported"); } // ----------------------------------------------------------------------------------------------- // 3.1.5: Check once if reading of messages is not supported: // 3.1.7: Do not check if not reading incoming messages: if (DEVICE.incoming && !reading_checked) { reading_checked = 1; writelogfile(LOG_INFO, 0, "Checking if reading of messages is supported"); sprintf(command,"AT+CPMS?\r"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "+CPMS: ,,,,,,,,")) { sprintf(command,"AT+CPMS=?\r"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "+CPMS: (),(),()")) { writelogfile0(LOG_ERR, 1, tb_sprintf("Error: Looks like your device does not support reading of messages")); alarm_handler0(LOG_ERR, tb); } } } // ----------------------------------------------------------------------------------------------- // 3.1.7: Report details of device once: if (DEVICE.report_device_details) { int save_log_single_lines = log_single_lines; int i; char tmp[256]; char *commands[] = { "AT+CGMI", "Manufacturer identification", "AT+CGMM", "Model identification", "AT+CGMR", "Revision identification", "AT+CNMI=?", "New message indications, list of supported modes", "AT+CNMI?", "New message indications, current settings", "AT+CPMS=?", "Preferred message storage, list of supported mem's", //"AT+CPMS?", "Preferred message storage, current mem's and counters", "AT+CPBS=?", "Phonebook storage, available mem's", //"AT+CPBS?", "Phonebook storage, current storage and counters", "AT+CMGL=?", "List messages, list of supported stat's", "AT+CMGD=?", "Delete message, list of supported values", "AT+CPAS=?", "Phone activity status, list of supported stat's", "AT+CSCS=?", "TE character set, list of supported charset's", "AT+CSCS?", "TE character set, current setting", "" // end marker }; DEVICE.report_device_details = 0; log_single_lines = 0; change_loglevel(LOG_DEBUG); writelogfile(LOG_DEBUG, 0, "## Start of device details"); for (i = 0; commands[i][0]; i += 2) { if (terminate) break; snprintf(tmp, sizeof(tmp), "# %s:", commands[i + 1]); writelogfile(LOG_DEBUG, 0, tmp); sprintf(command, "%s\r", commands[i]); put_command0(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR, 1); } writelogfile(LOG_DEBUG, 0, "## End of device details"); log_single_lines = save_log_single_lines; restore_loglevel(); } // ----------------------------------------------------------------------------------------------- // TODO: Check if AT+CMGD=? is supported. return 0; } int initialize_modem_sending(char *new_smsc) { return initmodem(new_smsc, 0); } int initialize_modem_receiving() { return initmodem("", 1); } #ifndef DISABLE_INET_SOCKET /* Start Changes by Hubert Gilch, SEP Logistik AG * * 2 functions for connecting to a socket instead of a serial device * in order to use ethernet GPRS-modems * * Code was "stolen" from interceptty by Scott W. Gifford * */ struct sockaddr_in inet_resolve(const char *sockname) { struct sockaddr_in sa; char *hostname, *netport; struct hostent *he; if (!(hostname = strdup(sockname))) writelogfile(LOG_CRIT, 0, "Couldn't dup string: %s", strerror(errno)); netport = strchr(hostname, ':'); *netport = '\0'; netport++; sa.sin_family = AF_INET; if (!(he = gethostbyname(hostname))) writelogfile(LOG_ERR, 0, "Couldn't resolve name '%s': %s.", hostname, (h_errno == HOST_NOT_FOUND) ? "Host not found" : ((h_errno == NO_ADDRESS) || (h_errno == NO_DATA)) ? "No data available" : (h_errno == NO_RECOVERY) ? "A non-recoverable name server error occured" : (h_errno == TRY_AGAIN) ? "A temporary error occured." : "An unknown error occured"); memcpy(&(sa.sin_addr), he->h_addr, he->h_length); #if 0 if (!(se = getservbyname(netport))) writelogfile(LOG_ERR, 0, "Couldn't resolve port."); host_port = htons(se->s_port); #endif if (!(sa.sin_port = htons(atoi(netport)))) writelogfile(LOG_ERR, 0, "Couldn't figure out port number."); free(hostname); return sa; } int open_inet_socket(char *backend) { struct sockaddr_in sa; int fd; int socketflags; int retries = 0; sa = inet_resolve(backend + 1); // cut first character @ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 3) { tb_sprintf("Couldn't open socket: %s: %s", backend, strerror(errno)); writelogfile0(LOG_ERR, 0, tb); alarm_handler0(LOG_ERR, tb); return -1; } while (connect(fd, (struct sockaddr *) &sa, sizeof(sa)) != 0) { retries++; if (terminate || (DEVICE.socket_connection_retries != -1 && retries > DEVICE.socket_connection_retries)) { close(fd); return (terminate)? -2 : -1; } tb_sprintf("Couldn't connect socket %s, error: %s, waiting %i sec.", backend, strerror(errno), DEVICE.socket_connection_errorsleeptime); if (retries - 1 == DEVICE.socket_connection_alarm_after) { writelogfile0(LOG_ERR, 0, tb); alarm_handler0(LOG_ERR, tb); } else { // Do not log the first failure: if (retries > 1) writelogfile(LOG_INFO, 0, tb); } t_sleep(DEVICE.socket_connection_errorsleeptime); } socketflags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, socketflags | O_NONBLOCK); return fd; } /* * End Changes by Hubert Gilch */ #endif int openmodem() { int retries = 0; // 3.1.7: //modem_handle = open(DEVICE.device, O_RDWR | O_NOCTTY | O_NONBLOCK); /* * if devicename starts with "@" it is not a serial device but * a socket, so open a socket instead a device file * * Change by Hubert Gilch, SEP Logistik AG */ #ifndef DISABLE_INET_SOCKET if (DEVICE_IS_SOCKET) modem_handle = open_inet_socket(DEVICE.device); else #endif { // 3.1.12: if (DEVICE.modem_disabled) { struct stat statbuf; if (stat(DEVICE.device, &statbuf) != 0) { FILE *fp; if ((fp = fopen(DEVICE.device, "w"))) fclose(fp); } } // 3.1.7: while ((modem_handle = open(DEVICE.device, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0) { retries++; if (terminate || (DEVICE.device_open_retries != -1 && retries > DEVICE.device_open_retries)) break; tb_sprintf("Couldn't open serial port %s, error: %s, waiting %i sec.", DEVICE.device, strerror(errno), DEVICE.device_open_errorsleeptime); if (retries - 1 == DEVICE.device_open_alarm_after) { writelogfile0(LOG_ERR, 0, tb); alarm_handler0(LOG_ERR, tb); } else writelogfile(LOG_INFO, 0, tb); t_sleep(DEVICE.device_open_errorsleeptime); } } if (modem_handle < 0) { if (modem_handle == -1) { writelogfile0(LOG_ERR, 1, tb_sprintf((DEVICE_IS_SOCKET)? "Cannot open socket %s, error: %s" : "Cannot open serial port %s, error: %s", DEVICE.device, strerror(errno))); alarm_handler0(LOG_ERR, tb); } return -1; } if (strstr(smsd_version, "beta")) { if (DEVICE_IS_SOCKET) writelogfile(LOG_INFO, 0, "Socket %s opened as %i", DEVICE.device, modem_handle); else writelogfile(LOG_INFO, 0, "Serial port %s opened as %i, rtscts: %i, baudrate: %i", DEVICE.device, modem_handle, DEVICE.rtscts, DEVICE.baudrate); } return modem_handle; } int talk_with_modem() { int result = 0; int n; char tmp[256]; struct termios newtset, oldtset; char newdevice[PATH_MAX]; int stdinflags; int set_nonblock = 0; int idle; modem_handle = -1; stdinflags = fcntl(STDIN_FILENO, F_GETFL); if (!(stdinflags & O_NONBLOCK)) { if (fcntl(STDIN_FILENO, F_SETFL, stdinflags | O_NONBLOCK) == -1) printf("Failed to set STDIN nonblock.\n"); else set_nonblock = 1; } tcgetattr(STDIN_FILENO, &oldtset); newtset = oldtset; newtset.c_lflag &= ~(ECHO | ICANON); newtset.c_iflag &= ~ICRNL; newtset.c_cc[VMIN] = 1; newtset.c_cc[VTIME] = 0; tcsetattr(STDIN_FILENO, TCSAFLUSH, &newtset); printf("Communicating with %s. ( Press Ctrl-C to abort. )\n", process_title); printf("( If you need to send Ctrl-Z, change the suspend character first, like stty susp \\^N )\n"); writelogfile(LOG_CRIT, 0, "Communicating with terminal."); printf("Default device is %s\n", DEVICE.device); printf("Press Enter to start or type an another device name.\n"); *newdevice = 0; while (!terminate) { idle = 0; if ((n = read(STDIN_FILENO, tmp, (modem_handle != -1)? sizeof(tmp) -1 : 1)) > 0) { if (modem_handle != -1) { tmp[n] = 0; write_to_modem(tmp, 5, 0, 1); } else { if (*tmp == 13) { printf("\n"); fflush(stdout); if (*newdevice) strcpy(DEVICE.device, newdevice); printf("Opening device %s\n", DEVICE.device); if (openmodem() == -1) { printf("Cannot open device %s, cause: %s.\n", DEVICE.device, strerror(errno)); *newdevice = 0; continue; } setmodemparams(); printf("Ready.\n"); result = 1; } else if (*tmp) { printf("%c", *tmp); fflush(stdout); if (*tmp == 127 || *tmp == 8) { if (*newdevice) newdevice[strlen(newdevice) -1] = 0; } else { if (strlen(newdevice) < sizeof(newdevice) -1) sprintf(strchr(newdevice, 0), "%c", *tmp); else { printf("\nDevice name too long.\n"); *newdevice = 0; continue; } } } } } else idle = 1; if (modem_handle != -1) { if ((n = read(modem_handle, tmp, sizeof(tmp) -1)) > 0) { // 3.1.12: if (log_read_from_modem) { char temp[SIZE_LOG_LINE]; int i; char *answer = tmp; snprintf(temp, sizeof(temp), "read_from_modem: got=%i:", n); for (i = 0; i < n; i++) { if (strlen(temp) >= sizeof(temp) - 6) { strcpy(temp, "ERROR: too much data"); break; } sprintf(strchr(temp, 0), " %02X[%c]", (unsigned char) answer[i], ((unsigned char) answer[i] >= ' ') ? answer[i] : '.'); } writelogfile(LOG_CRIT, 0, temp); } // 3.1.12: if (DEVICE_IS_SOCKET) negotiate_with_telnet(tmp, &n); write(STDOUT_FILENO, tmp, n); idle = 0; } } if (idle) usleep_until(time_usec() + 100000); } if (modem_handle >= 0) close(modem_handle); if (set_nonblock) fcntl(STDIN_FILENO, F_SETFL, stdinflags & ~O_NONBLOCK); tcsetattr(STDIN_FILENO, TCSANOW, &oldtset); return result; } // Return value: // -2 = terminated // -1 = modem is not registered // >= 0 = number of retries, modem is registered int wait_network_registration(int waitnetwork_errorsleeptime, int retry_count) { char answer[500]; int success = 0; int retries = 0; int registration_denied = 0; static int registration_ok = 0; char *p; // 3.1.14: static char prev_lac[32] = ""; static char prev_ci[32] = ""; char lac[32]; char ci[32]; writelogfile(LOG_INFO, 0, "Checking if Modem is registered to the network"); do { flush_smart_logging(); // 3.1: signal quality is logged: if (retries > 0) { put_command("AT+CSQ\r", answer, sizeof(answer), 2, EXPECT_OK_ERROR); // 3.1.5: ...with details: explain_csq(LOG_NOTICE, 0, answer, DEVICE.signal_quality_ber_ignore); } put_command("AT+CREG?\r", answer, sizeof(answer), 2, "(\\+CREG:.*OK)|(ERROR)"); // 3.1.14: if (get_loglevel() >= DEVICE.loglevel_lac_ci) { if ((p = strchr(answer, '\r'))) *p = ','; getfield(answer, 3, lac, sizeof(lac)); getfield(answer, 4, ci, sizeof(ci)); if (strlen(ci) > 4) memmove(ci, ci +strlen(ci) -4, 5); } // 3.1.1: Some modem include spaces in the response: while ((p = strchr(answer, ' '))) strcpyo(p, p +1); // 3.1.1: Drop additional fields: if ((p = strchr(answer, ','))) if ((p = strchr(p +1, ','))) *p = 0; // 3.1.1: Some modem (Motorola) gives values using three digits like "000,001": if ((p = strstr(answer, ",00"))) strcpyo(p +1, p +3); // 3.1: // Second field is tested. if (strstr(answer, ",1")) { writelogfile(LOG_INFO, 0, "Modem is registered to the network"); success = 1; } else if (strstr(answer, ",5")) { writelogfile(LOG_INFO, 0, "Modem is registered to a roaming partner network"); success = 1; } // 3.1.1: 3 - Registration denied is handled else if (strstr(answer, ",3")) { // 3.1.5: After a SIM has once been successfully registered to the network, failure with registration // does not stop the modem process. //if (registration_denied < 2) if (registration_ok || registration_denied < 2) { writelogfile(LOG_INFO, 1, "Modem said: registration denied. Retrying."); registration_denied++; if (t_sleep(waitnetwork_errorsleeptime)) return -2; } else { writelogfile0(LOG_ERR, 1, tb_sprintf("Error: registration is denied.")); alarm_handler0(LOG_ERR, tb); abnormal_termination(0); } } else if (strstr(answer,"ERROR")) { writelogfile(LOG_INFO, 1, "Ignoring that modem does not support +CREG command."); success = 1; DEVICE.check_network = 0; } else if (strstr(answer,"+CREG:")) { // 3.1.14: Skip logging if defined. Call alarmhandler. if (retries >= DEVICE.log_not_registered_after) { writelogfile0(LOG_NOTICE, 1, tb_sprintf("MODEM IS NOT REGISTERED, WAITING %i SEC. BEFORE RETRYING %i. TIME", waitnetwork_errorsleeptime, retries +1)); alarm_handler0(LOG_NOTICE, tb); } if (t_sleep(waitnetwork_errorsleeptime)) return -2; } else { writelogfile0(LOG_ERR, 1, tb_sprintf("Error: Unexpected answer from Modem after +CREG?, waiting %i sec. before retrying", waitnetwork_errorsleeptime)); alarm_handler0(LOG_ERR, tb); if (t_sleep(waitnetwork_errorsleeptime)) return -2; } if (!success) retries++; } while (!success && retries < retry_count); if (!success) { writelogfile0(LOG_ERR, 1, tb_sprintf("Error: Modem is not registered to the network")); alarm_handler0(LOG_ERR, tb); return -1; } // 3.1.14: if (get_loglevel() >= DEVICE.loglevel_lac_ci && *lac && *ci) { if (*prev_lac && *prev_ci) { if (strcmp(prev_lac, lac)) writelogfile(DEVICE.loglevel_lac_ci, 0, "Location area code changed: %s -> %s", prev_lac, lac); if (strcmp(prev_ci, ci)) writelogfile(DEVICE.loglevel_lac_ci, 0, "Cell ID changed: %s -> %s", prev_ci, ci); if (strcmp(prev_lac, lac) || strcmp(prev_ci, ci)) { put_command("AT+CSQ\r", answer, sizeof(answer), 2, EXPECT_OK_ERROR); explain_csq(DEVICE.loglevel_lac_ci, 0, answer, DEVICE.signal_quality_ber_ignore); } } else { writelogfile(DEVICE.loglevel_lac_ci, 0, "Location area code: %s, Cell ID: %s", lac, ci); put_command("AT+CSQ\r", answer, sizeof(answer), 2, EXPECT_OK_ERROR); explain_csq(DEVICE.loglevel_lac_ci, 0, answer, DEVICE.signal_quality_ber_ignore); } snprintf(prev_lac, sizeof(prev_lac), "%s", lac); snprintf(prev_ci, sizeof(prev_ci), "%s", ci); } registration_ok = 1; return retries; } int try_closemodem(int force) { int keep_open; if (force) keep_open = 0; else keep_open = DEVICE.keep_open; if (modem_handle >= 0 && !keep_open) { if (1 && strstr(smsd_version, "beta")) { writelogfile(LOG_INFO, 0, "Device %s (%i) closed", DEVICE.device, modem_handle); writelogfile(LOG_INFO, 0, "***********"); } #ifdef DEBUGMSG printf("!! Closing device %s\n", DEVICE.device); #endif close(modem_handle); modem_handle = -2; } return (modem_handle >= 0); } int try_openmodem() { int result = 1; if (modem_handle >= 0) { #ifdef DEBUGMSG printf("!! Opening device %s: already open\n", DEVICE.device); #endif return 1; } #ifdef DEBUGMSG printf("!! Opening device %s\n", DEVICE.device); #endif if (openmodem() == -1) { result = 0; #ifdef DEBUGMSG printf("!! Opening FAILED\n"); #endif } else { #ifdef DEBUGMSG printf("!! Setting modem parameters\n"); #endif put_command_timeouts = 0; setmodemparams(); } return result; } smstools-3.1.15/src/modeminit.h000077500000000000000000000050621223712572200164220ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef MODEMINIT_H #define MODEMINIT_H char *get_gsm_cme_error(int code); char *get_gsm_cms_error(int code); char *get_gsm_error(char *answer); char *explain_csq_buffer(char *buffer, int short_form, int ssi, int ber, int signal_quality_ber_ignore); void explain_csq(int loglevel, int short_form, char *answer, int signal_quality_ber_ignore); int write_to_modem(char *command, int timeout, int log_command, int print_error); int read_from_modem(char *answer, int max, int timeout); char *change_crlf(char *str, char ch); // Open the serial port, returns file handle or -1 on error int openmodem(); // Setup serial port void setmodemparams(); // Send init strings. // Returns 0 on success // 1 modem is not ready // 2 cannot enter pin // 3 cannot enter init strings // 4 modem is not registered // 5 cannot enter pdu mode // 6 cannot enter smsc number // 7 seen that the thread is going to terminate // 3.1.5: now private: int initmodem(char *new_smsc, int receiving); int initialize_modem_sending(char *new_smsc); int initialize_modem_receiving(); // Sends a command to the modem and waits max timout*0.1 seconds for an answer. // The function returns the length of the answer. // The function waits until a timeout occurs or the expected answer occurs. // modem is the serial port file handle // command is the command to send (may be empty or NULL) // answer is the received answer // max is the maxmum allowed size of the answer // timeout control the time how long to wait for the answer // expect is an extended regular expression. If this matches the modem answer, // then the program stops waiting for the timeout (may be empty or NULL). int put_command(char *command, char *answer, int max, int timeout_count, char *expect); int put_command0(char *command, char *answer, int max, int timeout_count, char *expect, int silent); int talk_with_modem(); int wait_network_registration(int waitnetwork_errorsleeptime, int retry_count); int try_closemodem(int force); int try_openmodem(); #endif smstools-3.1.15/src/pdu.c000077500000000000000000001673621223712572200152340ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include "pdu.h" #include "smsd_cfg.h" #include "logging.h" #include "charset.h" // required for conversions of partial text content. #include "extras.h" #define MAX_ADDRESS_LENGTH 50 #define MAX_SMSC_ADDRESS_LENGTH 30 char *err_too_short = "string is too short"; char *err_pdu_content = "invalid character(s) in string"; int add_warning(char *buffer, char *format, ...) { int result = 1; va_list argp; char text[2048]; char *title = "Warning: "; va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); if (buffer) { if (strlen(buffer) + strlen(text) +strlen(title) +1/* for \n */ < SIZE_WARNING_HEADERS) sprintf(strchr(buffer, 0), "%s%s\n", title, text); else { result = 0; writelogfile(LOG_ERR, 1, "PDU %s%s", title, text); } } return result; } void pdu_error(char **err_str, char *title, int position, int length, char *format, ...) { va_list argp; char text[2048]; char *default_title = "PDU ERROR: "; char *used_title; char tmp[51]; va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); used_title = (title)? title : default_title; if (position >= 0) { if (length > 0) sprintf(tmp, "Position %i,%i: ", position +1, length); else sprintf(tmp, "Position %i: ", position +1); } else *tmp = 0; if (!(*err_str)) { if ((*err_str = (char *)malloc(strlen(tmp) +strlen(text) +strlen(used_title) +2))) *err_str[0] = 0; } else *err_str = (char *)realloc((void *)*err_str, strlen(*err_str) +strlen(used_title) +strlen(tmp) +strlen(text) +2); if (*err_str) sprintf(strchr(*err_str, 0), "%s%s%s\n", used_title, tmp, text); } int isXdigit(char ch) { if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')) return 1; return 0; } /* Swap every second character */ void swapchars(char* string) { int Length; int position; char c; Length=strlen(string); for (position=0; positionmaxsms_pdu-udh_size_septets) length=maxsms_pdu-udh_size_septets; //clear the tmp buffer for (character=0;(size_t)charactermaxsms_binary) length=maxsms_binary; pdu[0]=0; for (character=0;character0) flags+=32; // Request Status Report if (alphabet == 1) coding = 4; // 8bit binary else if (alphabet == 2) coding = 8; // 16bit else coding = 0; // 7bit if (flash_sms > 0) coding += 0x10; // Bits 1 and 0 have a message class meaning (class 0, alert) /* Create the PDU string of the message */ if (alphabet==1 || alphabet==2 || system_msg) { // Unicode message can be concatenated: //if (alphabet == 2 && with_udh) // Binary message can be concatenated too: if (with_udh) { strcpy(tmp2, udh_data); while ((p = strchr(tmp2, ' '))) strcpyo(p, p +1); l = strlen(tmp2) /2; binary2pdu(message, messagelen, strchr(tmp2, 0)); messagelen += l; } else binary2pdu(message,messagelen,tmp2); } else messagelen=text2pdu(message,messagelen,tmp2,udh_data); /* concatenate the first part of the PDU string */ if (strcmp(mode,"old")==0) sprintf(pdu,"%02X00%02X%02X%s00%02X%02X",flags,numberlength,numberformat,tmp,coding,messagelen); else { int proto = 0; if (validity < 0 || validity > 255) validity = validity_period; if (system_msg) { proto = 0x40; coding = 0xF4; // binary // 3.1.7: if (system_msg == 2) { proto += (0x7F - 0x40); // SS (no show) coding += 2; // store to sim } } else if (replace_msg >= 1 && replace_msg <= 7) proto = 0x40 + replace_msg; //sprintf(pdu, "00%02X00%02X%02X%s%02X%02X%02X%02X", flags, numberlength, numberformat, tmp, // (system_msg) ? 0x40 : (replace_msg >= 1 && replace_msg <= 7) ? 0x40 + replace_msg : 0, // (system_msg) ? 0xF4 : coding, validity, messagelen); // 3.1.12: //sprintf(pdu, "00%02X00%02X%02X%s%02X%02X%02X%02X", flags, numberlength, numberformat, tmp, proto, coding, validity, messagelen); if (*tmp_smsc) sprintf(pdu, "%02X%s%s", (int)strlen(tmp_smsc) / 2 + 1, (tmp_smsc[1] == '0')? "81": "91", tmp_smsc); else strcpy(pdu, "00"); sprintf(strchr(pdu, 0), "%02X00%02X%02X%s%02X%02X%02X%02X", flags, numberlength, numberformat, tmp, proto, coding, validity, messagelen); } /* concatenate the text to the PDU string */ strcat(pdu,tmp2); } int octet2bin(char* octet) /* converts an octet to a 8-Bit value */ { int result=0; if (octet[0]>57) result+=octet[0]-55; else result+=octet[0]-48; result=result<<4; if (octet[1]>57) result+=octet[1]-55; else result+=octet[1]-48; return result; } // Converts an octet to a 8bit value, // returns < in case of error. int octet2bin_check(char *octet) { if (octet[0] == 0) return -1; if (octet[1] == 0) return -2; if (!isXdigit(octet[0])) return -3; if (!isXdigit(octet[1])) return -4; return octet2bin(octet); } // Return value: -1 = error, 0 = not found. // 1 = found 8bit, 2 = found 16bit. // udh must be in header format, "05 00 03 02 03 02 " int get_remove_concatenation(char *udh, int *message_id, int *parts, int *part) { int udh_length; int octets; int idx; char *con_start = NULL; int id; int i; char tmp[10]; if ((udh_length = octet2bin_check(udh)) < 0) return -1; octets = strlen(udh) /3; idx = 1; while (idx < octets) { if ((id = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; if (id == 0x00 || id == 0x08) { // It's here. con_start = udh +idx *2 +idx; if (++idx >= octets) return -1; i = octet2bin_check(udh +idx *2 +idx); if ((id == 0x00 && i != 0x03) || (id == 0x08 && i != 0x04)) return -1; if (++idx >= octets) return -1; if ((*message_id = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; if (id == 0x08) { if (++idx >= octets) return -1; if ((i = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; *message_id = *message_id *0xFF +i; } if (++idx >= octets) return -1; if ((*parts = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; if (++idx >= octets) return -1; if ((*part = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; if (++idx >= octets) *con_start = 0; else strcpy(con_start, udh +idx *2 +idx); i = (id == 0x00)? 5 : 6; udh_length -= i; if (udh_length > 0) { sprintf(tmp, "%02X", udh_length); memcpy(udh, tmp, 2); } else *udh = 0; return (id == 0x00)? 1 : 2; } else { // Something else data. Get the length and skip. if (++idx >= octets) return -1; if ((i = octet2bin_check(udh +idx *2 +idx)) < 0) return -1; idx += i +1; } } return 0; } int get_concatenation(char *udh, int *message_id, int *parts, int *part) { char *tmp; int result = -1; if ((tmp = strdup(udh))) { result = get_remove_concatenation(tmp, message_id, parts, part); free(tmp); } return result; } int remove_concatenation(char *udh) { int message_id; int parts; int part; return get_remove_concatenation(udh, &message_id, &parts, &part); } // Returns a length of udh (including UDHL), -1 if error. // pdu is 0-terminated ascii(hex) pdu string with // or without spaces. int explain_udh(char *udh_type, char *pdu) { int udh_length; int idx; char *Src_Pointer; char *p; int i; char tmp[512]; char buffer[1024]; *udh_type = 0; if (strlen(pdu) >= sizeof(buffer)) return -1; strcpy(buffer, pdu); while ((p = strchr(buffer, ' '))) strcpyo(p, p +1); if ((udh_length = octet2bin_check(buffer)) < 0) return -1; udh_length++; if ((size_t)(udh_length *2) > strlen(buffer)) return -1; sprintf(udh_type, "Length=%i", udh_length); idx = 1; while (idx < udh_length) { Src_Pointer = buffer +idx *2; p = NULL; i = octet2bin_check(Src_Pointer); switch (i) { case -1: //sprintf(strchr(udh_type, 0), ", ERROR"); return -1; // 3GPP TS 23.040 version 6.8.1 Release 6 - ETSI TS 123 040 V6.8.1 (2006-10) case 0x00: p = "Concatenated short messages, 8-bit reference number"; break; case 0x01: p = "Special SMS Message Indication"; break; case 0x02: p = "Reserved"; break; //case 0x03: p = "Value not used to avoid misinterpretation as character"; break; case 0x04: p = "Application port addressing scheme, 8 bit address"; break; case 0x05: p = "Application port addressing scheme, 16 bit address"; break; case 0x06: p = "SMSC Control Parameters"; break; case 0x07: p = "UDH Source Indicator"; break; case 0x08: p = "Concatenated short message, 16-bit reference number"; break; case 0x09: p = "Wireless Control Message Protocol"; break; case 0x0A: p = "Text Formatting"; break; case 0x0B: p = "Predefined Sound"; break; case 0x0C: p = "User Defined Sound (iMelody max 128 bytes)"; break; case 0x0D: p = "Predefined Animation"; break; case 0x0E: p = "Large Animation (16*16 times 4 = 32*4 =128 bytes)"; break; case 0x0F: p = "Small Animation (8*8 times 4 = 8*4 =32 bytes)"; break; case 0x10: p = "Large Picture (32*32 = 128 bytes)"; break; case 0x11: p = "Small Picture (16*16 = 32 bytes)"; break; case 0x12: p = "Variable Picture"; break; case 0x13: p = "User prompt indicator"; break; case 0x14: p = "Extended Object"; break; case 0x15: p = "Reused Extended Object"; break; case 0x16: p = "Compression Control"; break; case 0x17: p = "Object Distribution Indicator"; break; case 0x18: p = "Standard WVG object"; break; case 0x19: p = "Character Size WVG object"; break; case 0x1A: p = "Extended Object Data Request Command"; break; case 0x20: p = "RFC 822 E-Mail Header"; break; case 0x21: p = "Hyperlink format element"; break; case 0x22: p = "Reply Address Element"; break; case 0x23: p = "Enhanced Voice Mail Information"; break; } if (!p) { if (i >= 0x1B && i <= 0x1F) p = "Reserved for future EMS features"; else if (i >= 0x24 && i <= 0x6F) p = "Reserved for future use"; else if (i >= 0x70 && i <= 0x7F) p = "(U)SIM Toolkit Security Headers"; else if (i >= 0x80 && i <= 0x9F) p = "SME to SME specific use"; else if (i >= 0xA0 && i <= 0xBF) p = "Reserved for future use"; else if (i >= 0xC0 && i <= 0xDF) p = "SC specific use"; else if (i >= 0xE0 && i <= 0xFF) p = "Reserved for future use"; } if (!p) p = "unknown"; sprintf(tmp, ", [%.2s]%s", Src_Pointer, p); if (strlen(udh_type) + strlen(tmp) >= SIZE_UDH_TYPE) return -1; sprintf(strchr(udh_type, 0), "%s", tmp); // Next octet is length of data: if ((i = octet2bin_check(Src_Pointer +2)) < 0) return -1; if ((size_t)(i *2) > strlen(Src_Pointer +4)) return -1; idx += i +2; if (idx > udh_length) return -1; // Incorrect UDL or length of Information Element. } return udh_length; } /* converts a PDU-String to text, text might contain zero values! */ /* the first octet is the length */ /* return the length of text, -1 if there is a PDU error, -2 if PDU is too short */ /* with_udh must be set already if the message has an UDH */ /* this function does not detect the existance of UDH automatically. */ int pdu2text(char *pdu, char *text, int *text_length, int *expected_length, int with_udh, char *udh, char *udh_type, int *errorpos) { int bitposition; int byteposition; int byteoffset; int charcounter; int bitcounter; int septets; int octets; int udhsize; int octetcounter; int skip_characters = 0; char c; char binary = 0; int i; int result; #ifdef DEBUGMSG printf("!! pdu2text(pdu=%s,...)\n",pdu); #endif if (udh) *udh = 0; if (udh_type) *udh_type = 0; if ((septets = octet2bin_check(pdu)) < 0) { if (errorpos) *errorpos = -1 * septets -3; return (septets >= -2)? -2: -1; } if (with_udh) { // copy the data header to udh and convert to hex dump // There was at least one octet and next will give an error if there is no more data: if ((udhsize = octet2bin_check(pdu +2)) < 0) { if (errorpos) *errorpos = -1 * udhsize -3 +2; return (udhsize >= -2)? -2: -1; } i = 0; result = -1; for (octetcounter=0; octetcounter= SIZE_UDH_DATA) { i = octetcounter *2 +2; result = -2; break; } udh[octetcounter*3]=pdu[(octetcounter<<1)+2]; if (!isXdigit(udh[octetcounter *3])) { i = octetcounter *2 +2; if (!udh[octetcounter *3]) result = -2; break; } udh[octetcounter*3+1]=pdu[(octetcounter<<1)+3]; if (!isXdigit(udh[octetcounter *3 +1])) { i = octetcounter *2 +3; if (!udh[octetcounter *3 +1]) result = -2; break; } udh[octetcounter *3 +2] = ' '; udh[octetcounter *3 +3] = 0; } if (i) { if (errorpos) *errorpos = i; return result; } if (udh_type) if (explain_udh(udh_type, pdu +2) < 0) if (strlen(udh_type) +7 < SIZE_UDH_TYPE) sprintf(strchr(udh_type, 0), "%sERROR", (*udh_type)? ", " : ""); // Calculate how many text charcters include the UDH. // After the UDH there may follow filling bits to reach a 7bit boundary. skip_characters=(((udhsize+1)*8)+6)/7; #ifdef DEBUGMSG printf("!! septets=%i\n",septets); printf("!! udhsize=%i\n",udhsize); printf("!! skip_characters=%i\n",skip_characters); #endif } if (expected_length) *expected_length = septets -skip_characters; // Convert from 8-Bit to 7-Bit encapsulated in 8 bit // skipping storing of some characters used by UDH. // 3.1beta7: Simplified handling to allow partial decodings to be shown. octets = (septets *7 +7) /8; bitposition = 0; octetcounter = 0; for (charcounter = 0; charcounter < septets; charcounter++) { c = 0; for (bitcounter = 0; bitcounter < 7; bitcounter++) { byteposition = bitposition /8; byteoffset = bitposition %8; while (byteposition >= octetcounter && octetcounter < octets) { if ((i = octet2bin_check(pdu +(octetcounter << 1) +2)) < 0) { if (errorpos) { *errorpos = octetcounter *2 +2; if (i == -2 || i == -4) (*errorpos)++; } if (text_length) *text_length = charcounter -skip_characters; return (i >= -2)? -2: -1; } binary = i; octetcounter++; } if (binary & (1 << byteoffset)) c = c | 128; bitposition++; c = (c >> 1) & 127; // The shift fills with 1, but 0 is wanted. } if (charcounter >= skip_characters) text[charcounter -skip_characters] = c; } if (text_length) *text_length = charcounter -skip_characters; if (charcounter -skip_characters >= 0) text[charcounter -skip_characters] = 0; return charcounter -skip_characters; } int pdu2text0(char *pdu, char *text) { return pdu2text(pdu, text, 0, 0, 0, 0, 0, 0); } // Converts a PDU string to binary. Return -1 if there is a PDU error, -2 if PDU is too short. // Version > 3.0.9, > 3.1beta6 handles also udh. int pdu2binary(char* pdu, char* binary, int *data_length, int *expected_length, int with_udh, char *udh, char *udh_type, int *errorpos) { int octets; int octetcounter; int i; int udhsize = 0; int skip_octets = 0; int result; *udh = 0; *udh_type = 0; if ((octets = octet2bin_check(pdu)) < 0) { *errorpos = -1 * octets -3; return (octets >= -2)? -2: -1; } if (with_udh) { // copy the data header to udh and convert to hex dump // There was at least one octet and next will give an error if there is no more data: if ((udhsize = octet2bin_check(pdu +2)) < 0) { *errorpos = -1 * udhsize -3 +2; return (udhsize >= -2)? -2: -1; } i = 0; result = -1; for (octetcounter = 0; octetcounter < udhsize +1; octetcounter++) { if (octetcounter *3 +3 >= SIZE_UDH_DATA) { i = octetcounter *2 +2; result = -2; break; } udh[octetcounter *3] = pdu[(octetcounter << 1) +2]; if (!isXdigit(udh[octetcounter *3])) { i = octetcounter *2 +2; if (!udh[octetcounter *3]) result = -2; break; } udh[octetcounter *3 +1] = pdu[(octetcounter << 1) +3]; if (!isXdigit(udh[octetcounter *3 +1])) { i = octetcounter *2 +3; if (!udh[octetcounter *3 +1]) result = -2; break; } udh[octetcounter *3 +2] = ' '; udh[octetcounter *3 +3] = 0; } if (i) { *errorpos = i; return result; } if (udh_type) if (explain_udh(udh_type, pdu +2) < 0) if (strlen(udh_type) +7 < SIZE_UDH_TYPE) sprintf(strchr(udh_type, 0), "%sERROR", (*udh_type)? ", " : ""); skip_octets = udhsize +1; } *expected_length = octets -skip_octets; for (octetcounter = 0; octetcounter < octets -skip_octets; octetcounter++) { if ((i = octet2bin_check(pdu +(octetcounter << 1) +2 +(skip_octets *2))) < 0) { *errorpos = octetcounter *2 +2 +(skip_octets *2); if (i == -2 || i == -4) (*errorpos)++; *data_length = octetcounter; return (i >= -2)? -2: -1; } else binary[octetcounter] = i; } if (octets -skip_octets >= 0) binary[octets -skip_octets] = 0; *data_length = octets -skip_octets; return octets -skip_octets; } int explain_toa(char *dest, char *octet_char, int octet_int) { int result; char *p = "reserved"; if (octet_char) result = octet2bin_check(octet_char); else result = octet_int; if (result != -1) { switch ((result & 0x70) >> 4) { case 0: p = "unknown"; break; case 1: p = "international"; break; case 2: p = "national"; break; case 3: p = "network specific"; break; case 4: p = "subsciber"; break; case 5: p = "alphanumeric"; break; case 6: p = "abbreviated"; break; //case 7: p = "reserved"; break; } if (octet_char) sprintf(dest, "%.2s %s", octet_char, p); else sprintf(dest, "%02X %s", octet_int, p); switch (result & 0x0F) { case 0: p = "unknown"; break; case 1: p = "ISDN/telephone"; break; case 3: p = "data"; break; case 4: p = "telex"; break; case 8: p = "national"; break; case 9: p = "private"; break; case 10: p = "ERMES"; break; //default: p = "reserved"; break; } sprintf(strchr(dest, 0), ", %s", p); } return result; } // 3.1.14: void explain_status(char *dest, size_t size_dest, int status) { char *p = "unknown"; switch (status) { case 0: p = "Ok,short message received by the SME"; break; case 1: p = "Ok,short message forwarded by the SC to the SME but the SC is unable to confirm delivery"; break; case 2: p = "Ok,short message replaced by the SC"; break; // Temporary error, SC still trying to transfer SM case 32: p = "Still trying,congestion"; break; case 33: p = "Still trying,SME busy"; break; case 34: p = "Still trying,no response sendr SME"; break; case 35: p = "Still trying,service rejected"; break; case 36: p = "Still trying,quality of service not available"; break; case 37: p = "Still trying,error in SME"; break; // 38...47: Reserved // 48...63: Values specific to each SC // Permanent error, SC is not making any more transfer attempts case 64: p = "Error,remote procedure error"; break; case 65: p = "Error,incompatible destination"; break; case 66: p = "Error,connection rejected by SME"; break; case 67: p = "Error,not obtainable"; break; case 68: p = "Error,quality of service not available"; break; case 69: p = "Error,no interworking available"; break; case 70: p = "Error,SM validity period expired"; break; case 71: p = "Error,SM deleted by originating SME"; break; case 72: p = "Error,SM deleted by SC administration"; break; case 73: p = "Error,SM does not exist"; break; // 74...79: Reserved // 80...95: Values specific to each SC // Permanent error, SC is not making any more transfer attempts case 96: p = "Error,congestion"; break; case 97: p = "Error,SME busy"; break; case 98: p = "Error,no response sendr SME"; break; case 99: p = "Error,service rejected"; break; case 100: p = "Error,quality of service not available"; break; case 101: p = "Error,error in SME"; break; // 102...105: Reserved // 106...111: Reserved // 112...127: Values specific to each SC // 128...255: Reserved default: if (status >= 48 && status <= 63) p = "Temporary error, SC specific, unknown"; else if ((status >= 80 && status <= 95) || (status >= 112 && status <= 127)) p = "Permanent error, SC specific, unknown"; } snprintf(dest, size_dest, "%s", p); } // Subroutine for messages type 0 (SMS-Deliver) // Input: // Src_Pointer points to the PDU string // Output: // sendr Sender // date and time Date/Time-stamp // message the message text or binary data // bin_udh: 1 if udh is taken from the PDU with binary messages too // returns length of message int split_type_0(char *full_pdu, char* Src_Pointer, int* alphabet, char* sendr, char* date, char* time, char* message, int *message_length, int *expected_length, int with_udh, char* udh_data, char *udh_type, char *from_toa, int *replace, char **err_str, char *warning_headers, int *flash, int bin_udh) { int Length; int padding = 0; char tmpsender[100]; int result = 0; int i; int errorpos; #ifdef DEBUGMSG printf("!! split_type_0(Src_Pointer=%s, ...\n",Src_Pointer); #endif // There should be at least address-length and address-type: if (strlen(Src_Pointer) < 4) pdu_error(err_str, 0, Src_Pointer -full_pdu, 4, "While trying to read address length and address type: %s", err_too_short); else { Length = octet2bin_check(Src_Pointer); // 3.1.5: Sender address length can be zero. There is still address type octet (80). if (Length < 0 || Length > MAX_ADDRESS_LENGTH) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid sender address length: \"%.2s\"", Src_Pointer); else if (Length == 0) Src_Pointer += 4; else { padding=Length%2; Src_Pointer+=2; i = explain_toa(from_toa, Src_Pointer, 0); if (i < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid sender address type: \"%.2s\"", Src_Pointer); else if (i < 0x80) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Missing bit 7 in sender address type: \"%.2s\"", Src_Pointer); else { Src_Pointer += 2; if ((i & 112) == 80) { // Sender is alphanumeric if (strlen(Src_Pointer) < (size_t)(Length +padding)) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While trying to read sender address (alphanumeric, length %i): %s", Length +padding, err_too_short); else { snprintf(tmpsender,Length+3+padding,"%02X%s",Length*4/7,Src_Pointer); if (pdu2text0(tmpsender, sendr) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While reading alphanumeric sender address: %s", err_pdu_content); } } else { // sender is numeric if (strlen(Src_Pointer) < (size_t)(Length +padding)) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While trying to read sender address (numeric, length %i): %s", Length +padding, err_too_short); else { strncpy(sendr, Src_Pointer, Length +padding); sendr[Length +padding] = 0; swapchars(sendr); i = Length +padding -1; if (padding) { if (sendr[i] != 'F') add_warning(warning_headers, "Length of numeric sender address is odd, but not terminated with 'F'."); else sendr[i] = 0; } else { if (sendr[i] == 'F') { add_warning(warning_headers, "Length of numeric sender address is even, but still was terminated with 'F'."); sendr[i] = 0; } } for (i = 0; sendr[i]; i++) if (!isdigitc(sendr[i])) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "Invalid character(s) in sender address: \"%s\"", sendr); // *sendr = 0; add_warning(warning_headers, "Invalid character(s) in sender address."); break; } } } } } if (!(*err_str)) { Src_Pointer += Length +padding; // Next there should be: // XX protocol identifier // XX data encoding scheme // XXXXXXXXXXXXXX time stamp, 7 octets // XX length of user data // ( XX... user data ) if (strlen(Src_Pointer) < 20) pdu_error(err_str, 0, Src_Pointer -full_pdu, 20, "While trying to read TP-PID, TP-DSC, TP-SCTS and TP-UDL: %s", err_too_short); else { if ((i = octet2bin_check(Src_Pointer)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid protocol identifier: \"%.2s\"", Src_Pointer); else { if ((i & 0xF8) == 0x40) *replace = (i & 0x07); Src_Pointer += 2; if ((i = octet2bin_check(Src_Pointer)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid data encoding scheme: \"%.2s\"", Src_Pointer); else { *alphabet = (i & 0x0C) >>2; if (*alphabet == 3) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid alphabet in data encoding scheme: value 3 is not supported."); // ...or should this be a warning? If so, GSM alphabet is then used as a default. if (*alphabet == 0) *alphabet = -1; // 3.1: Check if this message was a flash message: if (i & 0x10) if (!(i & 0x01)) *flash = 1; if (!(*err_str)) { Src_Pointer += 2; sprintf(date,"%c%c-%c%c-%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]); if (!isdigitc(date[0]) || !isdigitc(date[1]) || !isdigitc(date[3]) || !isdigitc(date[4]) || !isdigitc(date[6]) || !isdigitc(date[7])) { pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid character(s) in date of Service Centre Time Stamp: \"%s\"", date); *date = 0; } else if (atoi(date +3) > 12 || atoi(date +6) > 31) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid value(s) in date of Service Centre Time Stamp: \"%s\"", date); // *date = 0; add_warning(warning_headers, "Invalid values(s) in date of Service Centre Time Stamp."); } Src_Pointer += 6; sprintf(time,"%c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]); if (!isdigitc(time[0]) || !isdigitc(time[1]) || !isdigitc(time[3]) || !isdigitc(time[4]) || !isdigitc(time[6]) || !isdigitc(time[7])) { pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid character(s) in time of Service Centre Time Stamp: \"%s\"", time); *time = 0; } else if (atoi(time) > 23 || atoi(time +3) > 59 || atoi(time +6) > 59) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid value(s) in time of Service Centre Time Stamp: \"%s\"", time); // *time = 0; add_warning(warning_headers, "Invalid values(s) in time of Service Centre Time Stamp."); } if (!(*err_str)) { Src_Pointer += 6; // Time zone is not used but bytes are checked: if (octet2bin_check(Src_Pointer) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid character(s) in Time Zone of Service Centre Time Stamp: \"%.2s\"", Src_Pointer); else Src_Pointer += 2; } } } } } } if (!(*err_str)) { // Src_Pointer now points to the User data length, which octet exists. // TODO: Can udh-len be zero? if (*alphabet <= 0) { if ((result = pdu2text(Src_Pointer, message, message_length, expected_length, with_udh, udh_data, udh_type, &errorpos)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu +errorpos, 0, "While reading TP-UD (GSM text): %s", (result == -1)? err_pdu_content : err_too_short); } else { // With binary messages udh is NOT taken from the PDU. i = with_udh; // 3.1.5: it should work now: if (bin_udh == 0) if (*alphabet == 1) i = 0; if ((result = pdu2binary(Src_Pointer, message, message_length, expected_length, i, udh_data, udh_type, &errorpos)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu +errorpos, 0, "While reading TP-UD (%s): %s", (*alphabet == 1)? "binary" : "UCS2 text", (result == -1)? err_pdu_content : err_too_short); } } } return result; } // Subroutine for messages type 2 (Status Report) // Input: // Src_Pointer points to the PDU string // Output: // sendr Sender // date and time Date/Time-stamp // result is the status value and text translation int split_type_2(char *full_pdu, char* Src_Pointer,char* sendr, char* date,char* time,char* result, char *from_toa, char **err_str, char *warning_headers) { int Length; int padding; int status; char temp[32]; char tmpsender[100]; int messageid; int i; const char SR_MessageId[] = "Message_id:"; // Fixed title inside the status report body. const char SR_Status[] = "Status:"; // Fixed title inside the status report body. strcat(result,"SMS STATUS REPORT\n"); // There should be at least message-id, address-length and address-type: if (strlen(Src_Pointer) < 6) pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "While trying to read message id, address length and address type: %s", err_too_short); else { // get message id if ((messageid = octet2bin_check(Src_Pointer)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid message id: \"%.2s\"", Src_Pointer); else { sprintf(strchr(result, 0), "%s %i\n", SR_MessageId, messageid); // get recipient address Src_Pointer+=2; Length = octet2bin_check(Src_Pointer); if (Length < 1 || Length > MAX_ADDRESS_LENGTH) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid recipient address length: \"%.2s\"", Src_Pointer); else { padding=Length%2; Src_Pointer+=2; i = explain_toa(from_toa, Src_Pointer, 0); if (i < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid recipient address type: \"%.2s\"", Src_Pointer); else if (i < 0x80) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Missing bit 7 in recipient address type: \"%.2s\"", Src_Pointer); else { Src_Pointer += 2; if ((i & 112) == 80) { // Sender is alphanumeric if (strlen(Src_Pointer) < (size_t)(Length +padding)) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While trying to read recipient address (alphanumeric, length %i): %s", Length +padding, err_too_short); else { snprintf(tmpsender,Length+3+padding,"%02X%s",Length*4/7,Src_Pointer); if (pdu2text0(tmpsender, sendr) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While reading alphanumeric recipient address: %s", err_pdu_content); } } else { // sender is numeric if (strlen(Src_Pointer) < (size_t)(Length +padding)) pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "While trying to read recipient address (numeric, length %i): %s", Length +padding, err_too_short); else { strncpy(sendr,Src_Pointer,Length+padding); sendr[Length +padding] = 0; swapchars(sendr); i = Length +padding -1; if (padding) { if (sendr[i] != 'F') add_warning(warning_headers, "Length of numeric recipient address is odd, but not terminated with 'F'."); else sendr[i] = 0; } else { if (sendr[i] == 'F') { add_warning(warning_headers, "Length of numeric recipient address is even, but still was terminated with 'F'."); sendr[i] = 0; } } for (i = 0; sendr[i]; i++) if (!isdigitc(sendr[i])) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, Length +padding, "Invalid character(s) in recipient address: \"%s\"", sendr); // *sendr = 0; add_warning(warning_headers, "Invalid character(s) in recipient address."); break; } } } if (!(*err_str)) { Src_Pointer+=Length+padding; if (strlen(Src_Pointer) < 14) pdu_error(err_str, 0, Src_Pointer -full_pdu, 14, "While trying to read SMSC Timestamp: %s", err_too_short); else { // get SMSC timestamp sprintf(date,"%c%c-%c%c-%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]); if (!isdigitc(date[0]) || !isdigitc(date[1]) || !isdigitc(date[3]) || !isdigitc(date[4]) || !isdigitc(date[6]) || !isdigitc(date[7])) { pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid character(s) in date of SMSC Timestamp: \"%s\"", date); *date = 0; } else if (atoi(date +3) > 12 || atoi(date +6) > 31) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid value(s) in date of SMSC Timestamp: \"%s\"", date); // *date = 0; add_warning(warning_headers, "Invalid value(s) in date of SMSC Timestamp."); } Src_Pointer += 6; sprintf(time,"%c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4]); if (!isdigitc(time[0]) || !isdigitc(time[1]) || !isdigitc(time[3]) || !isdigitc(time[4]) || !isdigitc(time[6]) || !isdigitc(time[7])) { pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid character(s) in time of SMSC Timestamp: \"%s\"", time); *time = 0; } else if (atoi(time) > 23 || atoi(time +3) > 59 || atoi(time +6) > 59) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, 6, "Invalid value(s) in time of SMSC Timestamp: \"%s\"", time); // *time = 0; add_warning(warning_headers, "Invalid value(s) in time of SMSC Timestamp."); } if (!(*err_str)) { Src_Pointer += 6; // Time zone is not used but bytes are checked: if (octet2bin_check(Src_Pointer) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid character(s) in Time Zone of SMSC Time Stamp: \"%.2s\"", Src_Pointer); else Src_Pointer += 2; } } } if (!(*err_str)) { if (strlen(Src_Pointer) < 14) pdu_error(err_str, 0, Src_Pointer -full_pdu, 14, "While trying to read Discharge Timestamp: %s", err_too_short); else { // get Discharge timestamp sprintf(temp,"%c%c-%c%c-%c%c %c%c:%c%c:%c%c",Src_Pointer[1],Src_Pointer[0],Src_Pointer[3],Src_Pointer[2],Src_Pointer[5],Src_Pointer[4],Src_Pointer[7],Src_Pointer[6],Src_Pointer[9],Src_Pointer[8],Src_Pointer[11],Src_Pointer[10]); if (!isdigitc(temp[0]) || !isdigitc(temp[1]) || !isdigitc(temp[3]) || !isdigitc(temp[4]) || !isdigitc(temp[6]) || !isdigitc(temp[7]) || !isdigitc(temp[9]) || !isdigitc(temp[10]) || !isdigitc(temp[12]) || !isdigitc(temp[13]) || !isdigitc(temp[15]) || !isdigitc(temp[16])) pdu_error(err_str, 0, Src_Pointer -full_pdu, 12, "Invalid character(s) in Discharge Timestamp: \"%s\"", temp); else if (atoi(temp +3) > 12 || atoi(temp +6) > 31 || atoi(temp +9) > 24 || atoi(temp +12) > 59 || atoi(temp +16) > 59) { // Not a fatal error (?) //pdu_error(err_str, 0, Src_Pointer -full_pdu, 12, "Invalid value(s) in Discharge Timestamp: \"%s\"", temp); add_warning(warning_headers, "Invalid values(s) in Discharge Timestamp."); } if (!(*err_str)) { Src_Pointer += 12; // Time zone is not used but bytes are checked: if (octet2bin_check(Src_Pointer) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid character(s) in Time Zone of Discharge Time Stamp: \"%.2s\"", Src_Pointer); else Src_Pointer += 2; } } if (!(*err_str)) { sprintf(strchr(result, 0), "Discharge_timestamp: %s", temp); if (strlen(Src_Pointer) < 2) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "While trying to read Status octet: %s", err_too_short); else { // get Status if ((status = octet2bin_check(Src_Pointer)) < 0) pdu_error(err_str, 0, Src_Pointer -full_pdu, 2, "Invalid Status octet: \"%.2s\"", Src_Pointer); else { char buffer[128]; explain_status(buffer, sizeof(buffer), status); sprintf(strchr(result, 0), "\n%s %i,%s", SR_Status, status, buffer); } } } } } } } } return strlen(result); } // Splits a PDU string into the parts // Input: // pdu is the pdu string // mode can be old or new and selects the pdu version // Output: // alphabet indicates the character set of the message. // sendr Sender // date and time Date/Time-stamp // message is the message text or binary message // smsc that sent this message // with_udh return the udh flag of the message // is_statusreport is 1 if this was a status report // is_unsupported_pdu is 1 if this pdu was not supported // Returns the length of the message int splitpdu(char *pdu, char *mode, int *alphabet, char *sendr, char *date, char *time, char *message, char *smsc, int *with_udh, char *a_udh_data, char *a_udh_type, int *is_statusreport, int *is_unsupported_pdu, char *from_toa, int *report, int *replace, char *warning_headers, int *flash, int bin_udh) { int Length; int Type; char* Pointer; int result = 0; char *err_str = NULL; char *save_err_str = NULL; char *try_mode = mode; int try_count; int i; int message_length; int expected_length; char tmp_udh_data[SIZE_UDH_DATA] = {}; char tmp_udh_type[SIZE_UDH_TYPE] = {}; char *udh_data; char *udh_type; udh_data = (a_udh_data)? a_udh_data : tmp_udh_data; udh_type = (a_udh_type)? a_udh_type : tmp_udh_type; // Patch for Wavecom SR memory reading: if (strncmp(pdu, "000000FF00", 10) == 0) { strcpyo(pdu, pdu +8); while (strlen(pdu) < 52) strcat(pdu, "00"); } // ------------------------------------ for (try_count = 0; try_count < 2; try_count++) { if (try_count) { if (strcmp(mode, "new") == 0) try_mode = "old"; else try_mode = "new"; } message_length = 0; expected_length = 0; sendr[0]=0; date[0]=0; time[0]=0; message[0]=0; smsc[0]=0; *alphabet=0; *with_udh=0; *udh_data = 0; *udh_type = 0; *is_statusreport = 0; *is_unsupported_pdu = 0; from_toa[0] = 0; *report = 0; *replace = -1; *flash = 0; if (warning_headers) *warning_headers = 0; #ifdef DEBUGMSG printf("!! splitpdu(pdu=%s, mode=%s, ...)\n",pdu,mode); #endif Pointer=pdu; if (strlen(Pointer) < 2) pdu_error(&err_str, 0, Pointer -pdu, 2, "While trying to read first octet: %s", err_too_short); else { if (strcmp(try_mode, "new") == 0) { if ((Length = octet2bin_check(Pointer)) < 0) pdu_error(&err_str, 0, Pointer -pdu, 2, "While reading first octet: %s", err_pdu_content); else { // smsc number is not mandatory if (Length == 0) Pointer += 2; else { // Address type and at least one octet is expected: if (Length < 2 || Length > MAX_SMSC_ADDRESS_LENGTH) pdu_error(&err_str, 0, Pointer -pdu, 2, "Invalid sender SMSC address length: \"%.2s\"", Pointer); else { Length = Length *2 -2; // No padding because the given value is number of octets. if (strlen(Pointer) < (size_t)(Length +4)) pdu_error(&err_str, 0, Pointer -pdu, Length +4, "While trying to read sender SMSC address (length %i): %s", Length, err_too_short); else { Pointer += 2; i = octet2bin_check(Pointer); if (i < 0) pdu_error(&err_str, 0, Pointer -pdu, 2, "Invalid sender SMSC address type: \"%.2s\"", Pointer); else if (i < 0x80) pdu_error(&err_str, 0, Pointer -pdu, 2, "Missing bit 7 in sender SMSC address type: \"%.2s\"", Pointer); else { Pointer += 2; strncpy(smsc, Pointer, Length); smsc[Length] = 0; swapchars(smsc); // Does any SMSC use alphanumeric number? if ((i & 112) == 80) { // There can be only hex digits from the original PDU. // The number itself is wrong in this case. for (i = 0; smsc[i]; i++) if (!isXdigit(smsc[i])) { pdu_error(&err_str, 0, Pointer -pdu, Length, "Invalid character(s) in alphanumeric SMSC address: \"%s\"", smsc); *smsc = 0; break; } } else { // Last character is allowed as F (and dropped) but all other non-numeric will produce an error: if (smsc[Length -1] == 'F') smsc[Length -1] = 0; for (i = 0; smsc[i]; i++) if (!isdigitc(smsc[i])) { // Not a fatal error (?) //pdu_error(&err_str, 0, Pointer -pdu, Length, "Invalid character(s) in numeric SMSC address: \"%s\"", smsc); // *smsc = 0; add_warning(warning_headers, "Invalid character(s) in numeric SMSC address"); break; } } if (!err_str) Pointer += Length; } } } } } } if (!err_str) { if (strlen(Pointer) < 2) pdu_error(&err_str, 0, Pointer -pdu, 2, "While trying to read First octet of the SMS-DELIVER PDU: %s", err_too_short); else { if ((i = octet2bin_check(Pointer)) < 0) pdu_error(&err_str, 0, Pointer -pdu, 2, "While reading First octet of the SMS-DELIVER PDU: %s", err_pdu_content); else { // Unused bits 3 and 4 should be zero, failure with this produces a warning: if (i & 0x18) add_warning(warning_headers, "Unused bits 3 and 4 are used in the first octet of the SMS-DELIVER PDU."); if (i & 0x40) // Is UDH bit set? *with_udh = 1; if (i & 0x20) // Is status report going to be returned to the SME? *report = 1; Type = i & 3; if (Type == 0) // SMS Deliver { Pointer += 2; result = split_type_0(pdu, Pointer, alphabet, sendr, date, time, message, &message_length, &expected_length, *with_udh, udh_data, udh_type, from_toa, replace, &err_str, warning_headers, flash, bin_udh); if (err_str && *udh_type) pdu_error(&err_str, "", -1, 0, "Message has Udh_type: %s", udh_type); // If a decoding fails, the reason is invalid or missing characters // in the PDU. Can also be too high TP-UDL value. // Decoders are modified to return partially decoded strings with an // additional length variables. Binary messages are not shown in the report. if (*alphabet != 1 && err_str && message_length > 0) { char ascii[MAXTEXT]; char title[101]; if (*alphabet <= 0) i = gsm2iso(message, message_length, ascii, sizeof(ascii)); else { memcpy(ascii, message, message_length); #ifndef USE_ICONV ascii[message_length] = 0; i = decode_ucs2(ascii, message_length); #else i = (int)iconv_ucs2utf(ascii, message_length, sizeof(ascii)); #endif expected_length /= 2; } if (i > 0) { sprintf(title, "Partial content of text (%i characters, expected %i):\n", i, expected_length); pdu_error(&err_str, title, -1, 0, "%s", ascii); } } } else if (Type == 2) // Status Report { Pointer += 2; result = split_type_2(pdu, Pointer, sendr, date, time, message, from_toa, &err_str, warning_headers); *is_statusreport=1; } else if (Type == 1) // Sent message { pdu_error(&err_str, "", -1, 0, "%s%.2s%s", "The PDU data (", Pointer, ") says that this is a sent message. Can only decode received messages."); *is_unsupported_pdu = 1; } else { pdu_error(&err_str, "", -1, 0, "%s%.2s%s%i%s", "The PDU data (", Pointer, ") says that the message format is ", Type, " which is not supported. Cannot decode."); *is_unsupported_pdu = 1; } } } } } if (!err_str) try_count++; // All ok, no more tries required. else { *alphabet = 0; *with_udh = 0; // Possible udh_data is now incorrect: *udh_data = 0; *udh_type = 0; *is_statusreport = 0; if (try_count == 0) { // First try. Save the result and try again with another PDU mode. if ((save_err_str = (char *)malloc(strlen(err_str) +1))) strcpy(save_err_str, err_str); } else { // Second try. Nothing more to do. Return report and some information. char *n_mode = "new (with CSA)"; char *o_mode = "old (without CSA)"; *message = 0; if (save_err_str) sprintf(message, "First tried with PDU mode %s:\n%s\nNext ", (*mode == 'n')? n_mode : o_mode, save_err_str); sprintf(strchr(message, 0), "tried with PDU mode %s:\n%s\n", (*mode == 'n')? o_mode : n_mode, err_str); sprintf(strchr(message, 0), "No success. This PDU cannot be decoded. There is something wrong.\n"); if (!(*is_unsupported_pdu)) strcat(message, "\nIf you are unsure, confused or angry, please view the GSM 03.40\n" "(ETSI TS 100 901) and related documents for details of correct\n" "PDU format. You can also get some help via the Support Website.\n"); *is_unsupported_pdu = 1; } result = strlen(message); free(err_str); err_str = NULL; } } if (save_err_str) free(save_err_str); return result; } int get_pdu_details(char *dest, size_t size_dest, char *pdu, int mnumber) { int result = 0; int udlen; int alphabet; char sender[100]; char date[9]; char time[9]; char ascii[MAXTEXT]; char smsc[31]; int with_udh; char udh_data[SIZE_UDH_DATA]; char udh_type[SIZE_UDH_TYPE]; int is_statusreport; int is_unsupported_pdu; char from_toa[51]; int report; int replace; char warning_headers[SIZE_WARNING_HEADERS]; int flash; int bin_udh = 1; int m_id = 999; // real id for concatenated messages only, others use this. size_t length_sender = 32; int p_count = 1; int p_number = 1; char buffer[100]; // 3.1.6: increased size, was too small (51). int i; char sort_ch; char *p; udlen = splitpdu(pdu, DEVICE.mode, &alphabet, sender, date, time, ascii, smsc, &with_udh, udh_data, udh_type, &is_statusreport, &is_unsupported_pdu, from_toa, &report, &replace, warning_headers, &flash, bin_udh); if (is_unsupported_pdu) { writelogfile(LOG_ERR, 1, "Message %i, unsupported PDU.", mnumber); result = 1; } else { if (with_udh) { if (get_remove_concatenation(udh_data, &m_id, &p_count, &p_number) < 0) { writelogfile(LOG_ERR, 1, "Message %i, error while checking UDH_DATA.", mnumber); result = 2; } } if (!result) { if (strlen(sender) > length_sender) { writelogfile(LOG_ERR, 1, "Message %i, too long sender field.", mnumber); result = 3; } while (strlen(sender) < length_sender) strcat(sender, " "); if (DEVICE.priviledged_numbers[0]) p = DEVICE.priviledged_numbers; else if (priviledged_numbers[0]) p = priviledged_numbers; else p = 0; sort_ch = 'Z'; if (p) { i = 0; while (*p) { if (strncmp(sender, p, strlen(p)) == 0) { sort_ch = 'A' +i; break; } i++; p = strchr(p, 0) +1; } } /* 001 A 358401234567____________________111 001/001 r 00-00-00 00-00-00n mnumber sort_ch sender m_id p_number p_count incoming / report date time snprintf(buffer, sizeof(buffer), "%.03i %c %s%.03i %.03i/%.03i %c %-8.8s %-8.8s\n", mnumber, sort_ch, sender, m_id, p_number, p_count, (is_statusreport) ? 'r' : 'i', date, time); 3.1.7: 001 A 00-00-00 00-00-00 358401234567____________________111 001/001 rn mnumber sort_ch date time sender m_id p_number p_count incoming / report */ snprintf(buffer, sizeof(buffer), "%.03i %c %-8.8s %-8.8s %s%.03i %.03i/%.03i %c\n", mnumber, sort_ch, date, time, sender, m_id, p_number, p_count, (is_statusreport) ? 'r' : 'i'); if (strlen(dest) +strlen(buffer) < size_dest) strcat(dest, buffer); else { writelogfile(LOG_ERR, 1, "Message %i, not enough storage space.", mnumber); result = 4; } } } return result; } int sort_pdu_helper(const void *a, const void *b) { //return(strncmp((char *)a +4, (char *)b +4, 45)); return (strncmp((char *) a + 4, (char *) b + 4, 63)); } void sort_pdu_details(char *dest) { int count; count = strlen(dest) / LENGTH_PDU_DETAIL_REC; if (count > 1) qsort((void *)dest, count, LENGTH_PDU_DETAIL_REC, sort_pdu_helper); } smstools-3.1.15/src/pdu.h000077500000000000000000000064451223712572200152330ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef PDU_H #define PDU_H #define SIZE_WARNING_HEADERS 4096 //Alphabet values: -1=GSM 0=ISO 1=binary 2=UCS2 #define NF_UNKNOWN 129 #define NF_INTERNATIONAL 145 #define NF_NATIONAL 161 int set_numberformat(int *numberformat, char *number, int number_type); // Make the PDU string from a mesage text and destination phone number. // The destination variable pdu has to be big enough. // alphabet indicates the character set of the message. // flash_sms enables the flash flag. // mode select the pdu version (old or new). // if udh is true, then udh_data contains the optional user data header in hex dump, example: "05 00 03 AF 02 01" void make_pdu(char* number, char* message, int messagelen, int alphabet, int flash_sms, int report, int udh, char* udh_data, char* mode, char* pdu, int validity, int replace_msg, int system_msg, int number_type, char *smsc); // Splits a PDU string into the parts // Input: // pdu is the pdu string // mode can be old or new and selects the pdu version // Output: // alphabet indicates the character set of the message. // sendr Sender // date and time Date/Time-stamp // message is the message text or binary message // smsc that sent this message // with_udh returns the udh flag of the message // is_statusreport is 1 if this was a status report // is_unsupported_pdu is 1 if this pdu was not supported // udh return the udh as hex dump // Returns the length of the message int splitpdu(char *pdu, char *mode, int *alphabet, char *sendr, char *date, char *time, char *message, char *smsc, int *with_udh, char *a_udh_data, char *a_udh_type, int *is_statusreport, int *is_unsupported_pdu, char *from_toa, int *report, int *replace, char *warning_headers, int *flash, int bin_udh); int octet2bin(char* octet); int octet2bin_check(char* octet); int isXdigit(char ch); // Returns a length of udh (including UDHL), -1 if error. // pdu is 0-terminated ascii(hex) pdu string with // or without spaces. int explain_udh(char *udh_type, char *pdu); // Return value: -1 = error, 0 = not found. // 1 = found 8bit, 2 = found 16bit. // udh must be in header format, "05 00 03 02 03 02 " int get_remove_concatenation(char *udh, int *message_id, int *parts, int *part); int get_concatenation(char *udh, int *message_id, int *parts, int *part); int remove_concatenation(char *udh); int explain_toa(char *dest, char *octet_char, int octet_int); void explain_status(char *dest, size_t size_dest, int status); int get_pdu_details(char *dest, size_t size_dest, char *pdu, int mnumber); void sort_pdu_details(char *dest); int pdu2text(char *pdu, char *text, int *text_length, int *expected_length, int with_udh, char *udh, char *udh_type, int *errorpos); int text2pdu(char* text, int length, char* pdu, char* udh); #endif smstools-3.1.15/src/smsd.c000077500000000000000000006232531223712572200154060ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #ifndef NOSTATS #include #endif #include #include #include #include #include #include "extras.h" #include "locking.h" #include "smsd_cfg.h" #include "stats.h" #include "version.h" #include "blacklist.h" #include "whitelist.h" #include "logging.h" #include "alarm.h" #include "charset.h" #include "cfgfile.h" #include "pdu.h" #include "modeminit.h" int logfilehandle; // handle of log file. int concatenated_id=0; // id number for concatenated messages. // This indicates that the PDU was read from file, not from SIM. #define PDUFROMFILE 22222 int break_workless_delay; // To break the delay when SIGCONT is received. int workless_delay; int break_suspend; // To break suspend when SIGUSR2 is received. const char *HDR_To = "To:"; // Msg file input. char HDR_To2[SIZE_HEADER] = {}; const char *HDR_ToTOA = "To_TOA:"; // Msg file input. Type Of Address (numbering plan): unknown (0), international (1), national (2). char HDR_ToTOA2[SIZE_HEADER] = {}; const char *HDR_From = "From:"; // Msg file input: informative, incoming message: senders address. char HDR_From2[SIZE_HEADER] = {}; const char *HDR_Flash = "Flash:"; // Msg file input. char HDR_Flash2[SIZE_HEADER] = {}; const char *HDR_Provider = "Provider:"; // Msg file input. char HDR_Provider2[SIZE_HEADER] = {}; const char *HDR_Queue = "Queue:"; // Msg file input. char HDR_Queue2[SIZE_HEADER] = {}; const char *HDR_Binary = "Binary:"; // Msg file input (sets alphabet to 1 or 0). char HDR_Binary2[SIZE_HEADER] = {}; const char *HDR_Report = "Report:"; // Msg file input. Incoming message: report was asked yes/no. char HDR_Report2[SIZE_HEADER] = {}; const char *HDR_Autosplit = "Autosplit:"; // Msg file input. char HDR_Autosplit2[SIZE_HEADER] = {}; const char *HDR_Validity = "Validity:"; // Msg file input. char HDR_Validity2[SIZE_HEADER] = {}; const char *HDR_Voicecall = "Voicecall:"; // Msg file input. char HDR_Voicecall2[SIZE_HEADER] = {}; const char *HDR_Replace = "Replace:"; // Msg file input. Incoming message: exists with code if replace char HDR_Replace2[SIZE_HEADER] = {}; // code was defined. const char *HDR_Alphabet = "Alphabet:"; // Msg file input. Incoming message. char HDR_Alphabet2[SIZE_HEADER] = {}; const char *HDR_Include = "Include:"; // Msg file input. char HDR_Include2[SIZE_HEADER] = {}; const char *HDR_Macro = "Macro:"; // Msg file input. char HDR_Macro2[SIZE_HEADER] = {}; const char *HDR_Hex = "Hex:"; // Msg file input. char HDR_Hex2[SIZE_HEADER] = {}; const char *HDR_SMSC = "SMSC:"; // Msg file input: smsc number. char HDR_SMSC2[SIZE_HEADER] = {}; const char *HDR_Priority = "Priority:"; // Msg file input. char HDR_Priority2[SIZE_HEADER] = {}; const char *HDR_System_message = "System_message:"; // Msg file input. char HDR_System_message2[SIZE_HEADER] = {}; const char *HDR_Smsd_debug = "Smsd_debug:"; // For debugging purposes char HDR_Smsd_debug2[SIZE_HEADER] = {}; const char *HDR_UDHDATA = "UDH-DATA:"; // Msg file input. Incoming message. const char *HDR_UDHDUMP = "UDH-DUMP:"; // Msg file input (for backward compatibility). const char *HDR_UDH = "UDH:"; // Msg file input. Incoming binary message: "yes" / "no". const char *HDR_Sent = "Sent:"; // Outgoing timestamp, incoming: senders date & time (from PDU). char HDR_Sent2[SIZE_HEADER] = {}; const char *HDR_Modem = "Modem:"; // Sent message, device name (=modemname). After >= 3.1.4 also incoming message. char HDR_Modem2[SIZE_HEADER] = {}; const char *HDR_Number = "Number:"; // 3.1.4: Sent message, incoming message, SIM card's telephone number. char HDR_Number2[SIZE_HEADER] = {}; const char *HDR_FromTOA = "From_TOA:"; // Incoming message: senders Type Of Address. char HDR_FromTOA2[SIZE_HEADER] = {}; const char *HDR_FromSMSC = "From_SMSC:"; // Incoming message: senders SMSC char HDR_FromSMSC2[SIZE_HEADER] = {}; const char *HDR_Name = "Name:"; // Incoming message: name from the modem response (???). char HDR_Name2[SIZE_HEADER] = {}; const char *HDR_Received = "Received:"; // Incoming message timestamp. char HDR_Received2[SIZE_HEADER] = {}; const char *HDR_Subject = "Subject:"; // Incoming message, modemname. char HDR_Subject2[SIZE_HEADER] = {}; const char *HDR_UDHType = "UDH-Type:"; // Incoming message, type(s) of content of UDH if present. char HDR_UDHType2[SIZE_HEADER] = {}; const char *HDR_Length = "Length:"; // Incoming message, text/data length. With Unicode: number of Unicode char HDR_Length2[SIZE_HEADER] = {}; // characters. With GSM/ISO: nr of chars, may differ if stored as UTF-8. const char *HDR_FailReason = "Fail_reason:"; // Failed outgoing message, error text. char HDR_FailReason2[SIZE_HEADER] = {}; const char *HDR_Failed = "Failed:"; // Failed outgoing message, timestamp. char HDR_Failed2[SIZE_HEADER] = {}; const char *HDR_Identity = "IMSI:"; // Incoming / Sent(or failed), exists with code if IMSI request char HDR_Identity2[SIZE_HEADER] = {}; // supported. const char *HDR_MessageId = "Message_id:"; // Sent (successfully) message. There is fixed "message id" and char HDR_MessageId2[SIZE_HEADER] = {}; // "status" titled inside the body of status report. const char *HDR_OriginalFilename = "Original_filename:"; // Stored when moving file from outgoing directory and char HDR_OriginalFilename2[SIZE_HEADER] = {}; // unique filenames are used in the spooler. const char *HDR_CallType = "Call_type:"; // Incoming message from phonebook. char HDR_CallType2[SIZE_HEADER] = {}; const char *HDR_missed = "missed"; // For incoming call type. char HDR_missed2[SIZE_HEADER] = {}; const char *HDR_missed_text = "CALL MISSED"; // For incoming call, message body. char HDR_missed_text2[SIZE_HEADER] = {}; const char *HDR_Result = "Result:"; // For voice call, result string from a modem char HDR_Result2[SIZE_HEADER] = {}; const char *HDR_Incomplete = "Incomplete:"; // For purged message files. char HDR_Incomplete2[SIZE_HEADER] = {}; char *EXEC_EVENTHANDLER = "eventhandler"; char *EXEC_RR_MODEM = "regular_run (modem)"; char *EXEC_RR_POST_MODEM = "regular_run_post_run (modem)"; char *EXEC_RR_MAINPROCESS = "regular_run (mainprocess)"; char *EXEC_CHECKHANDLER = "checkhandler"; // Prototype needed: void send_admin_message(int *quick, int *errorcounter, char *text); void sendsignal2devices(int signum) { int i; for (i = 0; i < NUMBER_OF_MODEMS; i++) if (device_pids[i] > 0) kill(device_pids[i], signum); } int exec_system(char *command, char *info) { int result; int *i = 0; char *to = NULL; //static int last_status_eventhandler = -1; //static int last_status_rr_modem = -1; //static int last_status_rr_post_modem = -1; //static int last_status_rr_mainprocess = -1; static int last_status = -1; // One status for each process result = my_system(command, info); if (!strcmp(info, EXEC_EVENTHANDLER)) i = &last_status; //_eventhandler; else if (!strcmp(info, EXEC_RR_MODEM)) i = &last_status; //_rr_modem; else if (!strcmp(info, EXEC_RR_POST_MODEM)) i = &last_status; //_rr_post_modem; else if (!strcmp(info, EXEC_RR_MAINPROCESS)) i = &last_status; //_rr_mainprocess; if (i) { if (!result) *i = 0; // running was ok else { char alert[256]; snprintf(alert, sizeof(alert), "problem with %s, result %i", info, result); if (process_id >= 0) { // Modems only. if (DEVICE.admin_to[0]) to = DEVICE.admin_to; else if (admin_to[0]) to = admin_to; } // If last result was ok or unknown, alert is sent. if (*i <= 0) { int timeout = 0; writelogfile(LOG_ERR, 1, "ALERT: %s", alert); if (to) { char msg[256]; int quick = 0; int errorcounter = 0; snprintf(msg, sizeof(msg), "Smsd3: %s, %s", DEVICE.name, alert); send_admin_message(&quick, &errorcounter, msg); } else { if (*adminmessage_device && process_id == -1 && shared_buffer) { time_t start_time; start_time = time(0); // Buffer should be free: while (*shared_buffer) { if (time(0) -start_time > 60) break; sendsignal2devices(SIGCONT); t_sleep(5); } if (*shared_buffer) { timeout = 1; writelogfile(LOG_INFO, 1, "Timeout while trying to deliver alert to %s", adminmessage_device); } else { snprintf(shared_buffer, SIZE_SHARED_BUFFER, "%s Sms3: mainprocess, %s", adminmessage_device, alert); sendsignal2devices(SIGCONT); } } } if (timeout) *i = -1; // retry next time if error remains else *i = 1; // running failed } else { (*i)++; writelogfile(LOG_INFO, 1, "ALERT (continues, %i): %s", *i, alert); } } } return result; } int read_translation(void) { int result = 0; // Number of problems FILE *fp; char name[32]; char value[PATH_MAX]; int getline_result; char *p; int i; if (*language_file) { if (!(fp = fopen(language_file, "r"))) { fprintf(stderr, "%s\n", tb_sprintf("Cannot read language file %s: %s", language_file, strerror(errno))); writelogfile(LOG_CRIT, 1, "%s", tb); result++; } else { while ((getline_result = my_getline(fp, name, sizeof(name), value, sizeof(value))) != 0) { if (getline_result == -1) { fprintf(stderr, "%s\n", tb_sprintf("Syntax Error in language file: %s", value)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } if (line_is_blank(value)) { fprintf(stderr, "%s\n", tb_sprintf("%s has no value in language file.", name)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } if (strlen(value) >= SIZE_HEADER) { fprintf(stderr, "%s\n", tb_sprintf("Too long value for %s in language file: %s", name, value)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } if (*value == '-') { while (value[1] && strchr(" \t", value[1])) strcpyo(value +1, value +2); if (!strcasecmp(name, HDR_From) || !strcasecmp(name, HDR_Received) || !strcasecmp(name, HDR_missed) || !strcasecmp(name, HDR_missed_text)) { fprintf(stderr, "%s\n", tb_sprintf("In language file, translation for %s cannot start with '-' character", name)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } } if (!strcasecmp(name, "incoming")) translate_incoming = yesno(value); else if (!strcasecmp(name, "datetime")) { // Not much can be checked, only if it's completelly wrong... char timestamp[81]; time_t now; time(&now); if (!strchr(value, '%') || strftime(timestamp, sizeof(timestamp), value, localtime(&now)) == 0 || !strcmp(timestamp, value)) { fprintf(stderr, "%s\n", tb_sprintf("In language file, format for datetime is completelly wrong: \"%s\"", value)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } else strcpy(datetime_format, value); } else if (!strcasecmp(name, "yes_word")) strcpy(yes_word, value); else if (!strcasecmp(name, "no_word")) strcpy(no_word, value); else if (!strcasecmp(name, "yes_chars") || !strcasecmp(name, "no_chars")) { // Every possible character (combination) is given between apostrophes. // This is because one UTF-8 character can be represented using more than on byte. // There can be more than one definition delimited with a comma. Uppercase and // lowercase is handled by the definition because of UTF-8 and because of // non-US-ASCII character sets. // Example (very easy one): 'K','k' // First remove commas, spaces and double apostrophes: while ((p = strchr(value, ','))) strcpyo(p, p +1); while ((p = strchr(value, ' '))) strcpyo(p, p +1); while ((p = strstr(value, "''"))) strcpyo(p, p +1); // First apostrophe is not needed: if (value[0] == '\'') strcpyo(value, value +1); // Ensure that last apostrophe is there: if ((i = strlen(value)) > 0) { if (value[i -1] != '\'') if (i < SIZE_HEADER -1) strcat(value, "'"); } else { fprintf(stderr, "%s\n", tb_sprintf("%s has an incomplete value in language file", name)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } // Example (UTF-8): (byte1)(byte2)(byte3)'(byte1)(byte2)' // yesno() is now able to check what was meant with an answer. if (!strcasecmp(name, "yes_chars")) strcpy(yes_chars, value); else strcpy(no_chars, value); } else if (!strcasecmp(name, HDR_To)) strcpy(HDR_To2, value); else if (!strcasecmp(name, HDR_ToTOA)) strcpy(HDR_ToTOA2, value); else if (!strcasecmp(name, HDR_From)) strcpy(HDR_From2, value); else if (!strcasecmp(name, HDR_Flash)) strcpy(HDR_Flash2, value); else if (!strcasecmp(name, HDR_Provider)) strcpy(HDR_Provider2, value); else if (!strcasecmp(name, HDR_Queue)) strcpy(HDR_Queue2, value); else if (!strcasecmp(name, HDR_Binary)) strcpy(HDR_Binary2, value); else if (!strcasecmp(name, HDR_Report)) strcpy(HDR_Report2, value); else if (!strcasecmp(name, HDR_Autosplit)) strcpy(HDR_Autosplit2, value); else if (!strcasecmp(name, HDR_Validity)) strcpy(HDR_Validity2, value); else if (!strcasecmp(name, HDR_Voicecall)) strcpy(HDR_Voicecall2, value); else if (!strcasecmp(name, HDR_Replace)) strcpy(HDR_Replace2, value); else if (!strcasecmp(name, HDR_Alphabet)) strcpy(HDR_Alphabet2, value); else if (!strcasecmp(name, HDR_Include)) strcpy(HDR_Include2, value); else if (!strcasecmp(name, HDR_Macro)) strcpy(HDR_Macro2, value); else if (!strcasecmp(name, HDR_Hex)) strcpy(HDR_Hex2, value); else if (!strcasecmp(name, HDR_SMSC)) strcpy(HDR_SMSC2, value); else if (!strcasecmp(name, HDR_Priority)) strcpy(HDR_Priority2, value); else if (!strcasecmp(name, HDR_System_message)) strcpy(HDR_System_message2, value); else if (!strcasecmp(name, HDR_Sent)) strcpy(HDR_Sent2, value); else if (!strcasecmp(name, HDR_Modem)) strcpy(HDR_Modem2, value); else if (!strcasecmp(name, HDR_Number)) strcpy(HDR_Number2, value); else if (!strcasecmp(name, HDR_FromTOA)) strcpy(HDR_FromTOA2, value); else if (!strcasecmp(name, HDR_FromSMSC)) strcpy(HDR_FromSMSC2, value); else if (!strcasecmp(name, HDR_Name)) strcpy(HDR_Name2, value); else if (!strcasecmp(name, HDR_Received)) strcpy(HDR_Received2, value); else if (!strcasecmp(name, HDR_Subject)) strcpy(HDR_Subject2, value); else if (!strcasecmp(name, HDR_UDHType)) strcpy(HDR_UDHType2, value); else if (!strcasecmp(name, HDR_Length)) strcpy(HDR_Length2, value); else if (!strcasecmp(name, HDR_FailReason)) strcpy(HDR_FailReason2, value); else if (!strcasecmp(name, HDR_Failed)) strcpy(HDR_Failed2, value); else if (!strcasecmp(name, HDR_Identity)) strcpy(HDR_Identity2, value); else if (!strcasecmp(name, HDR_MessageId)) strcpy(HDR_MessageId2, value); else if (!strcasecmp(name, HDR_OriginalFilename)) strcpy(HDR_OriginalFilename2, value); else if (!strcasecmp(name, HDR_CallType)) strcpy(HDR_CallType2, value); else if (!strcasecmp(name, HDR_missed)) strcpy(HDR_missed2, value); else if (!strcasecmp(name, HDR_missed_text)) strcpy(HDR_missed_text2, value); else if (!strcasecmp(name, HDR_Result)) strcpy(HDR_Result2, value); else if (!strcasecmp(name, HDR_Incomplete)) strcpy(HDR_Incomplete2, value); else { fprintf(stderr, "%s\n", tb_sprintf("Unknown variable in language file: %s", name)); writelogfile(LOG_CRIT, 1, "%s", tb); result++; continue; } } fclose(fp); } if (!result) writelogfile(LOG_INFO, 0, "Using language file %s", language_file); } return result; } // Used to select an appropriate header: char *get_header(const char *header, char *header2) { if (header2 && *header2 && strcmp(header2, "-")) { if (*header2 == '-') return header2 +1; return header2; } return (char *)header; } char *get_header_incoming(const char *header, char *header2) { if (!translate_incoming) return (char *)header; return get_header(header, header2); } // Return value: 1/0 // hlen = length of a header which matched. int test_header(int *hlen, char *line, const char *header, char *header2) { // header2: // NULL or "" = no translation // "-" = no translation and header is not printed // "-Relatório:" = input translated, header is not printed // "Relatório:" = input and output translated if (header2 && *header2 && strcmp(header2, "-")) { if (*header2 == '-') { if (!strncmp(line, header2 +1, *hlen = strlen(header2) -1)) return 1; } else if (!strncmp(line, header2, *hlen = strlen(header2))) return 1; } if (!strncmp(line, header, *hlen = strlen(header))) return 1; *hlen = 0; return 0; } int prepare_remove_headers(char *remove_headers, size_t size) { char *p; if (snprintf(remove_headers, size, "\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\nPDU:", HDR_FailReason, HDR_Failed, HDR_Identity, HDR_Modem, HDR_Number, HDR_Sent, HDR_MessageId, HDR_Result) >= (ssize_t)size) return 0; if ((p = get_header(NULL, HDR_FailReason2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Failed2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Identity2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Modem2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Number2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Sent2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_MessageId2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } if ((p = get_header(NULL, HDR_Result2))) { if (strlen(remove_headers) + strlen(p) +1 < size) sprintf(strchr(remove_headers, 0), "\n%s", p); else return 0; } return 1; } /* ======================================================================= Macros can be used with ISO/UTF-8 written messages with cs_convert=yes and with binary messages written with hex=yes. A buffer should be 0-terminated and it cannot include 0x00 characters. ======================================================================= */ int extract_macros(char *buffer, int buffer_size, char *macros) { int result = 0; char *p_buffer; char *p_macro; char *p_value; char *p; int len_buffer; int len_macro; int len_value; if (macros && *macros) { p_macro = macros; while (*p_macro) { if ((p_value = strchr(p_macro, '='))) { p_value++; *(p_value -1) = 0; // for easier use of strstr. len_macro = strlen(p_macro); len_value = strlen(p_value); p_buffer = buffer; while ((p = strstr(p_buffer, p_macro))) { if (len_macro < len_value) { len_buffer = strlen(buffer); if (len_buffer -len_macro +len_value >= buffer_size) { result = 1; break; } memmove(p +len_value, p +len_macro, len_buffer -(p -buffer) -len_macro +1); } else if (len_macro > len_value) strcpyo(p +len_value, p +len_macro); if (len_value > 0) { strncpy(p, p_value, len_value); p_buffer = p +len_value; } } *(p_value -1) = '='; // restore delimiter. } p_macro = strchr(p_macro, 0) +1; } } return result; } /* ======================================================================= */ int apply_filename_preview(char *filename, char *arg_text, int setting_length) { /* £$¥èéùìòÇØøÅå Ææ É ÄÖÑܧ äöñüà ¤ LSYeeuioCOoAa Aa E AONUS aonua e */ char *allowed_chars = "-."; char replace_from[] = { 0xA3, '$', 0xA5, 0xE8, 0xE9, 0xF9, 0xEC, 0xF2, 0xC7, 0xD8, 0xF8, 0xC5, 0xE5, 0xC6, 0xE6, 0xC9, 0xC4, 0xD6, 0xD1, 0xDC, 0xA7, 0xE4, 0xF6, 0xF1, 0xFC, 0xE0, '¤', 0x00 }; char *replace_to = "LSYeeuioCOoAaAaEAONUSaonuae"; char old_filename[PATH_MAX]; int i; char *p; char *text; // Fix in 3.1.7. if (!filename || strlen(filename) >= PATH_MAX -2) return 0; if (!arg_text || setting_length <= 0) return 0; if (!(text = strdup(arg_text))) return 0; strcpy(old_filename, filename); i = setting_length; //filename_preview; if (strlen(filename) +2 +i > PATH_MAX) i = PATH_MAX - strlen(filename) -2; if (strlen(text) > (size_t)i) text[i] = 0; for (i = 0; text[i] != 0; i++) { if (!isalnumc(text[i]) && !strchr(allowed_chars, text[i])) { if ((p = strchr(replace_from, text[i]))) text[i] = replace_to[p -replace_from]; else text[i] = '_'; } } strcat(filename, "-"); strcat(filename, text); free(text); if (rename(old_filename, filename) != 0) { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot rename file %s to %s", old_filename, filename)); alarm_handler0(LOG_ERR, tb); strcpy(filename, old_filename); return 0; } return 1; } /* ======================================================================= Runs checkhandler and returns return code: 0 = message is accepted. 1 = message is rejected. 2 = message is accepted and checkhandler has moved it to correct spooler. ======================================================================= */ int run_checkhandler(char* filename) { char cmdline[PATH_MAX+PATH_MAX+32]; if (checkhandler[0]) { snprintf(cmdline, sizeof(cmdline), "%s %s", checkhandler, filename); return my_system(cmdline, EXEC_CHECKHANDLER); } return 0; } /* ======================================================================= Stops the program if the given file exists ======================================================================= */ /* filename1 is checked. The others arguments are used to compose an error message. */ void stop_if_file_exists(char* infotext1, char* filename1, char* infotext2, char* filename2) { int datei; datei=open(filename1,O_RDONLY); if (datei>=0) { close(datei); writelogfile0(LOG_CRIT, 1, tb_sprintf("Fatal error: %s %s %s %s. Check file and dir permissions.", infotext1, filename1, infotext2, filename2)); alarm_handler0(LOG_CRIT, tb); abnormal_termination(1); } } /* ======================================================================= Remove and add headers in a message file - remove_headers: "\nHeader1:\nHeader2:" (\n and : are delimiters) - add_buffer is an additional header data wich is added after add_headers. This data is not processed with format string. For example to be used with pdu store which can be very large and there is no reason to alloc memory for just passing this data. - add_headers: "Header1: value\nHeader2: value\n" (actual line(s) to write) ======================================================================= */ int change_headers(char *filename, char *remove_headers, char *add_buffer, char *add_headers, ...) { int result = 0; char line[1024]; char header[1024 +1]; char *p; int in_headers = 1; char tmp_filename[PATH_MAX +7]; FILE *fp; FILE *fptmp; size_t n; va_list argp; char new_headers[2048]; va_start(argp, add_headers); vsnprintf(new_headers, sizeof(new_headers), add_headers, argp); va_end(argp); // 3.1.12: Temporary file in checked directory causes troubles with more than one modems: //sprintf(tmp_filename,"%s.XXXXXX", filename); sprintf(tmp_filename,"/tmp/smsd.XXXXXX"); close(mkstemp(tmp_filename)); unlink(tmp_filename); if (!(fptmp = fopen(tmp_filename, "w"))) { writelogfile0(LOG_WARNING, 1, tb_sprintf("Header handling aborted, creating %s failed", tmp_filename)); alarm_handler0(LOG_WARNING, tb); result = 1; } else { if (!(fp = fopen(filename, "r"))) { fclose(fptmp); unlink(tmp_filename); writelogfile0(LOG_WARNING, 1, tb_sprintf("Header handling aborted, reading %s failed", filename)); alarm_handler0(LOG_WARNING, tb); result = 2; } else { strcpy(header, "\n"); while (in_headers && fgets(line, sizeof(line), fp)) { if (remove_headers && *remove_headers) { // Possible old headers are removed: if ((p = strchr(line, ':'))) { strncpy(header +1, line, p -line +1); header[p -line +2] = 0; if (strstr(remove_headers, header)) continue; } } if (line_is_blank(line)) { if (*new_headers) fwrite(new_headers, 1, strlen(new_headers), fptmp); if (add_buffer && *add_buffer) fwrite(add_buffer, 1, strlen(add_buffer), fptmp); in_headers = 0; } fwrite(line, 1, strlen(line), fptmp); } // 3.1beta7: Because of Include feature, all text can be in different file // and therefore a delimiter line is not in this file. if (in_headers) { if (*new_headers) fwrite(new_headers, 1, strlen(new_headers), fptmp); if (add_buffer && *add_buffer) fwrite(add_buffer, 1, strlen(add_buffer), fptmp); } while ((n = fread(line, 1, sizeof(line), fp)) > 0) fwrite(line, 1, n, fptmp); fclose(fptmp); fclose(fp); // 3.1.14: rename does not work across different mount points: //unlink(filename); //rename(tmp_filename, filename); if (rename(tmp_filename, filename) != 0) { if (!(fptmp = fopen(tmp_filename, "r"))) { writelogfile0(LOG_WARNING, 1, tb_sprintf("Header handling aborted, reading %s failed", tmp_filename)); alarm_handler0(LOG_WARNING, tb); result = 2; } else { if (!(fp = fopen(filename, "w"))) { writelogfile0(LOG_WARNING, 1, tb_sprintf("Header handling aborted, creating %s failed", filename)); alarm_handler0(LOG_WARNING, tb); result = 1; } else { while ((n = fread(line, 1, sizeof(line), fptmp)) > 0) fwrite(line, 1, n, fp); fclose(fp); } fclose(fptmp); unlink(tmp_filename); } } } } return result; } /* ======================================================================= Read the header of an SMS file ======================================================================= */ void readSMSheader(char* filename, /* Filename */ int recursion_level, // output variables are: char* to, /* destination number */ char* from, /* sender name or number */ int* alphabet, /* -1=GSM 0=ISO 1=binary 2=UCS2 3=UTF8 4=unknown */ int* with_udh, /* UDH flag */ char* udh_data, /* UDH data in hex dump format. Only used in alphabet<=0 */ char* queue, /* Name of Queue */ int* flash, /* 1 if send as Flash SMS */ char* smsc, /* SMSC Number */ int* report, /* 1 if request status report */ int* split, /* 1 if request splitting */ int* validity, /* requested validity period value */ int* voicecall, /* 1 if request voicecall */ int* hex, /* 1 if binary message is presented as hexadecimal */ int *replace_msg, /* 1...7, 0=none */ char *macros, int *system_msg, /* 1 if sending as a system message. */ int *to_type) /* -1 = default, -2 = error, >= 0 = accepted value */ { FILE* File; char line[SIZE_UDH_DATA +256]; char *ptr; int hlen; if (recursion_level == 0) { to[0] = 0; from[0] = 0; *alphabet = 0; *with_udh = -1; udh_data[0] = 0; queue[0] = 0; *flash = 0; smsc[0] = 0; *report = -1; *split = -1; *validity = -1; *voicecall = 0; *hex = 0; if (macros) *macros = 0; *system_msg = 0; *to_type = -1; // This is global: smsd_debug[0] = 0; } #ifdef DEBUGMSG printf("!! readSMSheader(filename=%s, recursion_level:%i, ...)\n",filename, recursion_level); #endif if ((File = fopen(filename, "r"))) { // read until end of file or until an empty line was found while (fgets(line,sizeof(line),File)) { if (line_is_blank(line)) break; if (test_header(&hlen, line, HDR_To, HDR_To2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_TO) { // correct phone number if it has wrong syntax if (strstr(line,"00")==line) strcpy(to,line+2); else if ((ptr=strchr(line,'+'))) strcpy(to,ptr+1); else strcpy(to,line); // 3.1beta3: // Allow number grouping (like "358 12 345 6789") and *# characters in any position of a phone number. ptr = (*to == 's')? to +1: to; while (*ptr) { // 3.1.1: Allow - if (is_blank(*ptr) || *ptr == '-') strcpyo(ptr, ptr +1); else if (!strchr("*#0123456789", *ptr)) *ptr = 0; else ptr++; } } // 3.1.6: Cannot accept 's' only: if (!strcmp(to, "s")) *to = 0; } else if (test_header(&hlen, line, HDR_ToTOA, HDR_ToTOA2)) { cutspaces(strcpyo(line, line +hlen)); if (!strcasecmp(line, "unknown")) *to_type = 0; else if (!strcasecmp(line, "international")) *to_type = 1; else if (!strcasecmp(line, "national")) *to_type = 2; else *to_type = -2; } else if (test_header(&hlen, line, HDR_From, HDR_From2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_FROM) strcpy(from, line); } else if (test_header(&hlen, line, HDR_SMSC, HDR_SMSC2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_SMSC) { // 3.1beta7: allow grouping: // 3.1.5: fixed infinite loop bug. ptr = line; while (*ptr) { if (is_blank(*ptr)) strcpyo(ptr, ptr +1); else ptr++; } // correct phone number if it has wrong syntax if (strstr(line,"00")==line) strcpy(smsc,line+2); else if (strchr(line,'+')==line) strcpy(smsc,line+1); else strcpy(smsc,line); } } else if (test_header(&hlen, line, HDR_Flash, HDR_Flash2)) *flash = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Provider, HDR_Provider2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_QUEUENAME) strcpy(queue, line); } else if (test_header(&hlen, line, HDR_Queue, HDR_Queue2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_QUEUENAME) strcpy(queue, line); } else if (test_header(&hlen, line, HDR_Binary, HDR_Binary2)) *alphabet = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Report, HDR_Report2)) *report = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Autosplit, HDR_Autosplit2)) *split = atoi(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Validity, HDR_Validity2)) *validity = parse_validity(cutspaces(strcpyo(line, line +hlen)), *validity); else if (test_header(&hlen, line, HDR_Voicecall, HDR_Voicecall2)) *voicecall = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Hex, HDR_Hex2)) *hex = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Replace, HDR_Replace2)) { *replace_msg = atoi(cutspaces(strcpyo(line, line +hlen))); if (*replace_msg < 0 || *replace_msg > 7) *replace_msg = 0; } else if (test_header(&hlen, line, HDR_Alphabet, HDR_Alphabet2)) { cutspaces(strcpyo(line, line +hlen)); if (strcasecmp(line,"GSM")==0) *alphabet=-1; else if (strncasecmp(line,"iso",3)==0) *alphabet=0; else if (strncasecmp(line,"lat",3)==0) *alphabet=0; else if (strncasecmp(line,"ans",3)==0) *alphabet=0; else if (strncasecmp(line,"bin",3)==0) *alphabet=1; else if (strncasecmp(line,"chi",3)==0) *alphabet=2; else if (strncasecmp(line,"ucs",3)==0) *alphabet=2; else if (strncasecmp(line,"uni",3)==0) *alphabet=2; else if (strncasecmp(line,"utf",3)==0) *alphabet=3; else *alphabet=4; } else if (strncmp(line, HDR_UDHDATA, hlen = strlen(HDR_UDHDATA)) == 0) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_UDH_DATA) strcpy(udh_data, line); } else if (strncmp(line, HDR_UDHDUMP, hlen = strlen(HDR_UDHDUMP)) == 0) // same as UDH-DATA for backward compatibility { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_UDH_DATA) strcpy(udh_data, line); } else if (strncmp(line, HDR_UDH, hlen = strlen(HDR_UDH)) == 0) *with_udh = yesno(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_Include, HDR_Include2)) { cutspaces(strcpyo(line, line +hlen)); if (recursion_level < 1) readSMSheader(line, recursion_level +1, to, from, alphabet, with_udh, udh_data, queue, flash, smsc, report, split, validity, voicecall, hex, replace_msg, macros, system_msg, to_type); } else if (test_header(&hlen, line, HDR_Macro, HDR_Macro2)) { char *p, *p1, *p2; cut_ctrl(strcpyo(line, line +hlen)); while (*line == ' ' || *line == '\t') strcpyo(line, line +1); if ((p1 = strchr(line, '=')) && *line != '=' && macros) { // If there is previously defined macro with the same name, it is removed first: p = macros; while (*p) { if (strncmp(p, line, 1 +(int)(p1 -line)) == 0) { p2 = strchr(p, 0) +1; memmove(p, p2, SIZE_MACROS -(p2 -macros)); break; } p = strchr(p, 0) +1; } p = macros; while (*p) p = strchr(p, 0) +1; if ((ssize_t)strlen(line) <= SIZE_MACROS -2 -(p - macros)) { strcpy(p, line); *(p +strlen(line) +1) = 0; } // No space -error is not reported. } } else if (test_header(&hlen, line, HDR_System_message, HDR_System_message2)) { // 3.1.7: // *system_msg = yesno(cutspaces(strcpyo(line, line +hlen))); cutspaces(strcpyo(line, line + hlen)); if (!strcasecmp(line, "ToSIM") || !strcmp(line, "2")) *system_msg = 2; else *system_msg = yesno(line); } else if (test_header(&hlen, line, HDR_Smsd_debug, HDR_Smsd_debug2)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_SMSD_DEBUG) strcpy(smsd_debug, line); // smsd_debug is global. } else // if the header is unknown, then simply ignore { ;; } } // End of header reached fclose(File); #ifdef DEBUGMSG printf("!! to=%s\n",to); printf("!! from=%s\n",from); printf("!! alphabet=%i\n",*alphabet); printf("!! with_udh=%i\n",*with_udh); printf("!! udh_data=%s\n",udh_data); printf("!! queue=%s\n",queue); printf("!! flash=%i\n",*flash); printf("!! smsc=%s\n",smsc); printf("!! report=%i\n",*report); printf("!! split=%i\n",*split); printf("!! validity=%i\n",*validity); if (recursion_level == 0 && macros) { char *p = macros; printf("!! %s\n", (*p)? "macros:" : "no macros"); while (*p) { printf ("%s\n", p); p = strchr(p, 0) +1; } } #endif } else { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot read sms file %s.",filename)); alarm_handler0(LOG_ERR, tb); } } /* ======================================================================= Read the message text or binary data of an SMS file ======================================================================= */ void readSMStext(char* filename, /* Filename */ int recursion_level, int do_convert, /* shall I convert from ISO to GSM? Do not try to convert binary data. */ // output variables are: char* text, /* message text */ int* textlen, /* text length */ char *macros) { FILE *fp; int in_headers = 1; char line[MAXTEXT +1]; // 3.1beta7: We now need a 0-termination for extract_macros. int n; int hlen; if (recursion_level == 0) { // Initialize result with empty string text[0]=0; *textlen=0; } #ifdef DEBUGMSG printf("readSMStext(filename=%s, recursion_level=%i, do_convert=%i, ...)\n",filename, recursion_level, do_convert); #endif if (!(fp = fopen(filename, "r"))) { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot read sms file %s.",filename)); alarm_handler0(LOG_ERR, tb); } else { while (in_headers && fgets(line, sizeof(line), fp)) { if (test_header(&hlen, line, HDR_Include, HDR_Include2)) { strcpyo(line, line +hlen); cutspaces(line); if (recursion_level < 1) readSMStext(line, recursion_level +1, do_convert, text, textlen, macros); } if (line_is_blank(line)) in_headers = 0; } n = fread(line, 1, sizeof(line) -1, fp); fclose(fp); if (n > 0) { // Convert character set or simply copy if (do_convert == 1) { if (macros && *macros) { line[n] = 0; extract_macros(line, sizeof(line), macros); n = strlen(line); } // 3.1.7: if (trim_text) { // 3.1.9: do not trim if it becomes empty: char *saved_line; int saved_n; line[n] = 0; saved_line = strdup(line); saved_n = n; while (n > 0) { if (line[n - 1] && strchr(" \t\n\r", line[n - 1])) { n--; line[n] = 0; } else break; } if (!(*line)) { strcpy(line, saved_line); n = saved_n; } free(saved_line); } *textlen += iso_utf8_2gsm(line, n, text + *textlen, MAXTEXT - *textlen); } else { memmove(text + *textlen, line, n); *textlen += n; } } } #ifdef DEBUGMSG printf("!! textlen=%i\n",*textlen); #endif } void readSMShex(char *filename, int recursion_level, char *text, int *textlen, char *macros, char *errortext) { FILE *fp; char line[MAXTEXT +1]; int in_headers = 1; char *p; int h; int i = 0; char *p_length = NULL; int too_long = 0; int hlen; if (recursion_level == 0) { text[0]=0; *textlen=0; } if ((fp = fopen(filename, "r"))) { while (in_headers && fgets(line, sizeof(line), fp)) { if (test_header(&hlen, line, HDR_Include, HDR_Include2)) { strcpyo(line, line +hlen); cutspaces(line); if (recursion_level < 1) readSMShex(line, recursion_level +1, text, textlen, macros, errortext); } if (line_is_blank(line)) in_headers = 0; } while (fgets(line, sizeof(line), fp) && !too_long) { cut_ctrl(line); while (*line == ' ' || *line == '\t') strcpyo(line, line +1); extract_macros(line, sizeof(line), macros); if (strncmp(line, "INLINESTRING:", 13) == 0) { if (*textlen +strlen(line) +1 -13 +1 >= MAXTEXT) { too_long = 1; break; } // Inline String follows: text[*textlen] = 0x03; (*textlen)++; // Actual text: strcpy(text + *textlen, line +13); *textlen += strlen(line) -13; // Termination: text[*textlen] = 0x00; (*textlen)++; } else if (strncmp(line, "STRING:", 7) == 0) { if (*textlen +strlen(line) -7 >= MAXTEXT) { too_long = 1; break; } strcpy(text + *textlen, line +7); *textlen += strlen(line) -7; } else if (strncmp(line, "LENGTH", 6) == 0) { if (!p_length) { if (*textlen +1 >= MAXTEXT) { too_long = 1; break; } p_length = text + *textlen; (*textlen)++; } else { *p_length = text + *textlen - p_length -1; p_length = NULL; } } else { if ((p = strstr(line, "/"))) *p = 0; if ((p = strstr(line, "'"))) *p = 0; if ((p = strstr(line, "#"))) *p = 0; if ((p = strstr(line, ":"))) *p = 0; while ((p = strchr(line, ' '))) strcpyo(p, p +1); if (*line) { if (strlen(line) % 2 != 0) { writelogfile0(LOG_ERR, 1, tb_sprintf("Hex presentation error in sms file %s: incorrect length of data: \"%s\".", filename, line)); alarm_handler0(LOG_ERR, tb); if (errortext) strcpy(errortext, tb); text[0]=0; *textlen=0; break; } p = line; while (*p) { if ((i = sscanf(p, "%2x", &h)) == 0) break; if (*textlen +1 >= MAXTEXT) { too_long = 1; break; // Main loop is breaked by too_long variable. } text[*textlen] = h; (*textlen)++; p += 2; } if (i < 1) { writelogfile0(LOG_ERR, 1, tb_sprintf("Hex conversion error in sms file %s: \"%s\".", filename, p)); alarm_handler0(LOG_ERR, tb); if (errortext) strcpy(errortext, tb); text[0]=0; *textlen=0; break; } } } } fclose(fp); if (p_length) { writelogfile0(LOG_ERR, 1, tb_sprintf("LENGTH termination error in sms file %s.", filename)); alarm_handler0(LOG_ERR, tb); if (errortext) strcpy(errortext, tb); text[0]=0; *textlen=0; } if (too_long) { writelogfile0(LOG_ERR, 1, tb_sprintf("Data is too long in sms file %s.", filename)); alarm_handler0(LOG_ERR, tb); if (errortext) strcpy(errortext, tb); text[0]=0; *textlen=0; } } else { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot read sms file %s.", filename)); alarm_handler0(LOG_ERR, tb); if (errortext) strcpy(errortext, tb); } // No need to show filename in the message file: if (errortext) if ((p = strstr(errortext, filename))) strcpyo(p -1, p +strlen(filename)); } /* ======================================================================= Mainspooler (sorts SMS into queues) ======================================================================= */ void mainspooler() { char filename[PATH_MAX]; char newfilename[PATH_MAX]; char to[SIZE_TO]; char from[SIZE_FROM]; char smsc[SIZE_SMSC]; char queuename[SIZE_QUEUENAME]; char directory[PATH_MAX]; char cmdline[PATH_MAX+PATH_MAX+32]; int with_udh; char udh_data[SIZE_UDH_DATA]; int queue; int alphabet; int i; int flash; int success; int report; int split; int validity; int voicecall; int hex; int replace_msg; int system_msg; int to_type; time_t now; time_t last_regular_run; time_t last_stats; time_t last_status; time_t last_spooling; char *fail_text = 0; char text[MAXTEXT]; int textlen; *smsd_debug = 0; // 3.1.6: mainprocess will mark starting time to the trouble log and after it "Everything ok now.": flush_smart_logging(); writelogfile(LOG_NOTICE, 1, "Outgoing file checker has started. PID: %i.", (int)getpid()); flush_smart_logging(); // 3.1.12: sprintf(cmdline, "All PID's: %i", (int)getpid()); for (i = 0; i < NUMBER_OF_MODEMS; i++) if (device_pids[i] > 0) sprintf(strchr(cmdline, 0), ",%i", (int)device_pids[i]); writelogfile0(LOG_DEBUG, 0, cmdline); last_regular_run = 0; // time(0); last_stats = time(0); last_status = time(0); last_spooling = 0; workless_delay = 1; break_workless_delay = 0; // 3.1.4: if (delaytime_mainprocess == -1) delaytime_mainprocess = delaytime; flush_smart_logging(); while (terminate == 0) { now = time(0); if (now >= last_stats +1) { last_stats = now; print_status(); checkwritestats(); } // 3.1.5: if (now >= last_status +status_interval) { last_status = now; write_status(); } if (terminate == 1) return; if (*regular_run && regular_run_interval > 0) { now = time(0); if (now >= last_regular_run +regular_run_interval) { last_regular_run = now; writelogfile(LOG_INFO, 0, "Running a regular_run."); i = exec_system(regular_run, EXEC_RR_MAINPROCESS); if (i) writelogfile(LOG_ERR, 1, "Regular_run returned %i.", i); } } if (terminate == 1) return; now = time(0); if ((now >= last_spooling + delaytime_mainprocess) || break_workless_delay) { // In Cygwin serial port cannot be opened if another smsd is started. // This causes another modem process to shut down. Therefore pid checking is not necessary. // 3.1.14: Still do the test in Cygwin too, because two mainspoolers may cause "conflicts": //if (!os_cygwin) //{ if ((i = check_pid(pidfile))) { writelogfile(LOG_ERR, 1, tb_sprintf("FATAL ERROR: Looks like another smsd (%i) is running. I (%i) quit now.", i, (int)getpid())); alarm_handler(LOG_ERR, tb); *pidfile = 0; abnormal_termination(1); } //} last_spooling = now; workless_delay = 0; break_workless_delay = 0; success=0; *tb = 0; if (getfile(trust_outgoing, d_spool, filename, 0)) { // Checkhandler is run first, it can make changes to the message file: // Does the checkhandler accept the message? i = run_checkhandler(filename); if (i == 1) { writelogfile(LOG_NOTICE, 0, tb_sprintf("SMS file %s rejected by checkhandler", filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Rejected by checkhandler"; } else // Did checkhandler move the file by itself? if (i == 2) { writelogfile(LOG_NOTICE, 0, "SMS file %s spooled by checkhandler",filename); success = 1; } else { // If checkhandler failed, message is processed as usual. readSMSheader(filename, 0, to,from,&alphabet,&with_udh,udh_data,queuename,&flash,smsc,&report,&split, &validity, &voicecall, &hex, &replace_msg, NULL, &system_msg, &to_type); readSMStext(filename, 0, 0,text, &textlen, NULL); // Is the destination set? if (to[0]==0) { writelogfile(LOG_NOTICE, 0, tb_sprintf("No destination in file %s", filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "No destination"; } // is To: in the blacklist? else if (inblacklist(to)) { writelogfile(LOG_NOTICE, 0, tb_sprintf("Destination %s in file %s is blacklisted", to, filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Destination is blacklisted"; } // Whitelist can also set the queue. // is To: in the whitelist? else if (!inwhitelist_q(to, queuename)) { writelogfile(LOG_NOTICE, 0, tb_sprintf("Destination %s in file %s is not whitelisted", to, filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Destination is not whitelisted"; } // Is the alphabet setting valid? #ifdef USE_ICONV else if (alphabet>3) #else else if (alphabet>2) #endif { writelogfile(LOG_NOTICE, 0, tb_sprintf("Invalid alphabet in file %s", filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Invalid alphabet"; } // is there is a queue name, then set the queue by this name else if ((queuename[0]) && ((queue=getqueue(queuename,directory))==-1)) { writelogfile(LOG_NOTICE, 0, tb_sprintf("Wrong provider queue %s in file %s", queuename, filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Wrong provider queue"; } // if there is no queue name, set it by the destination phone number else if ((queuename[0]==0) && ((queue=getqueue(to,directory))==-1)) { writelogfile(LOG_NOTICE, 0, tb_sprintf("Destination number %s in file %s does not match any provider", to, filename)); alarm_handler(LOG_NOTICE, tb); fail_text = "Destination number does not match any provider"; } // If a file has no text or data, it can be rejected here. else if (textlen <= 0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has no text or data",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "No text or data"; } // Is numbering plan set correctly else if (to_type == -2) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid number type in file %s", filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid number type"; } else { // Everything is ok, move the file into the queue // 3.1beta7, 3.0.9: Return value handled: if (movefilewithdestlock_new(filename, directory, keep_filename, store_original_filename, "", newfilename) == 1) { // 1 = lockfile cannot be created. It exists. writelogfile0(LOG_CRIT, 1, tb_sprintf("Conflict with .LOCK file in the spooler: %s %s", filename, directory)); alarm_handler0(LOG_CRIT, tb); // TODO: If original filename was used, could switch to unique mode automatically. if (movefilewithdestlock_new(filename, directory, keep_filename, store_original_filename, "", newfilename) == 0) writelogfile0(LOG_CRIT, 0, tb_sprintf("Retry solved the spooler conflict: %s %s", filename, directory)); } stop_if_file_exists("Cannot move", filename, "to", directory); writelogfile(LOG_NOTICE, 0, "Moved file %s to %s", filename, (keep_filename)? directory : newfilename); success = 1; sendsignal2devices(SIGCONT); } } if (!success) { char remove_headers[4096]; char add_headers[4096]; // 3.1.1: Empty file is not moved to the failed folder. struct stat statbuf; int file_is_empty = 0; char timestamp[81]; if (stat(filename, &statbuf) == 0) if (statbuf.st_size == 0) file_is_empty = 1; prepare_remove_headers(remove_headers, sizeof(remove_headers)); *add_headers = 0; if (fail_text && *fail_text && *HDR_FailReason2 != '-') sprintf(add_headers, "%s %s\n", get_header(HDR_FailReason, HDR_FailReason2), fail_text); // 3.1.5: Timestamp for failed message: make_datetime_string(timestamp, sizeof(timestamp), 0, 0, 0); if (*HDR_Failed2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Failed, HDR_Failed2), timestamp); change_headers(filename, remove_headers, 0, "%s", add_headers); rejected_counter++; if (d_failed[0] && !file_is_empty) { movefilewithdestlock_new(filename,d_failed, keep_filename, store_original_filename, process_title, newfilename); stop_if_file_exists("Cannot move",filename,"to",d_failed); } if (eventhandler[0]) { snprintf(cmdline, sizeof(cmdline), "%s %s %s", eventhandler, "FAILED", (d_failed[0] == 0 || file_is_empty)? filename : newfilename); exec_system(cmdline, EXEC_EVENTHANDLER); } if (d_failed[0] == 0 || file_is_empty) { unlink(filename); stop_if_file_exists("Cannot delete",filename,"",""); writelogfile(LOG_INFO, 0, "Deleted file %s",filename); } } last_spooling = 0; continue; } workless_delay = 1; } if (terminate == 1) return; flush_smart_logging(); // 3.1.7: //sleep(1); if (delaytime_mainprocess != 0 && !terminate && !break_workless_delay) t_sleep(1); } } /* ======================================================================= Delete message on the SIM card ======================================================================= */ void deletesms(int sim) /* deletes the selected sms from the sim card */ { char command[100]; char answer[500]; if (sim == PDUFROMFILE) return; if (keep_messages || DEVICE.keep_messages) writelogfile(LOG_INFO, 0, "Keeping message %i", sim); else { writelogfile(LOG_INFO, 0, "Deleting message %i", sim); sprintf(command,"AT+CMGD=%i\r", sim); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); } } void deletesms_list(char *memory_list) { char command[100]; char answer[500]; int sim; char *p; while (*memory_list) { sim = atoi(memory_list); if ((p = strchr(memory_list, ','))) strcpyo(memory_list, p +1); else *memory_list = 0; if (sim != PDUFROMFILE) { if (keep_messages || DEVICE.keep_messages) writelogfile(LOG_INFO, 0, "Keeping message %i", sim); else { writelogfile(LOG_INFO, 0, "Deleting message %i", sim); sprintf(command,"AT+CMGD=%i\r", sim); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); } } } } /* ======================================================================= Check size of SIM card ======================================================================= */ // Return value: 1 = OK, 0 = check failed. int check_memory(int *used_memory, int *max_memory, char *memory_list, size_t memory_list_size, char *delete_list, int delete_list_size) { // 3.1.5: GMGL list needs much more space: using global buffer. //char answer[500]; char *answer = check_memory_buffer; // 3.1.12: Use allocated memory: //int size_answer = SIZE_CHECK_MEMORY_BUFFER; int size_answer = (int)check_memory_buffer_size; char* start; char* end; char tmp[100]; char *p; char *pos; int i; if (!answer) return 0; (void) delete_list_size; // 3.1.7: remove warning. // Set default values in case that the modem does not support the +CPMS command *used_memory=1; *max_memory=10; *answer = 0; // Ability to read incoming PDU from file: if (DEVICE.pdu_from_file[0]) { FILE *fp; char filename[PATH_MAX]; strcpy(filename, DEVICE.pdu_from_file); if (getpdufile(filename)) { if ((fp = fopen(filename, "r"))) { fclose(fp); writelogfile(LOG_INFO, 0, "Found an incoming message file %s", filename); return 1; } } } if (DEVICE.modem_disabled == 1) { *used_memory = 0; return 1; } writelogfile(LOG_INFO, 0, "Checking memory size"); // 3.1.5: if (DEVICE.check_memory_method == CM_CMGD) { *used_memory = 0; *memory_list = 0; put_command("AT+CMGD=?\r", answer, size_answer, 1, "(\\+CMGD:.*OK)|(ERROR)"); // +CMGD: (1,22,23,28),(0-4) \r OK // +CMGD: (),(0-4) \r OK if (strstr(answer, "()")) return 1; if ((p = strstr(answer, " ("))) { strcpyo(answer, p +2); if ((p = strstr(answer, "),"))) { *p = 0; if (strlen(answer) < memory_list_size) { *used_memory = 1; strcpy(memory_list, answer); p = memory_list; while ((*p) && (p = strstr(p +1, ","))) (*used_memory)++; } } else writelogfile(LOG_INFO, 1, "Incomplete answer for AT+CMGD=?, feature not supported?"); } writelogfile(LOG_INFO, 0, "Used memory is %i%s%s", *used_memory, (*memory_list)? ", list: " : "", memory_list); return 1; } else if (value_in(DEVICE.check_memory_method, 5, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) { // 3.1.5: Check CMGL result, it can be incorrect or broken if bad baudrate is used: char *errorstr = 0; char *term; char *p2; int mnumber; int mlength; int pdu1; int save_log_single_lines = log_single_lines; char buffer[256 *LENGTH_PDU_DETAIL_REC +1]; *used_memory = 0; *memory_list = 0; *buffer = 0; sprintf(tmp, "AT+CMGL=%s\r", DEVICE.cmgl_value); // 3.1.5: Empty list gives OK answer without +CMGL prefix: log_single_lines = 0; // 3.1.12: With large number of messages and slow modem, much longer timeout than "1" is required. put_command(tmp, answer, size_answer, 10, EXPECT_OK_ERROR); log_single_lines = save_log_single_lines; pos = answer; while ((p = strstr(pos, "+CMGL:"))) { mnumber = 0; // initial value for error message if (!(term = strchr(p, '\r'))) { errorstr = "Line end not found (fields)"; break; } // 3.1.6: Message number can be zero: //if ((mnumber = atoi(p + 6)) <= 0) mnumber = (int) strtol(p +6, NULL, 0); if (errno == EINVAL || mnumber < 0) { errorstr = "Invalid message number"; break; } if (value_in(DEVICE.check_memory_method, 3, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) { p2 = term; while (*p2 != ',') { if (p2 <= p) { errorstr = "Message length field not found"; break; } p2--; } if (errorstr) break; if ((mlength = atoi(p2 +1)) <= 0) { errorstr = "Invalid length information"; break; } p = term +1; if (*p == '\n') p++; if ((pdu1 = octet2bin_check(p)) <= 0) { errorstr = "Invalid first byte of PDU"; break; } if (!(term = strchr(p, '\r'))) { errorstr = "Line end not found (PDU)"; break; } // Some devices give PDU total length, some give length of TPDU: if (pdu1 *2 +2 +mlength *2 != (int)(term -p) && mlength *2 != (int)(term -p)) { errorstr = "PDU does not match with length information"; break; } i = get_pdu_details(buffer, sizeof(buffer), p, mnumber); if (i > 1) { errorstr = "Fatal error"; break; } } // By default this is the result: sprintf(tmp, "%s%i", (*memory_list)? "," : "", mnumber); if (strlen(memory_list) +strlen(tmp) < memory_list_size) { (*used_memory)++; strcat(memory_list, tmp); } else { errorstr = "Too many messages"; break; } pos = term +1; } if (errorstr) { writelogfile0(LOG_ERR, 1, tb_sprintf("CMGL handling error: message %i, %s", mnumber, errorstr)); alarm_handler0(LOG_ERR, tb); return 0; } if (*buffer) { sort_pdu_details(buffer); // Show even if there is only one message, timestamp may be interesting. //if (strlen(buffer) > LENGTH_PDU_DETAIL_REC) if (strstr(smsd_version, "beta")) writelogfile(LOG_DEBUG, 0, "Sorted message order:\n%s", buffer); } // If there is at least one message and SIMCOM SIM600 style handling is required. // ( one because delete_list is always needed ). if (*used_memory > 0 && value_in(DEVICE.check_memory_method, 1, CM_CMGL_SIMCOM)) { /* 001 A 358401234567____________________111 001/001 r 00-00-00 00-00-00n 3.1.7: 001 A 00-00-00 00-00-00 358401234567____________________111 001/001 rn */ int pos_sender = 24; //6; int pos_messageid = 56; //38; int length_messagedetail = 11; int pos_partnr = 60; //42; int pos_partcount = 64; //46; int pos_timestamp = 6; //52; int offset_messageid = 32; // from sender_id // NOTE: // Exact size for buffers: char sender[32 +1]; char sender_id[35 +1]; int p_count; int read_all_parts; *used_memory = 0; *memory_list = 0; *delete_list = 0; pos = buffer; while (*pos) { if (!strncmp(&pos[pos_messageid], "999 001/001", length_messagedetail)) { // Single part message. Always taken and fits to the list. mnumber = atoi(pos); sprintf(strchr(memory_list, 0), "%s%i", (*memory_list)? "," : "", mnumber); (*used_memory)++; sprintf(strchr(delete_list, 0), "%s%i", (*delete_list)? "," : "", mnumber); pos += LENGTH_PDU_DETAIL_REC; continue; } // Multipart message, first part which is seen. strncpy(sender, &pos[pos_sender], sizeof(sender) -1); sender[sizeof(sender) -1] = 0; while (sender[strlen(sender) -1] == ' ') sender[strlen(sender) -1] = 0; strncpy(sender_id, &pos[pos_sender], sizeof(sender_id) -1); sender_id[sizeof(sender_id) -1] = 0; p_count = atoi(&pos[pos_partcount]); p = pos; for (i = 1; *p && i <= p_count; i++) { if (strncmp(&p[pos_sender], sender_id, sizeof(sender_id) -1) || atoi(&p[pos_partnr]) != i) break; p += LENGTH_PDU_DETAIL_REC; } read_all_parts = 1; if (i <= p_count) { // Some part(s) missing. // With SIM600: only the first _available_ part can be deleted and all // other parts of message are deleted by the modem. int ms_purge; int remaining = -1; read_all_parts = 0; if ((ms_purge = DEVICE.ms_purge_hours *60 +DEVICE.ms_purge_minutes) > 0) { time_t rawtime; struct tm *timeinfo; time_t now; time_t msgtime; time(&rawtime); timeinfo = localtime(&rawtime); now = mktime(timeinfo); p = pos +pos_timestamp; timeinfo->tm_year = atoi(p) +100; timeinfo->tm_mon = atoi(p +3) -1; timeinfo->tm_mday = atoi(p +6); timeinfo->tm_hour = atoi(p +9); timeinfo->tm_min = atoi(p +12); timeinfo->tm_sec = atoi(p +15); msgtime = mktime(timeinfo); if (ms_purge *60 > now - msgtime) { remaining = ms_purge - (now - msgtime) /60; ms_purge = 0; } } if (ms_purge > 0) { if (DEVICE.ms_purge_read) { read_all_parts = 1; writelogfile0(LOG_NOTICE, 0, tb_sprintf("Reading message %s from %s partially, all parts not found and timeout expired", sender_id +offset_messageid, sender)); } else { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Deleting message %s from %s, all parts not found and timeout expired", sender_id +offset_messageid, sender)); deletesms(atoi(pos)); } } else { writelogfile0(LOG_INFO, 0, tb_sprintf("Skipping message %s from %s, all parts not found", sender_id +offset_messageid, sender)); if (remaining > 0) writelogfile0(LOG_DEBUG, 0, tb_sprintf("Message %s from %s will be %s after %i minutes unless remaining parts are received", sender_id +offset_messageid, sender, (DEVICE.ms_purge_read)? "read partially" : "deleted", remaining)); } } if (read_all_parts) sprintf(strchr(delete_list, 0), "%s%i", (*delete_list)? "," : "", atoi(pos)); while (*pos) { if (strncmp(&pos[pos_sender], sender_id, sizeof(sender_id) -1)) break; if (read_all_parts) { sprintf(strchr(memory_list, 0), "%s%i", (*memory_list)? "," : "", atoi(pos)); (*used_memory)++; } pos += LENGTH_PDU_DETAIL_REC; } } } else if (*buffer) { // Re-create memory_list because messages are sorted. *used_memory = 0; *memory_list = 0; pos = buffer; while (*pos) { sprintf(strchr(memory_list, 0), "%s%i", (*memory_list)? "," : "", atoi(pos)); (*used_memory)++; pos += LENGTH_PDU_DETAIL_REC; } } writelogfile(LOG_INFO, 0, "Used memory is %i%s%s", *used_memory, (*memory_list)? ", list: " : "", memory_list); if (*delete_list && value_in(DEVICE.check_memory_method, 1, CM_CMGL_SIMCOM)) writelogfile(LOG_DEBUG, 0, "Will later delete messages: %s", delete_list); return 1; } else { put_command("AT+CPMS?\r", answer, size_answer, 1, "(\\+CPMS:.*OK)|(ERROR)"); if ((start=strstr(answer,"+CPMS:"))) { // 3.1.5: Check if reading of messages is not supported: if (strstr(answer, "+CPMS: ,,,,,,,,")) writelogfile(LOG_INFO, 1, "Reading of messages is not supported?"); else { end=strchr(start,'\r'); if (end) { *end=0; getfield(start,2,tmp, sizeof(tmp)); if (tmp[0]) *used_memory=atoi(tmp); getfield(start,3,tmp, sizeof(tmp)); if (tmp[0]) *max_memory=atoi(tmp); writelogfile(LOG_INFO, 0, "Used memory is %i of %i",*used_memory,*max_memory); return 1; } } } } // 3.1.5: If CPMS did not work but it should work: if (DEVICE.check_memory_method == CM_CPMS) { writelogfile(LOG_INFO, 1, "Command failed."); return 0; } writelogfile(LOG_INFO, 0, "Command failed, using defaults."); return 1; } /* ======================================================================= Read and delete phonecall (phonebook) entries Return value: -2 = fatal error while handling an answer -1 = modem does not support necessary commands 0 = no entries > 0 = number of entries processed ======================================================================= */ int savephonecall(char *entry_number, int entry_type, char *entry_text) { int result = 0; char filename[PATH_MAX]; FILE *fp; char timestamp[81]; char cmdline[PATH_MAX+PATH_MAX+32]; char e_type[SIZE_PB_ENTRY]; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, date_filename_format); if (date_filename == 1) sprintf(filename, "%s/%s.%s.XXXXXX", (*d_phonecalls)? d_phonecalls : d_incoming, timestamp, DEVICE.name); else if (date_filename == 2) sprintf(filename, "%s/%s.%s.XXXXXX", (*d_phonecalls)? d_phonecalls : d_incoming, DEVICE.name, timestamp); else sprintf(filename, "%s/%s.XXXXXX", (*d_phonecalls)? d_phonecalls : d_incoming, DEVICE.name); close(mkstemp(filename)); unlink(filename); if (!(fp = fopen(filename, "w"))) { writelogfile0(LOG_ERR, 1, tb_sprintf("Could not create file %s for phonecall from %s: %s", filename, entry_number, strerror(errno))); alarm_handler0(LOG_ERR, tb); result = 1; } else { char *message_body; explain_toa(e_type, 0, entry_type); fprintf(fp, "%s %s\n", get_header_incoming(HDR_From, HDR_From2), entry_number); if (*HDR_FromTOA2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_FromTOA, HDR_FromTOA2), e_type); if (entry_text[0] && *HDR_Name2 != '-') fprintf(fp,"%s %s\n", get_header_incoming(HDR_Name, HDR_Name2), entry_text); if (*HDR_Subject2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_Subject, HDR_Subject2), DEVICE.name); if (*HDR_Modem2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_Modem, HDR_Modem2), DEVICE.name); if (DEVICE.number[0]) if (*HDR_Number2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_Number, HDR_Number2), DEVICE.number); if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_Identity, HDR_Identity2), DEVICE.identity); if (*HDR_CallType2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_CallType, HDR_CallType2), get_header_incoming(HDR_missed, HDR_missed2)); make_datetime_string(timestamp, sizeof(timestamp), 0, 0, 0); fprintf(fp, "%s %s\n", get_header_incoming(HDR_Received, HDR_Received2), timestamp); message_body = get_header_incoming(HDR_missed_text, HDR_missed_text2); fprintf(fp, "\n%s\n", message_body); fclose(fp); apply_filename_preview(filename, message_body, filename_preview); writelogfile(LOG_INFO, 0, "Wrote an incoming message file: %s", filename); if (eventhandler[0] || DEVICE.eventhandler[0]) { snprintf(cmdline, sizeof(cmdline), "%s %s %s", (DEVICE.eventhandler[0])? DEVICE.eventhandler : eventhandler, "CALL", filename); exec_system(cmdline, EXEC_EVENTHANDLER); } } return result; } int readphonecalls() { int result = 0; char command[1024]; char answer[2048]; static int errors = 0; #define PB_MAX_ERRORS DEVICE.phonecalls_error_max static int index_max = 0; #define PB_INDEX_DEFAULT 0 char *p, *p2, *e_start, *e_end, *e_line_end; int len; int count, ok; char entry_number[SIZE_PB_ENTRY]; char entry_type[SIZE_PB_ENTRY]; char entry_text[SIZE_PB_ENTRY]; // THIS FUNCTION CAN RETURN DIRECTLY (if smsd is terminating). if (errors >= PB_MAX_ERRORS) result = -1; else { writelogfile(LOG_INFO, 0, "Reading phonecall entries"); sprintf(command,"AT+CPBS=\"%s\"\r", "MC"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) { if (++errors >= PB_MAX_ERRORS) { writelogfile(LOG_INFO, 1, "Ignoring phonecalls, too many errors"); result = -1; } } else { if (index_max == 0) { if (terminate == 1) return 0; writelogfile(LOG_INFO, 0, "Checking phonecall limits (once)"); sprintf(command,"AT+CPBR=?\r"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) { if (++errors >= PB_MAX_ERRORS) { writelogfile(LOG_INFO, 1, "Ignoring phonecalls, too many errors"); result = -1; } } else { if ((p = strchr(answer, '-'))) { p++; if ((p2 = strchr(p, ')'))) *p2 = 0; index_max = atoi(p); writelogfile(LOG_INFO, 0, "Phonecall limit is %i", index_max); } else index_max = PB_INDEX_DEFAULT; } } if (index_max <= 0) { errors = PB_MAX_ERRORS; writelogfile(LOG_INFO, 1, "Ignoring phonecalls, cannot resolve maximum index value"); result = -1; } else { if (terminate == 1) return 0; sprintf(command,"AT+CPBR=1,%i\r", index_max); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) { if (++errors >= PB_MAX_ERRORS) { writelogfile(LOG_INFO, 1, "Ignoring phonecalls, too many errors"); result = -1; } } else { if (!strstr(answer, "+CPBR:")) writelogfile(LOG_INFO, 0, "No phonecall entries"); if (terminate == 1) return 0; // After this point terminate is not checked, because if entries are // processed, they should also be deleted from the phone. count = 0; p2 = answer; while ((p = strstr(p2, "+CPBR:"))) { ok = 0; *entry_number = 0; *entry_type = 0; *entry_text = 0; e_line_end = p; while (*e_line_end != '\r' && *e_line_end != '\n') { if (*e_line_end == 0) { writelogfile(LOG_INFO, 1, "Fatal error while handling phonecall data"); result = -2; break; } e_line_end++; } if (result != 0) break; e_start = strchr(p, '"'); if (e_start && e_start < e_line_end) { e_start++; e_end = strchr(e_start, '"'); if (e_end && e_end < e_line_end) { if ((len = e_end -e_start) < SIZE_PB_ENTRY) { sprintf(entry_number, "%.*s", len, e_start); cutspaces(entry_number); if (*entry_number == '+') strcpyo(entry_number, entry_number +1); } if (strlen(e_end) < 2) e_end = 0; else { e_start = e_end +2; e_end = strchr(e_start, ','); } if (e_end && e_end < e_line_end) { if ((len = e_end -e_start) < SIZE_PB_ENTRY) { sprintf(entry_type, "%.*s", len, e_start); cutspaces(entry_type); if (strlen(e_end) < 2) e_end = 0; else { e_start = e_end +2; e_end = strchr(e_start, '"'); } if (e_end && e_end < e_line_end) { if ((len = e_end -e_start) < SIZE_PB_ENTRY) { sprintf(entry_text, "%.*s", len, e_start); cutspaces(entry_text); writelogfile(LOG_INFO, 0, "Got phonecall entry from %s", entry_number); ok = 1; savephonecall(entry_number, atoi(entry_type), entry_text); } } } } } } if (!ok) { writelogfile(LOG_INFO, 1, "Syntax error while handling phonecall data"); result = -2; break; } else count++; p2 = p +6; } if (result == 0 && count > 0 && !keep_messages && !DEVICE.keep_messages) { result = count; writelogfile(LOG_INFO, 0, "Removing processed phonecall entries", count); // 3.1.7: if (DEVICE.phonecalls_purge[0]) { int i; i = yesno_check(DEVICE.phonecalls_purge); if (i != 0) { p = DEVICE.phonecalls_purge; if (i == 1) p = "AT^SPBD=\"MC\""; sprintf(command, "%s\r", p); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); count = 0; if (strstr(answer, "ERROR")) { if (++errors >= PB_MAX_ERRORS) { writelogfile(LOG_WARNING, 1, "Ignoring phonecalls, too many errors"); result = -1; } } } } while (count && result != -1) { sprintf(command, "AT+CPBW=%i\r", count); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); count--; if (strstr(answer, "ERROR")) { if (++errors >= PB_MAX_ERRORS) { writelogfile(LOG_WARNING, 1, "Ignoring phonecalls, too many errors"); result = -1; } } } if (result != -1) writelogfile(LOG_INFO, 0, "%i phonecall entries processed", result); } } } } } return result; } /* ======================================================================= Read a memory space from SIM card ======================================================================= */ int readsim(int sim, char* line1, char* line2) /* reads a SMS from the given SIM-memory */ /* returns number of SIM memory if successful, otherwise -1 */ /* 3.1.5: In case of timeout return value is -2. */ /* line1 contains the first line of the modem answer */ /* line2 contains the pdu string */ { char command[500]; char answer[1024]; char* begin1; char* begin2; char* end1; char* end2; line2[0]=0; line1[0]=0; #ifdef DEBUGMSG printf("!! readsim(sim=%i, ...)\n",sim); #endif // Ability to read incoming PDU from file: if (sim == 1 && DEVICE.pdu_from_file[0]) { FILE *fp; char *p; char filename[PATH_MAX]; // 3.1beta7, 3.0.9: If a definition is a directory, first file found from there is taken. // Note: for safety reasons this directory definition is accepted only if it ends with slash and no dot // is used in any position of definition. A single file definition is accepted if a definition does // not end with slash and a directory using the same name does not exist. strcpy(filename, DEVICE.pdu_from_file); if (getpdufile(filename)) { if ((fp = fopen(filename, "r"))) { int result = PDUFROMFILE; char st[1024]; #ifdef DEBUGMSG printf("!! reading message from %s\n", filename); #endif writelogfile(LOG_INFO, 0, "Reading an incoming message from file %s", filename); // 3.1beta7, 3.0.9: // First check if there is a line starting with "PDU: " or "PDU:_". If found, it's taken. while (fgets(st, sizeof(st), fp)) { cutspaces(st); cut_ctrl(st); if (!strncmp(st, "PDU: ", 5) || !strncmp(st, "PDU:_", 5)) { strcpy(line2, st +5); break; } } if (*line2 == 0) { fseek(fp, 0, SEEK_SET); while (fgets(st, sizeof(st), fp)) { cutspaces(st); cut_ctrl(st); if (*st && *st != '#') { if (*line1 == 0) strcpy(line1, st); else if (*line2 == 0) strcpy(line2, st); else break; } } if (*line2 == 0) { // line1 is not necessary. If there is only one line, it should be the PDU string. strcpy(line2, line1); line1[0] = 0; } if ((p = strrchr(line2, ' '))) strcpyo(line2, p +1); if (*line2 == 0) { result = -1; writelogfile(LOG_CRIT, 0, "Syntax error in the incoming message file."); } } fclose(fp); unlink(filename); #ifdef DEBUGMSG printf("!! read result:%i\n", result); #endif return result; } } } if (DEVICE.modem_disabled == 1) { writelogfile(LOG_CRIT, 0, "Cannot try to get stored message %i, MODEM IS DISABLED", sim); return 0; } writelogfile(LOG_INFO, 0, "Trying to get stored message %i",sim); sprintf(command,"AT+CMGR=%i\r",sim); // 3.1beta3: Some modems answer OK in case of empty memory space (added "|(OK)") if (put_command(command, answer, sizeof(answer), 1, "(\\+CMGR:.*OK)|(ERROR)|(OK)") == -2) return -2; // 3.1.7: while ((begin1 = strchr(answer, '\n'))) *begin1 = '\r'; while ((begin1 = strstr(answer, "\r\r"))) strcpyo(begin1, begin1 + 1); // ------ if (strstr(answer,",,0\r")) // No SMS, because Modem answered with +CMGR: 0,,0 return -1; if (strstr(answer,"ERROR")) // No SMS, because Modem answered with ERROR return -1; begin1=strstr(answer,"+CMGR:"); if (begin1==0) return -1; end1=strstr(begin1,"\r"); if (end1==0) return -1; begin2=end1+1; end2=strstr(begin2+1,"\r"); if (end2==0) return -1; strncpy(line1,begin1,end1-begin1); line1[end1-begin1]=0; strncpy(line2,begin2,end2-begin2); line2[end2-begin2]=0; cutspaces(line1); cut_ctrl(line1); cutspaces(line2); cut_ctrl(line2); if (strlen(line2)==0) return -1; #ifdef DEBUGMSG printf("!! line1=%s, line2=%s\n",line1,line2); #endif return sim; } /* ======================================================================= Write a received message into a file ======================================================================= */ // returns 1 if this was a status report int received2file(char *line1, char *line2, char *filename, int *stored_concatenated, int incomplete) { int userdatalength; char ascii[MAXTEXT]= {}; char sendr[100]= {}; int with_udh=0; char udh_data[SIZE_UDH_DATA] = {}; char udh_type[SIZE_UDH_TYPE] = {}; char smsc[31]= {}; char name[64]= {}; char date[9]= {}; char Time[9]= {}; char warning_headers[SIZE_WARNING_HEADERS] = {}; //char status[40]={}; not used int alphabet=0; int is_statusreport=0; FILE* fd; int do_decode_unicode_text = 0; int do_internal_combine = 0; int do_internal_combine_binary; int is_unsupported_pdu = 0; int pdu_store_length = 0; int result = 1; char from_toa[51] = {}; int report; int replace; int flash; int p_count = 1; int p_number; char sent[81] = ""; char received[81] = ""; if (DEVICE.decode_unicode_text == 1 || (DEVICE.decode_unicode_text == -1 && decode_unicode_text == 1)) do_decode_unicode_text = 1; if (!incomplete) if (DEVICE.internal_combine == 1 || (DEVICE.internal_combine == -1 && internal_combine == 1)) do_internal_combine = 1; do_internal_combine_binary = do_internal_combine; if (do_internal_combine_binary) if (DEVICE.internal_combine_binary == 0 || (DEVICE.internal_combine_binary == -1 && internal_combine_binary == 0)) do_internal_combine_binary = 0; #ifdef DEBUGMSG printf("!! received2file: line1=%s, line2=%s, decode_unicode_text=%i, internal_combine=%i, internal_combine_binary=%i\n", line1, line2, do_decode_unicode_text, do_internal_combine, do_internal_combine_binary); #endif //getfield(line1,1,status, sizeof(status)); not used getfield(line1,2,name, sizeof(name)); // Check if field 2 was a number instead of a name if (atoi(name)>0) name[0]=0; //Delete the name because it is missing userdatalength=splitpdu(line2, DEVICE.mode, &alphabet, sendr, date, Time, ascii, smsc, &with_udh, udh_data, udh_type, &is_statusreport, &is_unsupported_pdu, from_toa, &report, &replace, warning_headers, &flash, do_internal_combine_binary); if (alphabet==-1 && DEVICE.cs_convert==1) userdatalength=gsm2iso(ascii,userdatalength,ascii,sizeof(ascii)); else if (alphabet == 2 && do_decode_unicode_text == 1) { if (with_udh) { char *tmp; int m_id, p_count, p_number; if ((tmp = strdup(udh_data))) { if (get_remove_concatenation(tmp, &m_id, &p_count, &p_number) > 0) { if (p_count == 1 && p_number == 1) { strcpy(udh_data, tmp); if (!(*udh_data)) { with_udh = 0; *udh_type = 0; } else { if (explain_udh(udh_type, udh_data) < 0) if (strlen(udh_type) +7 < SIZE_UDH_TYPE) sprintf(strchr(udh_type, 0), "%sERROR", (*udh_type)? ", " : ""); } } } free(tmp); } } #ifndef USE_ICONV // 3.1beta7, 3.0.9: decoding is always done: userdatalength = decode_ucs2(ascii, userdatalength); alphabet = 0; #else userdatalength = iconv_ucs2utf(ascii, userdatalength, sizeof(ascii)); alphabet = 4; #endif } #ifdef DEBUGMSG printf("!! userdatalength=%i\n",userdatalength); printf("!! name=%s\n",name); printf("!! sendr=%s\n",sendr); printf("!! date=%s\n",date); printf("!! Time=%s\n",Time); if ((alphabet==-1 && DEVICE.cs_convert==1)||(alphabet==0)||(alphabet==4)) printf("!! ascii=%s\n",ascii); printf("!! smsc=%s\n",smsc); printf("!! with_udh=%i\n",with_udh); printf("!! udh_data=%s\n",udh_data); printf("!! udh_type=%s\n",udh_type); printf("!! is_statusreport=%i\n",is_statusreport); printf("!! is_unsupported_pdu=%i\n", is_unsupported_pdu); printf("!! from_toa=%s\n", from_toa); printf("!! report=%i\n", report); printf("!! replace=%i\n", replace); #endif if (is_statusreport) { char *p; char id[41]; char status[128]; const char SR_MessageId[] = "Message_id:"; // Fixed title inside the status report body. const char SR_Status[] = "Status:"; // Fixed title inside the status report body. *id = 0; *status = 0; if ((p = strstr(ascii, SR_MessageId))) sprintf(id, ", %s %i", SR_MessageId, atoi(p +strlen(SR_MessageId) +1)); if ((p = strstr(ascii, SR_Status))) // 3.1.14: include explanation //sprintf(status, ", %s %i", SR_Status, atoi(p +strlen(SR_Status) +1)); snprintf(status, sizeof(status), ", %s %s", SR_Status, p +strlen(SR_Status) +1); writelogfile(LOG_NOTICE, 0, "SMS received (report%s%s), From: %s", id, status, sendr); } *stored_concatenated = 0; if (do_internal_combine == 1) { int offset = 0; // points to the part count byte. int m_id; char storage_udh[SIZE_UDH_DATA] = {}; int a_type; if ((a_type = get_concatenation(udh_data, &m_id, &p_count, &p_number)) > 0) { if (p_count > 1) { if (a_type == 1) sprintf(storage_udh, "05 00 03 "); else sprintf(storage_udh, "06 08 04 %02X ", (m_id & 0xFF00) >> 8); sprintf(strchr(storage_udh, 0), "%02X ", m_id & 0xFF); sprintf(strchr(storage_udh, 0), "%02X %02X ", p_count, p_number); offset = (a_type == 1)? 12 : 15; } } if (offset) { // This is a part of a concatenated message. char con_filename[PATH_MAX]; //int partcount; char st[1024]; char messageid[6]; int i; int found = 0; int udlen; int ftmp; char tmp_filename[PATH_MAX]; int cmp_start; int cmp_length; char *p; int part; struct stat statbuf; // First we store it to the concatenated store of this device: // 3.1beta7: Own folder for storage and smsd's other saved files: sprintf(con_filename, CONCATENATED_DIR_FNAME, (*d_saved)? d_saved : d_incoming, DEVICE.name); if (!(fd = fopen(con_filename, "a"))) { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot open file %s: %s", con_filename, strerror(errno))); alarm_handler0(LOG_ERR, tb); result = 0; } else { //UDH-DATA: 05 00 03 02 03 02 PDU.... //UDH-DATA: 06 08 04 00 02 03 02 PDU.... fprintf(fd, "%s%s\n", storage_udh/*udh_data*/, line2); fclose(fd); //partcount = octet2bin(udh_data +offset); userdatalength = 0; *ascii = '\0'; sprintf(messageid, (offset == 12)? "%.2s" : "%.5s", storage_udh/*udh_data*/ + 9); i = octet2bin(line2); cmp_length = octet2bin(line2 +2 +i*2 +2); if (cmp_length%2 != 0) cmp_length++; // 3.1.5: fixed start position of compare: //cmp_start = 2 +i*2 +4; cmp_start = 2 +i*2 +6; #ifdef DEBUGMSG printf("!! --------------------\n"); printf("!! storage_udh=%s\n", storage_udh); printf("!! line2=%.50s...\n", line2); printf("!! messageid=%s\n", messageid); printf("!! cmp_start=%i\n", cmp_start); printf("!! cmp_length=%i\n", cmp_length); printf("!!\n"); #endif // Next we try to find each part, starting at the first one: fd = fopen(con_filename, "r"); for (i = 1; i <= p_count/*partcount*/; i++) { found = 0; fseek(fd, 0, SEEK_SET); while (fgets(st, sizeof(st), fd)) { p = st +(octet2bin(st) +1) *3; part = (strncmp(st +3, "00", 2) == 0)? octet2bin(st +15) : octet2bin(st +18); if (strncmp(st +9, messageid, strlen(messageid)) == 0 && part == i && strncmp(p +cmp_start, line2 +cmp_start, cmp_length) == 0) { found = 1; pdu_store_length += strlen(p) +5; break; } } // If some part was not found, we can take a break. if (!found) break; } if (!found) { fclose(fd); *stored_concatenated = 1; } else { incoming_pdu_store = (char *)malloc(pdu_store_length +1); if (incoming_pdu_store) *incoming_pdu_store = 0; for (i = 1; i <= p_count/*partcount*/; i++) { fseek(fd, 0, SEEK_SET); while (fgets(st, sizeof(st), fd)) { p = st +(octet2bin(st) +1) *3; part = (strncmp(st +3, "00", 2) == 0)? octet2bin(st +15) : octet2bin(st +18); if (strncmp(st +9, messageid, strlen(messageid)) == 0 && part == i && strncmp(p +cmp_start, line2 +cmp_start, cmp_length) == 0) { if (incoming_pdu_store) { strcat(incoming_pdu_store, "PDU: "); strcat(incoming_pdu_store, p); } // Correct part was found, concatenate it's text to the buffer: if (i == 1) // udh_data and _type are taken from the first part only. udlen = splitpdu(p, DEVICE.mode, &alphabet, sendr, date, Time, ascii +userdatalength, smsc, &with_udh, udh_data, udh_type, &is_statusreport, &is_unsupported_pdu, from_toa, &report, &replace, warning_headers, &flash, do_internal_combine_binary); else udlen = splitpdu(p, DEVICE.mode, &alphabet, sendr, date, Time, ascii +userdatalength, smsc, &with_udh, 0, 0, &is_statusreport, &is_unsupported_pdu, from_toa, &report, &replace, warning_headers, &flash, do_internal_combine_binary); if (alphabet==-1 && DEVICE.cs_convert==1) udlen=gsm2iso(ascii +userdatalength,udlen,ascii +userdatalength,sizeof(ascii) -userdatalength); else if (alphabet == 2 && do_decode_unicode_text == 1) { #ifndef USE_ICONV udlen = decode_ucs2(ascii +userdatalength, udlen); alphabet = 0; #else udlen = iconv_ucs2utf(ascii +userdatalength, udlen, sizeof(ascii) -userdatalength); alphabet = 4; #endif } userdatalength += udlen; break; } } } sprintf(tmp_filename,"%s/%s.XXXXXX",d_incoming,DEVICE.name); ftmp = mkstemp(tmp_filename); fseek(fd, 0, SEEK_SET); while (fgets(st, sizeof(st), fd)) { p = st +(octet2bin(st) +1) *3; if (!(strncmp(st +9, messageid, strlen(messageid)) == 0 && strncmp(p +cmp_start, line2 +cmp_start, cmp_length) == 0)) //write(ftmp, &st, strlen(st)); write(ftmp, st, strlen(st)); } close(ftmp); fclose(fd); unlink(con_filename); rename(tmp_filename, con_filename); // 3.1beta7: If a file is empty now, remove it: if (stat(con_filename, &statbuf) == 0) if (statbuf.st_size == 0) unlink(con_filename); // UDH-DATA is not valid anymore: // *udh_data = '\0'; // with_udh = 0; if (remove_concatenation(udh_data) > 0) { if (!(*udh_data)) { with_udh = 0; *udh_type = 0; } else { if (explain_udh(udh_type, udh_data) < 0) if (strlen(udh_type) +7 < SIZE_UDH_TYPE) sprintf(strchr(udh_type, 0), "%sERROR", (*udh_type)? ", " : ""); } } } // if, all parts were found. } } // if (offset), received message had concatenation header with more than 1 parts. } // do_internal_combine ends. if (p_count == 1) { if (!is_statusreport) writelogfile(LOG_NOTICE, 0, "SMS received, From: %s", sendr); } else writelogfile(LOG_NOTICE, 0, "SMS received (part %i/%i), From: %s", p_number, p_count, sendr); if (result) { if (*stored_concatenated) result = 0; else { // 3.1: //sprintf(filename, "%s/%s.XXXXXX", (is_statusreport && *d_report)? d_report : d_incoming, DEVICE.name); char timestamp[81]; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, date_filename_format); if (date_filename == 1) sprintf(filename, "%s/%s.%s.XXXXXX", (is_statusreport && *d_report)? d_report : d_incoming, timestamp, DEVICE.name); else if (date_filename == 2) sprintf(filename, "%s/%s.%s.XXXXXX", (is_statusreport && *d_report)? d_report : d_incoming, DEVICE.name, timestamp); else sprintf(filename, "%s/%s.XXXXXX", (is_statusreport && *d_report)? d_report : d_incoming, DEVICE.name); close(mkstemp(filename)); //Replace the temp file by a new file with same name. This resolves wrong file permissions. unlink(filename); if ((fd = fopen(filename, "w"))) { char *p; // 3.1beta7, 3.0.9: This header can be used to detect that a message has no usual content: if (is_unsupported_pdu) fprintf(fd, "Error: Cannot decode PDU, see text part for details.\n"); if (*warning_headers) fprintf(fd, "%s", warning_headers); fprintf(fd, "%s %s\n", get_header_incoming(HDR_From, HDR_From2), sendr); if (*from_toa && *HDR_FromTOA2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_FromTOA, HDR_FromTOA2), from_toa); if (name[0] && *HDR_Name2 != '-') fprintf(fd,"%s %s\n", get_header_incoming(HDR_Name, HDR_Name2), name); if (smsc[0] && *HDR_FromSMSC2 != '-') fprintf(fd,"%s %s\n", get_header_incoming(HDR_FromSMSC, HDR_FromSMSC2), smsc); if (date[0] && Time[0] && *HDR_Sent2 != '-') { make_datetime_string(sent, sizeof(sent), date, Time, 0); fprintf(fd, "%s %s\n", get_header_incoming(HDR_Sent, HDR_Sent2), sent); } // Add local timestamp make_datetime_string(received, sizeof(received), 0, 0, 0); fprintf(fd, "%s %s\n", get_header_incoming(HDR_Received, HDR_Received2), received); if (*HDR_Subject2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Subject, HDR_Subject2), DEVICE.name); // 3.1.4: if (*HDR_Modem2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Modem, HDR_Modem2), DEVICE.name); if (DEVICE.number[0]) if (*HDR_Number2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Number, HDR_Number2), DEVICE.number); if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Identity, HDR_Identity2), DEVICE.identity); if (report >= 0 && *HDR_Report2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Report, HDR_Report2), (report)? yes_word : no_word); if (replace >= 0 && *HDR_Replace2 != '-') fprintf(fd, "%s %i\n", get_header_incoming(HDR_Replace, HDR_Replace2), replace); // 3.1: Flash message is now detected. Header printed only if flash was used. if (flash > 0 && *HDR_Flash2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Flash, HDR_Flash2), yes_word); if (incomplete > 0 && *HDR_Incomplete2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Incomplete, HDR_Incomplete2), yes_word); p = ""; if (alphabet == -1) { if (DEVICE.cs_convert) p = "ISO"; else p = "GSM"; } else if (alphabet == 0) p = "ISO"; else if (alphabet == 1) p = "binary"; else if (alphabet == 2) p = "UCS2"; else if (alphabet == 4) p = "UTF-8"; else if (alphabet == 3) p = "reserved"; // 3.1.5: if (incoming_utf8 == 1 && alphabet <= 0) p = "UTF-8"; if (*HDR_Alphabet2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Alphabet, HDR_Alphabet2), p); if (udh_data[0]) fprintf(fd,"%s %s\n", HDR_UDHDATA, udh_data); if (udh_type[0] && *HDR_UDHType2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_UDHType, HDR_UDHType2), udh_type); // 3.1beta7: new header: if (*HDR_Length2 != '-') fprintf(fd, "%s %i\n", get_header_incoming(HDR_Length, HDR_Length2), (alphabet == 2)? userdatalength /2 : userdatalength); // 3.1beta7: This header is only necessary with binary messages. With other message types // there is UDH-DATA header included if UDH is presented. "true" / "false" is now // presented as "yes" / "no" which may be translated to some other language. if (alphabet == 1) fprintf(fd, "%s %s\n", HDR_UDH, (with_udh)? yes_word : no_word); // 3.1beta7, 3.0.9: with value 2 unsupported pdu's were not stored. if (store_received_pdu == 3 || (store_received_pdu == 2 && (alphabet == 1 || alphabet == 2)) || (store_received_pdu >= 1 && is_unsupported_pdu == 1)) { if (incoming_pdu_store) fprintf(fd,"%s", incoming_pdu_store); else fprintf(fd,"PDU: %s\n", line2); } // Show the error position (first) if possible: if (store_received_pdu >= 1 && is_unsupported_pdu == 1) { char *p; char *p2; int pos; int len = 1; int i = 0; if ((p = strstr(ascii, "Position "))) { if ((pos = atoi(p +9)) > 0) { if ((p = strchr(p +9, ','))) if ((p2 = strchr(p, ':')) && p2 > p +1) len = atoi(p +1); fprintf(fd, "Pos: "); while (i++ < pos -1) if (i % 10) { if (i % 5) fprintf(fd, "."); else fprintf(fd, "-"); } else fprintf(fd, "*"); for (i = 0; i < len; i++) fprintf(fd, "^"); fprintf(fd, "~here(%i)\n", pos); } } } // -------------------------------------------- fprintf(fd,"\n"); // UTF-8 conversion if necessary: if (incoming_utf8 == 1 && alphabet <= 0) { // 3.1beta7, 3.0.9: GSM alphabet is first converted to ISO if (alphabet == -1 && DEVICE.cs_convert == 0) { userdatalength = gsm2iso(ascii, userdatalength, ascii, sizeof(ascii)); alphabet = 0; // filename_preview will need this information. } iso2utf8_file(fd, ascii, userdatalength); } else fwrite(ascii,1,userdatalength,fd); fclose(fd); #ifdef DEBUGMSG if ((fd = fopen(filename, "r"))) { char buffer[1024]; printf("!! FILE: %s\n", filename); while (fgets(buffer, sizeof(buffer) -1, fd)) printf("%s", buffer); fclose(fd); } #endif if (filename_preview > 0) { char *text = NULL; char buffer[21]; if (alphabet < 1) { if (alphabet == -1 && DEVICE.cs_convert == 0) userdatalength = gsm2iso(ascii, userdatalength, ascii, sizeof(ascii)); text = ascii; } else if (alphabet == 1 || alphabet == 2) { strcpy(buffer, (alphabet == 1)? "binary" : "ucs2"); text = buffer; } apply_filename_preview(filename, text, filename_preview); } writelogfile(LOG_INFO, 0, "Wrote an incoming %smessage file: %s", (incomplete)? "incomplete " : "", filename); result = is_statusreport; //return is_statusreport; } else { writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot create file %s!", filename)); alarm_handler0(LOG_ERR, tb); result = 0; //return 0; } } } if (incoming_pdu_store) { free(incoming_pdu_store); incoming_pdu_store = NULL; } return result; } /* ======================================================================= Receive one SMS ======================================================================= */ int receivesms(int* quick, int only1st) // receive one SMS or as many as the modem holds in memory // if quick=1 then no initstring // if only1st=1 then checks only 1st memory space // Returns 1 if successful // Return 0 if no SM available // Returns -1 on error { int max_memory,used_memory; int start_time=time(0); int found; int foundsomething=0; int statusreport; int sim = 0; char line1[1024]; char line2[1024]; char filename[PATH_MAX]; char cmdline[PATH_MAX+PATH_MAX+32]; int stored_concatenated; int memories; char *p; char *p2; char memory_list[1024]; char delete_list[1024]; size_t i; flush_smart_logging(); if (terminate == 1) return 0; used_memory = 0; #ifdef DEBUGMSG printf("receivesms(quick=%i, only1st=%i)\n",*quick,only1st); #endif STATISTICS->status = 'r'; writelogfile(LOG_INFO, 0, "Checking device for incoming SMS"); if (*quick==0 && DEVICE.modem_disabled == 0) { if (initialize_modem_receiving()) { STATISTICS->usage_r+=time(0)-start_time; flush_smart_logging(); return -1; } } // Dual-memory handler: for (memories = 0; memories <= 2; memories++) { if (terminate == 1) break; *delete_list = 0; if (DEVICE.primary_memory[0] && DEVICE.secondary_memory[0] && DEVICE.modem_disabled == 0) { char command[128]; char answer[1024]; char *memory; memory = 0; if (memories == 1) { if (only1st) break; memory = DEVICE.secondary_memory; } else if (memories == 2) memory = DEVICE.primary_memory; if (memory) { sprintf(command, "AT+CPMS=\"%s\"\r", memory); // 3.1beta7: initially the value was giwen as ME or "ME" without multiple memories defined. // If there is ME,ME,ME or "ME","ME,"ME" given, all "'s are removed while reading the setup. // Now, if there is a comma in the final command string, "'s are added back: p = command; while (*p && (p = strchr(p, ','))) { if (strlen(command) > sizeof(command) -3) break; for (p2 = strchr(command, 0); p2 > p; p2--) *(p2 +2) = *p2; strncpy(p, "\",\"", 3); p += 3; } writelogfile(LOG_INFO, 0, "Changing memory"); put_command(command, answer, sizeof(answer), 1, EXPECT_OK_ERROR); // 3.1.9: if (strstr(answer, "ERROR")) { writelogfile(LOG_ERR, 1, "The modem said ERROR while trying to change memory to %s", memory); return -1; } } if (memories == 2) break; } else if (memories > 0) break; *check_memory_buffer = 0; // Check how many memory spaces we really can read if (check_memory(&used_memory, &max_memory, memory_list, sizeof(memory_list), delete_list, sizeof(delete_list)) == 0) break; found=0; if (used_memory>0) { if (max_memory == 0 && memories == 1) max_memory = DEVICE.secondary_memory_max; // 3.1.5: memory list handling if method 2 or 3 is used: //for (sim=DEVICE.read_memory_start; sim<=DEVICE.read_memory_start+max_memory-1; sim++) if (!value_in(DEVICE.check_memory_method, 6, CM_CMGD, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) sim = DEVICE.read_memory_start; for (;;) { if (!(*delete_list)) if (terminate == 1) break; if (value_in(DEVICE.check_memory_method, 6, CM_CMGD, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) { if (!(*memory_list)) break; sim = atoi(memory_list); if ((p = strchr(memory_list, ','))) strcpyo(memory_list, p +1); else *memory_list = 0; } else { if (sim > DEVICE.read_memory_start +max_memory -1) break; } *line2 = 0; if (value_in(DEVICE.check_memory_method, 3, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) { if (*check_memory_buffer) { // There can be more than one space before number, "+CMGL: %i" will not work. p = check_memory_buffer; while ((p = strstr(p, "+CMGL:"))) { if (atoi(p +6) == sim) break; p += 6; } if (p) { if ((p = strchr(p, '\r'))) { p++; if (*p == '\n') p++; if ((p2 = strchr(p, '\r'))) { i = p2 -p; if (i < sizeof(line2)) { strncpy(line2, p, i); line2[i] = 0; found = sim; } } } } } if (!(*line2)) writelogfile(LOG_ERR, 1, "CMGL PDU read failed with message %i, using CMGR.", sim); } if (!(*line2)) found = readsim(sim, line1, line2); // 3.1.5: Should stop if there was timeout: if (found == -2) { *quick = 0; break; } if (found>=0) { foundsomething=1; *quick=1; //Create a temp file for received message //3.1beta3: Moved to the received2file function, filename is now a return value: //sprintf(filename,"%s/%s.XXXXXX",d_incoming,DEVICE.name); //close(mkstemp(filename)); statusreport = received2file(line1, line2, filename, &stored_concatenated, 0); STATISTICS->received_counter++; if (stored_concatenated == 0) { if (eventhandler[0] || DEVICE.eventhandler[0]) { char *handler = eventhandler; if (DEVICE.eventhandler[0]) handler = DEVICE.eventhandler; snprintf(cmdline, sizeof(cmdline), "%s %s %s", handler, (statusreport)? "REPORT" : "RECEIVED", filename); exec_system(cmdline, EXEC_EVENTHANDLER); } } if (value_in(DEVICE.check_memory_method, 2, CM_CMGL_DEL_LAST, CM_CMGL_DEL_LAST_CHECK)) { char tmp[32]; sprintf(tmp, "%s%u", (*delete_list)? "," : "", found); if (strlen(delete_list) +strlen(tmp) < sizeof(delete_list)) strcat(delete_list, tmp); } else if (!value_in(DEVICE.check_memory_method, 1, CM_CMGL_SIMCOM)) deletesms(found); used_memory--; if (used_memory<1) break; // Stop reading memory if we got everything } if (only1st) break; //if (!value_in(DEVICE.check_memory_method, 6, CM_CMGD, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) sim++; } } if (*delete_list) deletesms_list(delete_list); } STATISTICS->usage_r+=time(0)-start_time; if (foundsomething) { flush_smart_logging(); return 1; } else { writelogfile(LOG_INFO, 0, (used_memory == 0)? "No SMS received" : "No SMS received (reading interrupted)"); flush_smart_logging(); return 0; } } /* ========================================================================================== Send a part of a message, this is physically one SM with max. 160 characters or 140 bytes ========================================================================================== */ int send_part(char* from, char* to, char* text, int textlen, int alphabet, int with_udh, char* udh_data, int quick, int flash, char* messageids, char* smsc, int report, int validity, int part, int parts, int replace_msg, int system_msg, int to_type, char *error_text) // alphabet can be -1=GSM 0=ISO 1=binary 2=UCS2 // with_udh can be 0=off or 1=on or -1=auto (auto = 1 for binary messages and text message with udh_data) // udh_data is the User Data Header, only used when alphabet= -1 or 0. // With alphabet=1 or 2, the User Data Header should be included in the text argument. // smsc is optional. Can be used to override config file setting. // Output: messageids // 3.1beta7: return value changed: // 0 = OK. // 1 = Modem initialization failed. // 2 = Cancelled because of too many retries. // 3 = Cancelled because shutdown request while retrying. // error_text: latest modem response. Might have a value even if return value is ok (when retry helped). { char pdu[1024]; int retries; char command[128]; char command2[1024]; char answer[1024]; char* posi1; char* posi2; char partstr[41]; char replacestr[41]; time_t start_time; char tmpid[10] = {0}; #ifdef DEBUGMSG printf("!! send_part(from=%s, to=%s, text=..., textlen=%i, alphabet=%i, with_udh=%i, udh_data=%s, quick=%i, flash=%i, messageids=..., smsc=%s, report=%i, validity=%i, part=%i, parts=%i, replace_msg=%i, system_msg=%i, to_type=%i)\n", from, to, textlen, alphabet, with_udh, udh_data, quick, flash, smsc, report, validity, part, parts, replace_msg, system_msg, to_type); #endif if (error_text) *error_text = 0; start_time = time(0); // Mark modem as sending STATISTICS->status = 's'; *partstr = 0; if (parts > 1) sprintf(partstr, " (part %i/%i)", part +1, parts); writelogfile(LOG_INFO, 0, "Sending SMS%s from %s to %s", partstr, from,to); // 3.1beta7: Now logged only if a message file contained Report:yes. if (report == 1 && !DEVICE.incoming) writelogfile(LOG_NOTICE, 0, "Cannot receive status report because receiving is disabled"); if ((quick==0 || (*smsc && !DEVICE.smsc_pdu)) && DEVICE.sending_disabled == 0) { int i; i = initialize_modem_sending(smsc); if (i) { STATISTICS->usage_s+=time(0)-start_time; flush_smart_logging(); return (i == 7)? 3 : 1; } } else { // 3.1: if (DEVICE.sending_disabled == 0 && DEVICE.check_network) { switch (wait_network_registration(1, 100)) { case -1: STATISTICS->usage_s+=time(0)-start_time; flush_smart_logging(); return 1; case -2: STATISTICS->usage_s+=time(0)-start_time; flush_smart_logging(); return 3; } } } // Compose the modem command make_pdu(to,text,textlen,alphabet,flash,report,with_udh,udh_data,DEVICE.mode,pdu,validity, replace_msg, system_msg, to_type, smsc); if (strcasecmp(DEVICE.mode,"old")==0) sprintf(command,"AT+CMGS=%i\r",(int)strlen(pdu)/2); else sprintf(command,"AT%s+CMGS=%i\r", (DEVICE.verify_pdu)? "E1" : "", (int)strlen(pdu)/2-1); // 3.1.4: verify_pdu sprintf(command2,"%s\x1A",pdu); if (store_sent_pdu) { char *title = "PDU: "; if (!outgoing_pdu_store) { if ((outgoing_pdu_store = (char *)malloc(strlen(title) +strlen(pdu) +2))) *outgoing_pdu_store = 0; } else outgoing_pdu_store = (char *)realloc((void *)outgoing_pdu_store, strlen(outgoing_pdu_store) +strlen(title) +strlen(pdu) +2); if (outgoing_pdu_store) sprintf(strchr(outgoing_pdu_store, 0), "%s%s\n", title, pdu); } // 3.1.5: DEBUGGING: //if (DEVICE.sending_disabled == 1) if (DEVICE.sending_disabled == 1 || (enable_smsd_debug && parts > 1 && part == 0 && strstr(smsd_debug, "drop1")) || (enable_smsd_debug && parts > 2 && part == 1 && strstr(smsd_debug, "drop2")) ) { writelogfile(LOG_NOTICE, 0, "Test run, NO actual sending:%s from %s to %s", partstr, from, to); writelogfile(LOG_DEBUG, 0, "PDU to %s: %s %s", to, command, pdu); // 3.1.12: Simulate sending time //sleep(1); t_sleep(4 + getrand(10)); strcpy(messageids, "1"); update_message_counter(1, DEVICE.name); STATISTICS->usage_s+=time(0)-start_time; STATISTICS->succeeded_counter++; writelogfile(LOG_NOTICE, 0, "SMS sent, Message_id: %s, To: %s, sending time %i sec.", messageids, to, time(0) -start_time); flush_smart_logging(); return 0; } else { retries=0; while (1) { // Send modem command // 3.1.5: initial timeout changed 1 --> 2 and for PDU 6 --> 12. put_command(command, answer, sizeof(answer), 2, "(>)|(ERROR)"); // Send message if command was successful if (!strstr(answer,"ERROR")) { put_command(command2, answer ,sizeof(answer), 12, EXPECT_OK_ERROR); // 3.1.14: if (DEVICE.verify_pdu) { char *buffer; if ((buffer = strdup(command2))) { int i; i = strlen(buffer); if (i > 1) { buffer[i - 1] = 0; if (strstr(answer, buffer)) writelogfile(LOG_NOTICE, 0, "Verify PDU: OK"); else { int src = 0; char *p = answer; while (buffer[src]) { if (*p && (p = strchr(p, buffer[src]))) { p++; src++; } else { writelogfile(LOG_ERR, 1, "Verify PDU: ERROR (pos: %s)", src); writelogfile(LOG_ERR, 1, "Verify PDU: -> %s", buffer); writelogfile(LOG_ERR, 1, "Verify PDU: <- %s", answer); break; } } if (buffer[src] == 0) writelogfile(LOG_NOTICE, 0, "Verify PDU: OK (not exact)"); } } free(buffer); } } } // 3.1.14: if (DEVICE.verify_pdu) { char answer2[1024]; put_command("ATE0\r", answer2 ,sizeof(answer2), 1, EXPECT_OK_ERROR); } // Check answer if (strstr(answer,"OK")) { // If the modem answered with an ID number then copy it into the messageid variable. posi1=strstr(answer,"CMGS: "); if (posi1) { posi1+=6; posi2=strchr(posi1,'\r'); if (! posi2) posi2=strchr(posi1,'\n'); if (posi2) posi2[0]=0; // 3.1: //strcpy(messageid,posi1); strcpy(tmpid, posi1); while (*tmpid == ' ') strcpyo(tmpid, tmpid +1); // 3.1.1: switch (DEVICE.messageids) { case 1: if (!(*messageids)) strcpy(messageids, tmpid); break; case 2: strcpy(messageids, tmpid); break; case 3: if (*messageids) sprintf(strchr(messageids, 0), " %s", tmpid); else strcpy(messageids, tmpid); break; } #ifdef DEBUGMSG printf("!! messageid=%s\n", tmpid); #endif } *replacestr = 0; if (replace_msg) sprintf(replacestr, ", Replace_msg: %i", replace_msg); // 3.1: //writelogfile(LOG_NOTICE, 0, "SMS sent%s, Message_id: %s%s, To: %s, sending time %i sec.", partstr, tmpid, replacestr, to, time(0) -start_time); // 3.1.14: show retries *answer = 0; if (retries > 0) snprintf(answer, sizeof(answer), " Retries: %i.", retries); writelogfile(LOG_NOTICE, 0, "SMS sent%s, Message_id: %s%s, To: %s, sending time %i sec.%s", partstr, tmpid, replacestr, to, time(0) -start_time, answer); // 3.1.1: update_message_counter(1, DEVICE.name); STATISTICS->usage_s+=time(0)-start_time; STATISTICS->succeeded_counter++; flush_smart_logging(); return 0; } else { // 3.1.14: show retries and trying time when stopping: int result = 0; // Set the error text: if (error_text) { strcpy(error_text, answer); cut_ctrl(error_text); cutspaces(error_text); } // 3.1.5: //writelogfile0(LOG_ERR, tb_sprintf("The modem said ERROR or did not answer.")); if (!(*answer)) writelogfile0(LOG_ERR, 1, tb_sprintf("The modem did not answer (expected OK).")); else writelogfile0(LOG_ERR, 1, tb_sprintf("The modem answer was not OK: %s", cut_ctrl(answer))); alarm_handler0(LOG_ERR, tb); if (++retries <= 2) { writelogfile(LOG_NOTICE, 1, "Waiting %i sec. before retrying", errorsleeptime); if (t_sleep(errorsleeptime)) result = 3; // Cancel if terminating else if (initialize_modem_sending("")) // Initialize modem after error result = 1; // Cancel if initializing failed } else result = 2; // Cancel if too many retries if (result) { STATISTICS->usage_s += time(0) - start_time; STATISTICS->failed_counter++; writelogfile0(LOG_WARNING, 1, tb_sprintf("Sending SMS%s to %s failed, trying time %i sec. Retries: %i.", partstr, to, time(0) - start_time, retries - 1)); alarm_handler0(LOG_WARNING, tb); flush_smart_logging(); return result; } } } } } /* ======================================================================= Send a whole message, this can have many parts ======================================================================= */ int send1sms(int* quick, int* errorcounter) // Search the queues for SMS to send and send one of them. // Returns 0 if queues are empty // Returns -1 if sending failed (v 3.0.1 because of a modem) // v 3.0.1 returns -2 if sending failed because of a message file, in this case // there is no reason to block a modem. // Returns 1 if successful { char filename[PATH_MAX]; char newfilename[PATH_MAX]; char to[SIZE_TO]; char from[SIZE_FROM]; char smsc[SIZE_SMSC]; char provider[SIZE_QUEUENAME]; char text[MAXTEXT]; int with_udh=-1; int had_udh = 0; // for binary message handling. char udh_data[SIZE_UDH_DATA]; int textlen; char part_text[maxsms_pdu+1]; int part_text_length; char directory[PATH_MAX]; char cmdline[PATH_MAX+PATH_MAX+32]; int q,queue; int part; int parts = 0; int maxpartlen; int eachpartlen; int alphabet; int success=0; int flash; int report; int split; int tocopy; int reserved; char messageids[SIZE_MESSAGEIDS] = {0}; int found_a_file=0; int validity; int voicecall; int hex; int system_msg; int to_type; int terminate_written=0; int replace_msg = 0; char macros[SIZE_MACROS]; int i; char *fail_text = 0; char error_text[2048]; char voicecall_result[1024] = {0}; char errortext[SIZE_TB] = {0}; // 3.1.4: char *filename_preview_buffer = 0; #ifdef DEBUGMSG printf("!! send1sms(quick=%i, errorcounter=%i)\n", *quick, *errorcounter); #endif // Search for one single sms file for (q = 0; q < NUMBER_OF_MODEMS; q++) { if (q == 1) if (DEVICE.queues[q][0] == 0) break; if (((queue=getqueue(DEVICE.queues[q],directory))!=-1) && (getfile(DEVICE.trust_spool, directory, filename, 1))) { found_a_file=1; break; } } // If there is no file waiting to send, then do nothing if (found_a_file==0) { #ifdef DEBUGMSG printf("!! No file\n"); printf("!! quick=%i errorcounter=%i\n",*quick,*errorcounter); #endif flush_smart_logging(); return 0; } readSMSheader(filename, 0, to,from,&alphabet,&with_udh,udh_data,provider,&flash,smsc,&report,&split, &validity, &voicecall, &hex, &replace_msg, macros, &system_msg, &to_type); // SMSC setting is allowed only if there is smsc set in the config file: if (DEVICE.smsc[0] == 0 && !DEVICE.smsc_pdu) smsc[0] = 0; // If the checkhandler has moved this message, some things are probably not checked: if (to[0]==0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("No destination in file %s",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "No destination"; success = -2; } #ifdef USE_ICONV else if (alphabet>3) #else else if (alphabet>2) #endif { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid alphabet in file %s",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid alphabet"; success = -2; } else if (to_type == -2) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid number type in file %s", filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid number type"; success = -2; } if (success == 0) { // Use config file setting if report is unset in file header if (report == -1) report = DEVICE.report; // 3.1beta7: With binary message udh data cannot be entered using the header: // 3.1: // if (alphabet == 1) // *udh_data = 0; // Set a default for with_udh if it is not set in the message file. if (with_udh==-1) { if ((alphabet==1 || udh_data[0]) && !system_msg) with_udh=1; else with_udh=0; } // Save the udh bit, with binary concatenated messages we need to know if // there is user pdu in the begin of a message. had_udh = with_udh; // If the header includes udh-data then enforce udh flag even if it is not 1. if (udh_data[0]) with_udh=1; // if Split is unset, use the default value from config file if (split==-1) split=autosplit; // disable splitting if udh flag or udh_data is 1 // 3.1beta7: binary message can have an udh: //if (with_udh && split) // 3.1: // if (*udh_data && split) if (*udh_data && split && alphabet != 1) { split=0; // Keke: very old and possible wrong message, if there is no need to do splitting? Autosplit=0 prevents this message. writelogfile(LOG_INFO, 0, "Cannot split this message because it has an UDH."); } #ifdef DEBUGMSG printf("!! to=%s, from=%s, alphabet=%i, with_udh=%i, udh_data=%s, provider=%s, flash=%i, smsc=%s, report=%i, split=%i\n",to,from,alphabet,with_udh,udh_data,provider,flash,smsc,report,split); #endif // If this is a text message, then read also the text if (alphabet<1 || alphabet >= 2) { #ifdef DEBUGMSG printf("!! This is %stext message\n", (alphabet >= 2)? "unicode " : ""); #endif maxpartlen = (alphabet >= 2)? maxsms_ucs2 : maxsms_pdu; // ucs2 = 140, pdu = 160 readSMStext(filename, 0, DEVICE.cs_convert && (alphabet==0), text, &textlen, macros); // Is the message empty? if (textlen==0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has no text",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "No text"; parts=0; success = -2; } else { // 3.1.4: if (filename_preview > 0 && alphabet < 1 && !system_msg) { if ((filename_preview_buffer = (char *)malloc(textlen +1))) { memcpy(filename_preview_buffer, text, textlen); filename_preview_buffer[textlen] = 0; gsm2iso(filename_preview_buffer, textlen, filename_preview_buffer, textlen +1); } } // ------ #ifdef USE_ICONV if (alphabet == 3) { if (is_ascii_gsm(text, textlen)) alphabet = -1; else { alphabet = 2; textlen = (int)iconv_utf2ucs(text, textlen, sizeof(text)); } } #endif // In how many parts do we need to split the text? if (split>0) { // 3.1beta7: Unicode part numbering now supported. //if (alphabet == 2) // With unicode messages // if (split == 2) // part numbering with text is not yet supported, // split = 3; // using udh numbering instead. // if it fits into 1 SM, then we need 1 part if (textlen<=maxpartlen) { parts=1; reserved=0; eachpartlen=maxpartlen; } else if (split==2) // number with text { reserved = 4; // 1/9_ if (alphabet == 2) reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; // If we have more than 9 parts, we need to reserve 6 chars for the numbers // And recalculate the number of parts. if (parts > 9) { reserved = 6; // 11/99_ if (alphabet == 2) reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; // 3.1beta7: there can be more than 99 parts: if (parts > 99) { reserved = 8; // 111/255_ if (alphabet == 2) reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; // 3.1.1: if (parts > 255) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has too long text",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Too long text"; parts=0; success = -2; } } } } else if (split==3) // number with udh { // reserve 7 chars for the UDH reserved=7; if (alphabet == 2) // Only six with unicode reserved = 6; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; concatenated_id++; if (concatenated_id>255) concatenated_id=0; } else { // no numbering, each part can have the full size eachpartlen=maxpartlen; reserved=0; parts = (textlen +eachpartlen -1) /eachpartlen; } } else { // split is 0, too long message is just cutted. eachpartlen=maxpartlen; reserved=0; parts=1; } if (parts>1) writelogfile(LOG_INFO, 0, "Splitting this message into %i parts of max %i characters%s.", parts, (alphabet == 2)? eachpartlen /2 : eachpartlen, (alphabet == 2)? " (unicode)" : ""); } } else { #ifdef DEBUGMSG printf("!! This is a binary message.\n"); #endif maxpartlen=maxsms_binary; if (hex == 1) readSMShex(filename, 0, text, &textlen, macros, errortext); else readSMStext(filename, 0, 0,text,&textlen, NULL); // 3.1: if (*udh_data) { int bytes = (strlen(udh_data) +1) / 3; int i; if (textlen <= (ssize_t)sizeof(text) -bytes) { memmove(text +bytes, text, textlen); for (i = 0; i < bytes; i++) text[i] = octet2bin(udh_data +i *3); textlen += bytes; } *udh_data = 0; } eachpartlen=maxpartlen; reserved=0; parts=1; // Is the message empty? if (textlen == 0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has no data.",filename)); alarm_handler0(LOG_NOTICE, tb); if (*errortext) fail_text = errortext; else fail_text = "No data"; parts=0; success = -2; } // 3.1beta7: Is the message too long?: if (textlen > maxpartlen) { if (system_msg) { // System message can use single part only writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has too long data for system message: %i (max: %i).", filename, textlen, maxpartlen)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Too long data for system message"; parts = 0; success = -2; } else if (!split) { // Binary messages are not sent partially. writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has too long data for single part (Autosplit: 0) sending: %i.", filename, textlen)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Too long data for single part sending"; parts = 0; success = -2; } else { // Always use UDH numbering. split = 3; reserved = 6; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; concatenated_id++; if (concatenated_id > 255) concatenated_id = 0; } } } } // success was ok after initial checks. // parts can now be 0 if there is some problems, // fail_text and success is also set. // Try to send each part if (parts > 0) writelogfile(LOG_INFO, 0, "I have to send %i short message for %s",parts,filename); #ifdef DEBUGMSG printf("!! textlen=%i\n",textlen); #endif // If sending concatenated message, replace_msg should not be used (otherwise previously // received part is replaced with a next one... if (parts > 1) replace_msg = 0; for (part=0; part1) // If more than 1 part and text numbering { sprintf(part_text, "%i/%i ", part +1, parts); if (alphabet == 2) { for (i = reserved; i > 0; i--) { part_text[i *2 -1] = part_text[i -1]; part_text[i *2 -2] = 0; } } tocopy = textlen -(part *eachpartlen); if (tocopy > eachpartlen) tocopy = eachpartlen; #ifdef DEBUGMSG printf("!! tocopy=%i, part=%i, eachpartlen=%i, reserved=%i\n",tocopy,part,eachpartlen,reserved); #endif memcpy(part_text +reserved, text +(eachpartlen *part), tocopy); part_text_length = tocopy +reserved; } else if (split==3 && parts>1) // If more than 1 part and UDH numbering { // in this case the numbers are not part of the text, but UDH instead tocopy = textlen -(part *eachpartlen); if (tocopy > eachpartlen) tocopy = eachpartlen; #ifdef DEBUGMSG printf("!! tocopy=%i, part=%i, eachpartlen=%i, reserved=%i\n",tocopy,part,eachpartlen,reserved); #endif memcpy(part_text, text +(eachpartlen *part), tocopy); part_text_length = tocopy; sprintf(udh_data,"05 00 03 %02X %02X %02X",concatenated_id,parts,part+1); with_udh=1; } else // No part numbers { tocopy = textlen -(part *eachpartlen); if (tocopy > eachpartlen) tocopy = eachpartlen; #ifdef DEBUGMSG printf("!! tocopy=%i, part=%i, eachpartlen=%i\n",tocopy,part,eachpartlen); #endif memcpy(part_text, text +(eachpartlen *part), tocopy); part_text_length = tocopy; } // Some modems cannot send if the memory is full. if ((receive_before_send) && (DEVICE.incoming)) { receivesms(quick, 1); // TODO: return value handling, if modem got broken... } // Voicecall ability: // 3.1beta7: added calling time. if (part == 0 && voicecall == 1) { char command[1024]; char answer[1024]; int i; int count = 3; char *p, *p2, *p3; int wait_delay = 0; time_t wait_time; char *expect = "(OK)|(NO CARRIER)|(BUSY)|(NO ANSWER)|(ERROR)|(DELAYED)"; // 3.1.7: int saved_detect_message_routing = DEVICE.detect_message_routing; int saved_detect_unexpected_input = DEVICE.detect_unexpected_input; // 3.1.12: int call_lost = 0; DEVICE.detect_message_routing = 0; DEVICE.detect_unexpected_input = 0; if (DEVICE.modem_disabled == 1) { writelogfile(LOG_CRIT, 0, "Cannot make a voice call, MODEM IS DISABLED"); fail_text = "Modem was disabled"; success = -2; } else if (initialize_modem_sending("")) { writelogfile(LOG_CRIT, 1, "Cannot make a voice call, modem initialization failed"); fail_text = "Modem initialization failed"; success = -2; } else { // Automatic redialing should be turned off in the phone settings! part_text[part_text_length] = '\0'; cutspaces(part_text); for (i = 0; part_text[i]; i++) part_text[i] = toupper((int)part_text[i]); // Currently the starting header is optional: if (strncmp(part_text, "TONE:", 5) == 0) strcpyo(part_text, part_text +5); if ((p = strstr(part_text, "TIME:"))) { p2 = p +5; while (is_blank(*p2)) p2++; p3 = p2; while (isdigitc(*p3)) p3++; *p3 = 0; wait_delay = atoi(p2); strcpyo(p, p3 +1); } cutspaces(part_text); // If there is a space, the first part is taken as count: if ((p = strchr(part_text, ' '))) { *p = '\0'; if ((count = atoi(part_text)) <= 0) count = 3; cutspaces(strcpyo(part_text, p +1)); } if (!(*part_text)) strcpy(part_text, "1,1,1,#,0,0,0,#,1,1,0,0,1"); writelogfile(LOG_INFO, 0, "I have to make a voice call to %s, with (%i times) DTMF %s", to,count,part_text); if (set_numberformat(NULL, to, to_type) == NF_INTERNATIONAL) sprintf(command, "ATD+%s;\r", to); else sprintf(command, "ATD%s;\r", to); if (DEVICE.voicecall_cpas || DEVICE.voicecall_clcc) { // OK is returned after ATD command. (BenQ M32) // Dest phone is off: after 25 sec "NO ANSWER" // Dest phone does not answer: after 1 min 40 sec "NO ANSWER" // Dest phone hooks: after couple of seconds "BUSY" // AT+CPAS return codes: // 0: ready (ME allows commands from TA/TE) // 1: unavailable (ME does not allow commands from TA/TE) // 2: unknown (ME is not guaranteed to respond to instructions) // 3: ringing (ME is ready for commands from TA/TE, but the ringer is active) // 4: call in progress (ME is ready for commands from TA/TE, but a call is in progress) // 5: asleep (ME is unable to process commands from TA/TE because it is in a low functionality state) Also all other values below 128 are reserved. // 3.1.12: int was_ringing = 0; wait_time = time(0); put_command(command, answer, sizeof(answer), 24, expect); for (;;) { change_crlf(cut_emptylines(cutspaces(strcpy(voicecall_result, answer))), ' '); usleep_until(time_usec() + 500000); if (strstr(answer, "NO CARRIER") || strstr(answer, "BUSY") || strstr(answer, "NO ANSWER") || strstr(answer, "ERROR") || strstr(answer, "DELAYED")) { *answer = 0; break; } if (DEVICE.voicecall_cpas && strstr(answer, "+CPAS:")) { if (strstr(answer, "4")) break; if (!was_ringing && strstr(answer, "3")) was_ringing = 1; if (was_ringing && strstr(answer, "0")) { strcpy(voicecall_result, "Hangup"); writelogfile(LOG_INFO, 0, "The result of a voice call was %s", voicecall_result); *answer = 0; call_lost = 1; break; } } // 3.1.12: if (DEVICE.voicecall_clcc) { char tmp[64]; int break_for = 0; int found_clcc = 0; p = strstr(answer, "+CLCC:"); while (p) { // Check direction, 0 = Mobile Originated: getfield(p, 2, tmp, sizeof(tmp)); if (!strcmp(tmp, "0")) { // Check the number. Some modems do not show the + sign: getfield(p, 6, tmp, sizeof(tmp)); if (!strcmp(to, (*tmp == '+')? tmp + 1 : tmp)) { found_clcc = 1; // State of the call (MO): // 0 = Active. // ( 1 = Held. ) // 2 = Dialing. // 3 = Alerting. getfield(p, 3, tmp, sizeof(tmp)); i = atoi(tmp); if (i == 0) { strcpy(answer, "OK"); break_for = 1; break; } if (!was_ringing && (i == 2 || i == 3)) was_ringing = 1; } } p = strstr(p +1, "+CLCC:"); } if (break_for) break; if (was_ringing && !found_clcc) { strcpy(voicecall_result, "Hangup"); writelogfile(LOG_INFO, 0, "The result of a voice call was %s", voicecall_result); *answer = 0; call_lost = 1; break; } } if (time(0) > wait_time + 150) { strcpy(voicecall_result, "Timeout"); writelogfile(LOG_INFO, 0, "The result of a voice call was %s", voicecall_result); *answer = 0; break; } put_command((DEVICE.voicecall_cpas)? "AT+CPAS\r" : "AT+CLCC\r", answer, sizeof(answer), 24, expect); } } else { if (!wait_delay) { // 3.1.7: // put_command(command, answer, sizeof(answer), 24, expect); i = put_command(command, answer, sizeof(answer), 24, expect); if (!(*answer) && i == -2) strcpy(answer, "Timeout"); change_crlf(cut_emptylines(cutspaces(strcpy(voicecall_result, answer))), ' '); writelogfile(LOG_INFO, 0, "The result of a voice call was %s", voicecall_result); } else { put_command(command, 0, 0, 0, 0); writelogfile(LOG_DEBUG, 0, "Waiting for %i seconds", wait_delay); answer[0] = 0; wait_time = time(0); do { read_from_modem(answer, sizeof(answer), 2); // One read attempt is 200ms if (strstr(answer, "OK") || strstr(answer, "NO CARRIER") || strstr(answer, "BUSY") || strstr(answer, "NO ANSWER") || strstr(answer, "ERROR") || strstr(answer, "DELAYED")) { // 3.1.5beta9: cutspaces(answer); cut_emptylines(answer); if (log_single_lines) change_crlf(answer, ' '); if (DEVICE.voicecall_ignore_modem_response) { writelogfile(LOG_DEBUG, 0, "<- %s (ignored)", answer); answer[0] = 0; } else { writelogfile(LOG_DEBUG, 0, "<- %s", answer); break; } } t_sleep(1); } while (time(0) < wait_time +wait_delay); change_crlf(cut_emptylines(cutspaces(strcpy(voicecall_result, answer))), ' '); // 3.1.7: writelogfile(LOG_INFO, 0, "The result of a voice call was %s", voicecall_result); } } // Some test results: // Dest phone is off: after 1 min 10 sec "NO ANSWER". // Dest phone does not answer: after 2 min 10 sec "", after CHUP "NO ANSWER". // Dest phone hooks: after couple of seconds "BUSY". // Dest phone answers: "OK". // CHUP after waiting 15 sec: "DELAYED". if (strstr(answer, "OK")) { // We are talking to the phone now. // ---------------------------------------------------------------------- // 3.1.3: Security fix: used entered string was sent to the modem without // checking if it contains any/illegal AT commands. // Alternate VTS usage format is added too. //sprintf(command, "AT+VTS=%s\r", part_text); char *ptr = part_text; int cmd_length; char end_char; int tones = 0; char *quotation_mark; cmd_length = (DEVICE.voicecall_vts_list) ? 2 : 9; end_char = (DEVICE.voicecall_vts_list) ? ',' : ';'; quotation_mark = (DEVICE.voicecall_vts_quotation_marks) ? "\"" : ""; while (*ptr) { // Some modems support ABCD too but some not. if (!strchr("*#0123456789", *ptr)) strcpyo(ptr, ptr + 1); else ptr++; } ptr = part_text; strcpy(command, "AT"); if (DEVICE.voicecall_vts_list) strcat(command, "+VTS="); while (*ptr) { if (strlen(command) + cmd_length >= sizeof(command)) break; if (DEVICE.voicecall_vts_list) sprintf(strchr(command, 0), "%c%c", *ptr, end_char); else sprintf(strchr(command, 0), "+VTS=%s%c%s%c", quotation_mark, *ptr, quotation_mark, end_char); ptr++; tones++; } if ((ptr = strrchr(command, end_char))) *ptr = '\r'; // ---------------------------------------------------------------------- for (i = 0; (i < count) && tones; i++) { t_sleep(3); put_command(command, answer, sizeof(answer), tones *0.39 +1, expect); if (strstr(answer, "ERROR")) if (i > 0) break; } t_sleep(1); } if (!call_lost) { if (DEVICE.voicecall_hangup_ath == 1 || (DEVICE.voicecall_hangup_ath == -1 && voicecall_hangup_ath == 1)) sprintf(command, "ATH\r"); else sprintf(command, "AT+CHUP\r"); put_command(command, answer, sizeof(answer), 1, expect); if (!(*voicecall_result)) change_crlf(cut_emptylines(cutspaces(strcpy(voicecall_result, answer))), ' '); } success = 1; } DEVICE.detect_message_routing = saved_detect_message_routing; DEVICE.detect_unexpected_input = saved_detect_unexpected_input; break; // for parts... } else { // Try to send the sms // If there is no user made udh (message header say so), the normal // concatenation header can be used. With user made udh the concatenation // information of a first part is inserted to the existing udh. Other but // first message part can be processed as usual. if (alphabet == 1 && part == 0 && parts > 1 && had_udh) { int n; *udh_data = 0; n = part_text[0]; // Check if length byte has too high value: if (n >= part_text_length) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s has incorrect first byte of UDH.",filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Incorrect first byte of UDH"; success = -2; break; // for parts... } for (i = part_text_length -1; i > n; i--) part_text[i +5] = part_text[i]; part_text[n +1] = 0; part_text[n +2] = 3; part_text[n +3] = concatenated_id; part_text[n +4] = parts; part_text[n +5] = part +1; part_text[0] = n +5; part_text_length += 5; } i = send_part((from[0])? from : DEVICE.number, to, part_text, part_text_length, alphabet, with_udh, udh_data, *quick, flash, messageids, smsc, report, validity, part, parts, replace_msg, system_msg, to_type, error_text); if (i == 0) { // Sending was successful *quick=1; success=1; // Possible previous errors are ignored because now the modem worked well: *errorcounter=0; } else { // Sending failed *quick=0; success=-1; if (i == 1) fail_text = "Modem initialization failed"; else if (*error_text) fail_text = error_text; else fail_text = "Unknown"; // Do not send the next part if sending failed break; } } if (partstatus = 'i'; if (*messageids && DEVICE.messageids == 3) strcat(messageids, " ."); if (success < 0) { // Sending failed // 3.1beta7: char remove_headers[4096]; char add_headers[4096]; // 3.1.1: Empty file is not moved to the failed folder. struct stat statbuf; int file_is_empty = 0; char timestamp[81]; if (stat(filename, &statbuf) == 0) if (statbuf.st_size == 0) file_is_empty = 1; prepare_remove_headers(remove_headers, sizeof(remove_headers)); *add_headers = 0; if (*HDR_Modem2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Modem, HDR_Modem2), DEVICE.name); // 3.1.4: if (DEVICE.number[0]) if (*HDR_Number2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Number, HDR_Number2), DEVICE.number); if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Identity, HDR_Identity2), DEVICE.identity); if (fail_text && *fail_text && *HDR_FailReason2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_FailReason, HDR_FailReason2), fail_text); // 3.1.5: Timestamp for failed message: make_datetime_string(timestamp, sizeof(timestamp), 0, 0, 0); if (*HDR_Failed2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Failed, HDR_Failed2), timestamp); change_headers(filename, remove_headers, outgoing_pdu_store, "%s", add_headers); // Move file into failed queue or delete if (d_failed[0] && !file_is_empty) { movefilewithdestlock_new(filename,d_failed, keep_filename, 0, process_title, newfilename); stop_if_file_exists("Cannot move",filename,"to",d_failed); // 3.1.1 Filename preview is applied for failed files too. if (filename_preview > 0 && !system_msg) { char *txt = NULL; char buffer[21]; if (alphabet < 1) { if (filename_preview_buffer) txt = filename_preview_buffer; else txt = text; } else if (alphabet == 1 || alphabet == 2) { strcpy(buffer, (alphabet == 1)? "binary" : "ucs2"); txt = buffer; } apply_filename_preview(newfilename, txt, filename_preview); } //writelogfile(LOG_INFO, process_title, "Moved file %s to %s",filename, (keep_filename)? d_failed : newfilename); writelogfile(LOG_INFO, 0, "Moved file %s to %s", filename, newfilename); } if (eventhandler[0] || DEVICE.eventhandler[0]) { if (DEVICE.eventhandler[0]) snprintf(cmdline, sizeof(cmdline), "%s FAILED %s %s", DEVICE.eventhandler, (d_failed[0] == 0 || file_is_empty)? filename : newfilename, messageids); else snprintf(cmdline, sizeof(cmdline), "%s FAILED %s %s", eventhandler, (d_failed[0] == 0 || file_is_empty)? filename : newfilename, messageids); exec_system(cmdline, EXEC_EVENTHANDLER); } if (d_failed[0] == 0 || file_is_empty) { unlink(filename); stop_if_file_exists("Cannot delete",filename,"",""); writelogfile(LOG_INFO, 0, "Deleted file %s",filename); } unlockfile(filename); if (success == -1) { // Check how often this modem failed and block it if it seems to be broken (*errorcounter)++; if (*errorcounter>=blockafter) { writelogfile0(LOG_CRIT, 1, tb_sprintf("Fatal error: sending failed %i times. Blocking %i sec.", blockafter, blocktime)); alarm_handler0(LOG_CRIT, tb); STATISTICS->multiple_failed_counter++; STATISTICS->status = 'b'; t_sleep(blocktime); *errorcounter=0; } } } else { // Sending was successful char timestamp[81]; char remove_headers[4096]; char add_headers[8192]; char *p; prepare_remove_headers(remove_headers, sizeof(remove_headers)); *add_headers = 0; if (*HDR_Modem2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Modem, HDR_Modem2), DEVICE.name); // 3.1.4: if (DEVICE.number[0]) if (*HDR_Number2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Number, HDR_Number2), DEVICE.number); make_datetime_string(timestamp, sizeof(timestamp), 0, 0, 0); if (*HDR_Sent2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Sent, HDR_Sent2), timestamp); if (report > 0 && messageids[0] != 0) { if (*HDR_MessageId2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_MessageId, HDR_MessageId2), messageids); } if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Identity, HDR_Identity2), DEVICE.identity); if (*voicecall_result && *HDR_Result != '.') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Result, HDR_Result2), voicecall_result); if (store_sent_pdu == 3 || (store_sent_pdu == 2 && (alphabet == 1 || alphabet == 2))) p = outgoing_pdu_store; else p = NULL; change_headers(filename, remove_headers, p, "%s", add_headers); // Move file into sent queue or delete after eventhandler is executed. if (d_sent[0]) { movefilewithdestlock_new(filename,d_sent, keep_filename, 0, process_title, newfilename); stop_if_file_exists("Cannot move",filename,"to",d_sent); // 3.1.1 Filename preview is applied for sent files too. if (filename_preview > 0 && !system_msg) { char *txt = NULL; char buffer[21]; if (alphabet < 1) { if (filename_preview_buffer) txt = filename_preview_buffer; else txt = text; } else if (alphabet == 1 || alphabet == 2) { strcpy(buffer, (alphabet == 1)? "binary" : "ucs2"); txt = buffer; } apply_filename_preview(newfilename, txt, filename_preview); } //writelogfile(LOG_INFO, process_title, "Moved file %s to %s",filename, (keep_filename)? d_sent : newfilename); writelogfile(LOG_INFO, 0, "Moved file %s to %s", filename, newfilename); } if (eventhandler[0] || DEVICE.eventhandler[0]) { // Keke: Documentation says about the messsageid: // " it is only used if you sent a message successfully with status report enabled." // Perhaps it should be removed when status report was not requested? if (DEVICE.eventhandler[0]) snprintf(cmdline, sizeof(cmdline), "%s SENT %s %s", DEVICE.eventhandler, (d_sent[0] == 0)? filename : newfilename, messageids); else snprintf(cmdline, sizeof(cmdline), "%s SENT %s %s", eventhandler, (d_sent[0] == 0)? filename : newfilename, messageids); exec_system(cmdline, EXEC_EVENTHANDLER); } if (d_sent[0] == 0) { unlink(filename); stop_if_file_exists("Cannot delete",filename,"",""); writelogfile(LOG_INFO, 0, "Deleted file %s",filename); } unlockfile(filename); } #ifdef DEBUGMSG printf("quick=%i errorcounter=%i\n",*quick,*errorcounter); printf("returns %i\n",success); #endif if (outgoing_pdu_store) { free(outgoing_pdu_store); outgoing_pdu_store = NULL; } // 3.1.4: if (filename_preview_buffer) free(filename_preview_buffer); flush_smart_logging(); return success; } /* ======================================================================= Administrative message sending. This is done without filesystem. ======================================================================= */ void send_admin_message(int *quick, int *errorcounter, char *text) { char messageids[SIZE_MESSAGEIDS] = {0}; char *to = NULL; time_t now; static time_t last_msgc_clear = 0; static int message_count = 0; char buffer[256]; int textlen; (void) errorcounter; // 3.1.7: remove warning. if (DEVICE.admin_to[0]) to = DEVICE.admin_to; else if (admin_to[0]) to = admin_to; if (to) { if (!last_msgc_clear) last_msgc_clear = time(0); if (DEVICE.adminmessage_count_clear > 0) { now = time(0); if (now >= last_msgc_clear + DEVICE.adminmessage_count_clear) { if (message_count > 0) writelogfile(LOG_INFO, 0, "Alert limit counter cleared, it was %i.", message_count); last_msgc_clear = now; message_count = 0; } } if (DEVICE.adminmessage_limit > 0) { if (message_count >= DEVICE.adminmessage_limit) { writelogfile(LOG_INFO, 0, "Alert limit reached, did not send: %s", text); return; } } if (!message_count) last_msgc_clear = time(0); message_count++; writelogfile(LOG_INFO, 0, "Sending an administrative message: %s", text); textlen = iso_utf8_2gsm(text, strlen(text), buffer, sizeof(buffer)); send_part("Smsd3" /*from*/, to, buffer, textlen, 0 /*ISO alphabet*/, 0 /*with_udh*/, "" /*udh_data*/, *quick, 0 /*flash*/, messageids, "" /*smsc*/, DEVICE.report, -1 /*validity*/, 0, 1, 0, 0, -1 /*to_type*/, 0); } } /* ======================================================================= Device-Spooler (one for each modem) ======================================================================= */ int cmd_to_modem( // // // char *command, int cmd_number ) { int result = 1; char *cmd; char *p; char answer[500]; char buffer[600]; int fd; int log_retry = 3; int i; FILE *fp; int is_ussd = 0; // 3.1.7 if (!command || !(*command)) return 1; if (!try_openmodem()) return 0; if (cmd_number == 1 && DEVICE.needs_wakeup_at) { put_command("AT\r", 0, 0, 1, 0); usleep_until(time_usec() + 100000); read_from_modem(answer, sizeof(answer), 2); } if ((cmd = malloc(strlen(command) + 2))) { sprintf(cmd, "%s\r", command); // 3.1.5: Special case: AT+CUSD, wait USSD message: //put_command(*modem, device, cmd, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (!strncasecmp(command, "AT+CUSD", 7) && strlen(command) > 9) { is_ussd++; put_command(cmd, answer, sizeof(answer), 3, "(\\+CUSD:)|(ERROR)"); } else // 3.1.12: if (*cmd == '[' && strchr(cmd, ']')) { char *expect; if ((expect = strdup(cmd + 1))) { *(strchr(expect, ']')) = 0; put_command(strchr(cmd, ']') + 1, answer, sizeof(answer), 1, expect); free(expect); } } else // ------- put_command(cmd, answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (*answer) { char timestamp[81]; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); while ((p = strchr(answer, '\r'))) *p = ' '; while ((p = strchr(answer, '\n'))) *p = ' '; while ((p = strstr(answer, " "))) strcpyo(p, p + 1); if (*answer == ' ') strcpyo(answer, answer + 1); p = answer + strlen(answer); while (p > answer && p[-1] == ' ') --p; *p = 0; if (is_ussd && DEVICE.ussd_convert == 1) { #ifdef USE_ICONV // If answer of USSD received in unicode format, convert it to utf8 #define USSDOK "OK +CUSD: 2,\"" i = 0; if (!strncasecmp(answer, USSDOK, sizeof(USSDOK) - 1) && sscanf(p = answer + sizeof(USSDOK) - 1, "%[0-9A-Fa-f]\",72%n", buffer, &i) == 1 && !p[i] && i > 4 && ((i -= 4) & 3) == 0) #undef USSDOK { for (i = 0; buffer[i * 2]; i++) { unsigned v; sscanf(&buffer[i * 2], "%2X", &v); buffer[i] = (char) v; } i = (int) iconv_ucs2utf(buffer, i, sizeof(answer) - 2 - (p - answer)); //, 0); if (i != 0) { memcpy(p, buffer, i); p[i] = '"'; p[i + 1] = 0; } } #endif } else if (is_ussd && DEVICE.ussd_convert == 2) { if (strstr(answer, "+CUSD:")) { char *p1, *p2; if ((p1 = strchr(answer, '"'))) { p1++; if ((p2 = strchr(p1, '"'))) { snprintf(buffer, sizeof(buffer), "%.*s", (int)(p2 - p1), p1); if ((p = strdup(buffer))) { decode_7bit_packed(p, buffer, sizeof(buffer)); free(p); cut_ctrl(buffer); if (strlen(answer) < sizeof(answer) - strlen(buffer) - 4) sprintf(strchr(answer, 0), " // %s", buffer); } } } } } // 3.1.11: HEX dump: else if (is_ussd && DEVICE.ussd_convert == 4) { if (strstr(answer, "+CUSD:")) { char *p1, *p2; if ((p1 = strchr(answer, '"'))) { p1++; if ((p2 = strchr(p1, '"'))) { snprintf(buffer, sizeof(buffer), "%.*s", (int)(p2 - p1), p1); if ((p = strdup(buffer))) { int c; int idx; *buffer = 0; for (idx = 0; strlen(p +idx) > 1; idx += 2) { sscanf(&p[idx], "%2X", &c); sprintf(strchr(buffer, 0), "%c", (char)c); } free(p); cut_ctrl(buffer); if (strlen(answer) < sizeof(answer) - strlen(buffer) - 4) sprintf(strchr(answer, 0), " // %s", buffer); } } } } } if (is_ussd && DEVICE.eventhandler_ussd[0]) { char tmp_filename[PATH_MAX]; char te_charset[41]; size_t n, i; put_command("AT+CSCS?\r", te_charset, sizeof(te_charset), 1, EXPECT_OK_ERROR); cut_ctrl(cutspaces(te_charset)); if (!(p = strchr(te_charset, '"'))) *te_charset = 0; else { strcpyo(te_charset, p + 1); if ((p = strchr(te_charset, '"'))) *p = 0; } if (!(*te_charset)) strcpy(te_charset, "ERROR"); sprintf(tmp_filename, "%s/smsd.XXXXXX", "/tmp"); fd = mkstemp(tmp_filename); write(fd, answer, strlen(answer)); write(fd, "\n", 1); close(fd); n = snprintf(buffer, sizeof(buffer), "%s USSD %s %s %s \"", DEVICE.eventhandler_ussd, tmp_filename, DEVICE.name, te_charset); for (i = 0; command[i] != '\0' && n < sizeof(buffer) - 2; n++, i++) { if (command[i] == '"') buffer[n++] = '\\'; buffer[n] = command[i]; } if (n < sizeof(buffer) - 2) { FILE *fp_tmp; buffer[n] = '"'; buffer[n + 1] = '\0'; exec_system(buffer, EXEC_EVENTHANDLER); if ((fp_tmp = fopen(tmp_filename, "r"))) { if (fgets(buffer, sizeof(answer), fp_tmp)) strcpy(answer, cut_ctrl(buffer)); fclose(fp_tmp); } } else writelogfile(LOG_ERR, 0, "Buffer too small for execute USSD eventhadler for %s", DEVICE.name); unlink(tmp_filename); } if (DEVICE.dev_rr_logfile[0]) { while (log_retry-- > 0) { // NOTE: log files use mode 640 as a fixed value. if ((fd = open(DEVICE.dev_rr_logfile, O_APPEND | O_WRONLY | O_CREAT, 0640)) >= 0) { snprintf(buffer, sizeof(buffer), "%s,%i, %s: CMD: %s: %s\n", timestamp, DEVICE.dev_rr_loglevel, DEVICE.name, command, answer); write(fd, buffer, strlen(buffer)); close(fd); break; } if (log_retry > 0) { i = getrand(100); usleep_until(time_usec() + i * 10); } else writelogfile(LOG_ERR, 0, "Cannot open %s. %s", DEVICE.dev_rr_logfile, strerror(errno)); } } else writelogfile(DEVICE.dev_rr_loglevel, 0, "CMD: %s: %s", command, answer); // 3.1.5: If signal quality was checked, explain it to the log: if (!strcasecmp(cmd, "AT+CSQ\r")) explain_csq(DEVICE.dev_rr_loglevel, 0, answer, DEVICE.signal_quality_ber_ignore); if (DEVICE.dev_rr_statfile[0]) { // 3.1.6: //if ((fd = open(DEVICE.dev_rr_statfile, O_APPEND | O_WRONLY | O_CREAT, 0640)) >= 0) if ((fp = fopen(DEVICE.dev_rr_statfile, "a"))) { //snprintf(buffer, sizeof(buffer), "%s,%i, %s: CMD: %s: %s\n", timestamp, LOG_NOTICE, DEVICE.name, command, answer); //write(fd, buffer, strlen(buffer)); //close(fd); fprintf(fp, "%s,%i, %s: CMD: %s: %s\n", timestamp, LOG_NOTICE, DEVICE.name, command, answer); fclose(fp); } else writelogfile(LOG_ERR, 0, "Cannot open %s. %s", DEVICE.dev_rr_statfile, strerror(errno)); } } free(cmd); } return result; } int run_rr() { int result = 1; int modem_was_open; int i; FILE *fp; char st[4096]; char *p; char cmdline[PATH_MAX + PATH_MAX + 32]; int cmd_number = 0; modem_was_open = modem_handle >= 0; // 3.1.7: pre_run and post_run. dev_rr_statfile as $2. if (DEVICE.dev_rr[0]) { p = "PRE_RUN"; writelogfile(LOG_INFO, 0, "Running a regular_run (%s)", p); try_closemodem(1); // 3.1.9: added devicename. snprintf(cmdline, sizeof(cmdline), "%s %s \"%s\" %s", DEVICE.dev_rr, p, DEVICE.dev_rr_statfile, DEVICE.name); i = exec_system(cmdline, EXEC_RR_MODEM); if (i) { writelogfile0(LOG_ERR, 0, tb_sprintf("Regular_run %s %s returned %i", DEVICE.dev_rr, p, i)); alarm_handler0(LOG_ERR, tb); } } if (DEVICE.dev_rr_statfile[0]) unlink(DEVICE.dev_rr_statfile); // cmd_to_modem opens a modem if necessary. if (DEVICE.dev_rr_cmdfile[0]) { if ((fp = fopen(DEVICE.dev_rr_cmdfile, "r"))) { while (fgets(st, sizeof(st), fp)) { // 3.1.12: remove only linebreaks: //cutspaces(st); //cut_ctrl(st); cut_crlf(st); if (*st && *st != '#') { if (!cmd_to_modem(st, ++cmd_number)) { result = 0; break; } } } fclose(fp); unlink(DEVICE.dev_rr_cmdfile); } } if (result == 1) { p = DEVICE.dev_rr_cmd; while (*p) { if (!cmd_to_modem(p, ++cmd_number)) { result = 0; break; } p = strchr(p, 0) + 1; } } if (DEVICE.dev_rr_post_run[0]) { p = "POST_RUN"; if (DEVICE.dev_rr[0] == 0 || strcmp(DEVICE.dev_rr, DEVICE.dev_rr_post_run)) writelogfile(LOG_INFO, 0, "Running a regular_run_post_run %s", p); else writelogfile(LOG_INFO, 0, "Running a regular_run %s", p); try_closemodem(1); // 3.1.9: added devicename. snprintf(cmdline, sizeof(cmdline), "%s %s \"%s\" %s", DEVICE.dev_rr_post_run, p, DEVICE.dev_rr_statfile, DEVICE.name); i = exec_system(cmdline, EXEC_RR_POST_MODEM); if (i) { if (DEVICE.dev_rr[0] == 0 || strcmp(DEVICE.dev_rr, DEVICE.dev_rr_post_run)) writelogfile0(LOG_ERR, 0, tb_sprintf("Regular_run_post_run %s %s returned %i", DEVICE.dev_rr_post_run, p, i)); else writelogfile0(LOG_ERR, 0, tb_sprintf("Regular_run %s %s returned %i", DEVICE.dev_rr_post_run, p, i)); alarm_handler0(LOG_ERR, tb); } } if (modem_was_open) try_openmodem(); else try_closemodem(0); return result; } void do_ic_purge() { int ic_purge; char con_filename[PATH_MAX]; char tmp_filename[PATH_MAX +7]; struct stat statbuf; FILE *fp; FILE *fptmp; char line[1024]; int mcount = 0; int i; char *p; char buffer[LENGTH_PDU_DETAIL_REC +1]; ic_purge = ic_purge_hours *60 +ic_purge_minutes; if (ic_purge <= 0) return; sprintf(con_filename, CONCATENATED_DIR_FNAME, (*d_saved)? d_saved : d_incoming, DEVICE.name); if (stat(con_filename, &statbuf) == 0) if (statbuf.st_size == 0) return; if ((fp = fopen(con_filename, "r"))) { writelogfile0(LOG_INFO, 0, "Checking if concatenation storage has expired message parts"); sprintf(tmp_filename,"%s.XXXXXX", con_filename); close(mkstemp(tmp_filename)); unlink(tmp_filename); if (!(fptmp = fopen(tmp_filename, "w"))) { writelogfile0(LOG_WARNING, 0, tb_sprintf("Concatenation storage handling aborted, creating %s failed", tmp_filename)); alarm_handler0(LOG_WARNING, tb); fclose(fp); return; } #ifdef DEBUGMSG printf("!! do_ic_purge, %i\n", ic_purge); #endif while (fgets(line, sizeof(line), fp)) { //UDH-DATA: 05 00 03 02 03 02 PDU.... //UDH-DATA: 06 08 04 00 02 03 02 PDU.... #ifdef DEBUGMSG printf("!! %.50s...\n", line); #endif i = octet2bin(line); if (i == 5 || i == 6) { p = (i == 5)? line +18 : line +21; if ((size_t)(p -line) < strlen(line)) { *buffer = 0; i = get_pdu_details(buffer, sizeof(buffer), p, 0); if (i == 0) { time_t rawtime; struct tm *timeinfo; time_t now; time_t msgtime; char *p2; int pos_timestamp = 6; // 52; time(&rawtime); timeinfo = localtime(&rawtime); now = mktime(timeinfo); p2 = buffer +pos_timestamp; timeinfo->tm_year = atoi(p2) +100; timeinfo->tm_mon = atoi(p2 +3) -1; timeinfo->tm_mday = atoi(p2 +6); timeinfo->tm_hour = atoi(p2 +9); timeinfo->tm_min = atoi(p2 +12); timeinfo->tm_sec = atoi(p2 +15); msgtime = mktime(timeinfo); if (ic_purge *60 > now - msgtime) { #ifdef DEBUGMSG printf("!! %s", buffer); printf("!! remaining: %i\n", (int)(ic_purge - (now - msgtime) /60)); #endif fputs(line, fptmp); } else if (ic_purge_read) { char filename[PATH_MAX]; // Remove line termination (if PDU is printed to the message file): while (strlen(p) > 1 && strchr("\r\n", p[strlen(p) -1])) p[strlen(p) -1] = 0; received2file("", p, filename, &i, 1); mcount++; } } } } } fclose(fp); fclose(fptmp); if (mcount) { unlink(con_filename); rename(tmp_filename, con_filename); } else unlink(tmp_filename); } } // 3.1.7: int send_startstring() { // 3.1.12: if (DEVICE.modem_disabled) return 0; if (DEVICE.startstring[0]) { char answer[500]; int retries = 0; char *p; writelogfile(LOG_INFO, 0, "Sending start string to the modem"); do { retries++; put_command(DEVICE.startstring, answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) t_sleep(1); } while (retries < 2 && !strstr(answer, "OK")); if (strstr(answer, "OK") == 0) { p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Modem did not accept the start string%s%s", (*p) ? ", " : "", p)); alarm_handler0(LOG_ERR, tb); } flush_smart_logging(); if (DEVICE.startsleeptime > 0) { writelogfile(LOG_INFO, 0, "Spending sleep time after starting (%i sec)", DEVICE.startsleeptime); if (t_sleep(DEVICE.startsleeptime)) return 1; } } return 0; } // 3.1.7: int send_stopstring() { // 3.1.12: if (DEVICE.modem_disabled) return 0; if (DEVICE.stopstring[0]) { char answer[500]; int retries = 0; char *p; if (!try_openmodem()) writelogfile(LOG_ERR, 1, "Cannot send stop string to the modem"); else { writelogfile(LOG_INFO, 0, "Sending stop string to the modem"); do { retries++; put_command(DEVICE.stopstring, answer, sizeof(answer), 2, EXPECT_OK_ERROR); if (strstr(answer, "ERROR")) if (retries < 2) t_sleep(1); } while (retries < 2 && !strstr(answer, "OK")); if (strstr(answer, "OK") == 0) { p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Modem did not accept the stop string%s%s", (*p) ? ", " : "", p)); alarm_handler0(LOG_ERR, tb); } flush_smart_logging(); } } return 0; } // 3.1.7: Check if a modem is suspended. int check_suspend(int initialized) { if (break_suspend) return 0; if (*suspend_filename) { int suspended = 0; FILE *fp; int i; char name[33]; char line[PATH_MAX]; int found; int modem_was_open; int dt; modem_was_open = modem_handle >= 0; snprintf(name, sizeof(name), "%s:", DEVICE.name); while ((fp = fopen(suspend_filename, "r"))) { found = 0; while (fgets(line, sizeof(line), fp)) { cutspaces(cut_ctrl(line)); if (!strncmp(line, name, strlen(name)) || !strncmp(line, "ALL:", 4)) { found = 1; if (!suspended) { if (initialized) send_stopstring(); try_closemodem(1); strcpyo(line, strchr(line, ':') +1); cutspaces(line); writelogfile(LOG_NOTICE, 0, "Suspend started. %s", line); suspended = 1; } break; } } fclose(fp); if (suspended && (!found || break_suspend)) { writelogfile(LOG_NOTICE, 0, (break_suspend)? "Suspend break." : "Suspend ended."); suspended = 0; if (modem_was_open) if (try_openmodem()) if (initialized) if (send_startstring()) return 1; } if (!suspended) break; dt = (delaytime > 10)? 10 : (delaytime < 1)? 1 : delaytime; for (i = 0; i < dt; i++) { if (terminate == 1) { // Do not send stop string when terminating: DEVICE.stopstring[0] = 0; return 1; } t_sleep(1); } continue; } } return 0; } void devicespooler() { int workless; int quick=0; int errorcounter; int i; time_t now; time_t last_msgc_clear; time_t last_rr; time_t last_ic_purge; time_t started_sending; int continuous_sent; // 3.1.14. char *p = ""; // Load initial modemname.counter value: update_message_counter(0, DEVICE.name); *smsd_debug = 0; i = LOG_NOTICE; if (DEVICE.outgoing && !DEVICE.incoming) p = " Will only send messages."; else if (!DEVICE.outgoing && DEVICE.incoming) p = " Will only receive messages."; else if (!DEVICE.outgoing && !DEVICE.incoming) { p = " Nothing to do with a modem: sending and receiving are both disabled!"; i = LOG_CRIT; } writelogfile(i, 0, "Modem handler %i has started. PID: %i.%s", process_id, (int)getpid(), p); // 3.1beta7: This message is printed to stderr while reading setup. Now also // log it and use the alarmhandler. Setting is cleared. Later this kind of // message is only given if there is Report:yes in the message file. if (DEVICE.report == 1 && !DEVICE.incoming && DEVICE.outgoing) { writelogfile0(LOG_WARNING, 0, tb_sprintf("Cannot receive status reports because receiving is disabled on modem %s", DEVICE.name)); alarm_handler0(LOG_WARNING, tb); DEVICE.report = 0; } errorcounter=0; concatenated_id = getrand(255); if (check_suspend(0)) return; // Open serial port or return if not successful if (!try_openmodem()) return; if (DEVICE.sending_disabled == 1 && DEVICE.modem_disabled == 0) { printf("%s: Modem handler %i is in testing mode, SENDING IS DISABLED\n", process_title, process_id); writelogfile(LOG_CRIT, 0, "Modem handler %i is in testing mode, SENDING IS DISABLED", process_id); } if (DEVICE.modem_disabled == 1) { printf("%s: Modem handler %i is in testing mode, MODEM IS DISABLED\n", process_title, process_id); writelogfile(LOG_CRIT, 0, "Modem handler %i is in testing mode, MODEM IS DISABLED", process_id); DEVICE.sending_disabled = 1; } if (DEVICE.priviledged_numbers[0]) { char buffer[PATH_MAX]; sprintf(buffer, "Using priviledged_numbers: "); p = DEVICE.priviledged_numbers; while (*p) { if (p != DEVICE.priviledged_numbers) strcat(buffer, ","); strcat(buffer, p); p = strchr(p, 0) +1; } writelogfile(LOG_NOTICE, 0, buffer); } // 3.1.7: Report check memory method only if modem will read incoming messages: if (DEVICE.incoming) { p = "Using check_memory_method "; i = DEVICE.check_memory_method; switch (i) { case CM_NO_CPMS: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_NO_CPMS); break; case CM_CPMS: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CPMS); break; case CM_CMGD: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGD); break; case CM_CMGL: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGL); break; case CM_CMGL_DEL_LAST: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGL_DEL_LAST); break; case CM_CMGL_CHECK: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGL_CHECK); break; case CM_CMGL_DEL_LAST_CHECK: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGL_DEL_LAST_CHECK); break; case CM_CMGL_SIMCOM: writelogfile(LOG_NOTICE, 0, "%s%i: %s", p, i, CM_S_CMGL_SIMCOM); break; } } if (DEVICE.read_timeout != 5) writelogfile(LOG_NOTICE, 0, "Using read_timeout %i seconds.", DEVICE.read_timeout); if (DEVICE.communication_delay > 0) writelogfile(LOG_NOTICE, 0, "Using communication_delay between new commands %i milliseconds.", DEVICE.communication_delay); #ifdef DEBUGMSG printf("!! Entering endless send/receive loop\n"); #endif last_msgc_clear = time(0); last_rr = 0; last_ic_purge = 0; // 3.1.12: Allocate memory for check_memory_buffer: if (DEVICE.incoming) { check_memory_buffer_size = select_check_memory_buffer_size(); if (!(check_memory_buffer = (char *)malloc(check_memory_buffer_size))) { writelogfile0(LOG_CRIT, 1, tb_sprintf("Did not get memory for check_memory_buffer (%i). Stopping.", check_memory_buffer_size)); alarm_handler0(LOG_CRIT, tb); return; } } if (send_startstring()) return; // 3.1.1: If a modem is used for sending only, it's first initialized. // 3.1.12: Check if a modem is disabled. if (DEVICE.outgoing && !DEVICE.incoming && !DEVICE.modem_disabled) { if (initialize_modem_sending("")) { writelogfile0(LOG_CRIT, 1, tb_sprintf("Failed to initialize modem %s. Stopping.", DEVICE.name)); alarm_handler0(LOG_CRIT, tb); return; } else writelogfile(LOG_NOTICE, 0, "Waiting for messages to send..."); } // Copy device value to global value: if (DEVICE.max_continuous_sending != -1) max_continuous_sending = DEVICE.max_continuous_sending; if (max_continuous_sending < 0) max_continuous_sending = 0; flush_smart_logging(); while (terminate == 0) /* endless loop */ { workless=1; break_workless_delay = 0; workless_delay = 0; started_sending = time(0); continuous_sent = 0; while (!terminate && DEVICE.outgoing) { if (check_suspend(1)) return; if (DEVICE.message_count_clear > 0) { now = time(0); if (now >= last_msgc_clear + DEVICE.message_count_clear) { if (message_count > 0) writelogfile(LOG_NOTICE, 0, "Message limit counter cleared, it was %i.", message_count); last_msgc_clear = now; message_count = 0; } } if (DEVICE.message_limit > 0) if (message_count >= DEVICE.message_limit) break; if (!strncmp(shared_buffer, DEVICE.name, strlen(DEVICE.name))) { char msg[SIZE_SHARED_BUFFER]; strcpy(msg, shared_buffer); *shared_buffer = 0; if ((p = strchr(msg, ' '))) { writelogfile(LOG_NOTICE, 0, "Mainprocess asked to send: %s", p +1); send_admin_message(&quick, &errorcounter, p +1); } } if (!try_openmodem()) return; i = send1sms(&quick, &errorcounter); if (i > 0) { if (!message_count) last_msgc_clear = time(0); message_count++; continuous_sent++; if (DEVICE.message_limit > 0 && message_count == DEVICE.message_limit) { char msg[MAXTEXT]; writelogfile0(LOG_WARNING, 0, tb_sprintf("Message limit %i is reached.", DEVICE.message_limit)); alarm_handler0(LOG_WARNING, tb); sprintf(msg, "Smsd3: %s: Message limit %i is reached.", process_title, DEVICE.message_limit); send_admin_message(&quick, &errorcounter, msg); } if (max_continuous_sending > 0) { if (time(0) >= started_sending +max_continuous_sending) { writelogfile0(LOG_DEBUG, 0, "Max continuous sending time reached, will do other tasks and then continue."); workless = 0; if (continuous_sent) { time_t seconds; seconds = time(0) - started_sending; writelogfile(LOG_INFO, 0, "Sent %d messages in %d sec. Average time for one message: %.1f sec.", continuous_sent, seconds, (double)seconds / continuous_sent); } break; } } } else if (i != -2) // If there was a failed messsage, do not break. break; workless=0; if (DEVICE.incoming == 2) // repeat only if receiving has low priority break; if (terminate == 1) return; flush_smart_logging(); } if (terminate == 1) return; // Receive SM if (DEVICE.incoming) { if (check_suspend(1)) return; if (!try_openmodem()) return; // In case of (fatal or permanent) error return value is < 0: if (receivesms(&quick, 0) > 0) workless = 0; flush_smart_logging(); if (routed_pdu_store) { char *term; char filename[PATH_MAX]; int stored_concatenated; int statusreport; char cmdline[PATH_MAX+PATH_MAX+32]; writelogfile0(LOG_INFO, 0, "Handling saved routed messages / status reports"); p = routed_pdu_store; while (*p) { if (!(term = strchr(p, '\n'))) break; *term = 0; statusreport = received2file("", p, filename, &stored_concatenated, 0); STATISTICS->received_counter++; if (stored_concatenated == 0) { if (eventhandler[0] || DEVICE.eventhandler[0]) { char *handler = eventhandler; if (DEVICE.eventhandler[0]) handler = DEVICE.eventhandler; snprintf(cmdline, sizeof(cmdline), "%s %s %s", handler, (statusreport)? "REPORT" : "RECEIVED", filename); exec_system(cmdline, EXEC_EVENTHANDLER); } } p = term +1; } free(routed_pdu_store); routed_pdu_store = NULL; } if (terminate == 1) return; } if (DEVICE.phonecalls == 1) readphonecalls(); if (DEVICE.dev_rr_interval > 0 && DEVICE.modem_disabled == 0) { now = time(0); if (now >= last_rr +DEVICE.dev_rr_interval) { last_rr = now; if (!run_rr()) return; } } if (DEVICE.internal_combine == 1 || (DEVICE.internal_combine == -1 && internal_combine == 1)) { if ((ic_purge_hours *60 +ic_purge_minutes) > 0) { now = time(0); if (now >= last_ic_purge +ic_purge_interval) { last_ic_purge = now; do_ic_purge(); } } } if (DEVICE.incoming && keep_messages) { writelogfile0(LOG_WARNING, 0, tb_sprintf("Messages are kept, stopping.")); try_closemodem(0); kill((int)getppid(), SIGTERM); return; } break_suspend = 0; if (check_suspend(1)) return; if (workless==1) // wait a little bit if there was no SM to send or receive to save CPU usage { try_closemodem(0); // Disable quick mode if modem was workless quick=0; if (!trouble_logging_started) STATISTICS->status = 'i'; workless_delay = 1; for (i=0; i 0 && !DEVICE.modem_disabled) { now = time(0); if (now >= last_rr +DEVICE.dev_rr_interval) { last_rr = now; if (!run_rr()) return; } } t_sleep(1); } workless_delay = 0; } flush_smart_logging(); } } /* ======================================================================= Termination handler ======================================================================= */ // Stores termination request when termination signal has been received void soft_termination_handler (int signum) { (void) signum; // 3.1.7: remove warning. if (process_id==-1) { signal(SIGTERM,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGHUP,SIG_IGN); signal(SIGUSR1,SIG_IGN); // 3.1.2: Signal handlers are now silent. #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_CRIT, 0, "Smsd mainprocess received termination signal. PID: %i.", (int)getpid()); if (signum==SIGINT) printf("Received SIGINT, smsd will terminate now.\n"); #endif sendsignal2devices(SIGTERM); #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION if (*run_info) { printf("%s: Currently running: %s. Will wait until it is completed.\n", process_title, run_info); writelogfile(LOG_CRIT, 0, "Currently running: %s. Will wait until it is completed.", run_info); } #endif } else if (process_id>=0) { signal(SIGTERM,SIG_IGN); signal(SIGINT,SIG_IGN); signal(SIGHUP,SIG_IGN); signal(SIGUSR1,SIG_IGN); // process_id has always the same value like device when it is larger than -1 #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_CRIT, 0, "Modem handler %i has received termination signal, will terminate after current task has been finished. PID: %i.", process_id, (int)getpid()); if (*run_info) { printf("%s: Currently running: %s. Will wait until it is completed.\n", process_title, run_info); writelogfile(LOG_CRIT, 0, "Currently running: %s. Will wait until it is completed.", run_info); } #endif } terminate=1; } void abnormal_termination(int all) { if (process_id == -1) { if (all) sendsignal2devices(SIGTERM); remove_pid(pidfile); if (*infofile) unlink(infofile); writelogfile(LOG_CRIT, 1, "Smsd mainprocess terminated abnormally. PID: %i.", (int)getpid()); flush_smart_logging(); closelogfile(); #ifndef NOSTATS MM_destroy(); #endif exit(EXIT_FAILURE); } else if (process_id >= 0) { if (all) kill((int)getppid(), SIGTERM); writelogfile(LOG_CRIT, 1, "Modem handler %i terminated abnormally. PID: %i.", process_id, (int)getpid()); flush_smart_logging(); closelogfile(); exit(EXIT_FAILURE); } } void signal_handler(int signum) { signal(SIGCONT,SIG_IGN); signal(SIGUSR2,SIG_IGN); if (signum == SIGCONT) { if (process_id == -1) { // 3.1.2: Signal handlers are now silent. #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_CRIT, 0, "Smsd mainprocess received SIGCONT, will continue %s. PID: %i.", (workless_delay)? "immediately" : "without delays", (int)getpid()); #endif sendsignal2devices(SIGCONT); break_workless_delay = 1; } else if (process_id >= 0) { #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_INFO, 0, "Modem handler %i received SIGCONT, will continue %s. PID: %i.", process_id, (workless_delay)? "immediately" : "without delays", (int)getpid()); #endif break_workless_delay = 1; } } else if (signum == SIGUSR2) break_suspend = 1; signal(SIGCONT,signal_handler); signal(SIGUSR2,signal_handler); } /* ======================================================================= Main ======================================================================= */ int main(int argc,char** argv) { int i; struct passwd *pwd; struct group *grp; int result = 1; pid_t pid; char timestamp[81]; terminate = 0; strcpy(process_title, "smsd"); signal(SIGTERM,soft_termination_handler); signal(SIGINT,soft_termination_handler); signal(SIGHUP,soft_termination_handler); signal(SIGUSR1,soft_termination_handler); signal(SIGUSR2,signal_handler); signal(SIGCONT,signal_handler); // TODO: Some more signals should be ignored or handled too? *run_info = 0; incoming_pdu_store = NULL; outgoing_pdu_store = NULL; routed_pdu_store = NULL; getfile_err_store = NULL; check_memory_buffer = NULL; check_memory_buffer_size = 0; for (i = 0; i < NUMBER_OF_MODEMS; i++) device_pids[i] = 0; parsearguments(argc,argv); initcfg(); if (!readcfg()) exit(EXIT_FAILURE); if (do_encode_decode_arg_7bit_packed) { char buffer[512]; #ifdef USE_ICONV if (!iconv_init()) exit(EXIT_FAILURE); #endif if (do_encode_decode_arg_7bit_packed == 1) encode_7bit_packed(arg_7bit_packed, buffer, sizeof(buffer)); else decode_7bit_packed(arg_7bit_packed, buffer, sizeof(buffer)); printf("%s\n", buffer); #ifdef DEBUGMSG if (do_encode_decode_arg_7bit_packed == 1) { strcpy(arg_7bit_packed, buffer); decode_7bit_packed(arg_7bit_packed, buffer, sizeof(buffer)); printf("back:\n"); printf("%s\n", buffer); } #endif exit(0); } // Command line overrides smsd.conf settings: if (*arg_infofile) strcpy(infofile, arg_infofile); if (*arg_pidfile) strcpy(pidfile, arg_pidfile); if (*arg_logfile) strcpy(logfile, arg_logfile); if (*arg_username) strcpy(username, arg_username); if (*arg_groupname) strcpy(groupname, arg_groupname); if (arg_terminal == 1) terminal = 1; // 3.1.7: If group was given, add that to the group access list (previously was set to only group). if (getuid() == 0 && *username && strcmp(username, "root")) { if (!(pwd = getpwnam(username))) { fprintf(stderr, "User %s not found.\n", username); result = 0; } else { gid_t gt = pwd->pw_gid; if (*groupname) { if (!(grp = getgrnam(groupname))) { fprintf(stderr, "Group %s not found.\n", groupname); result = 0; } else gt = grp->gr_gid; } if (result) { if (setgid(gt)) { fprintf(stderr, "Unable to setgid to %i. errno %i\n", (int) gt, errno); result = 0; } else if (initgroups(pwd->pw_name, gt)) { fprintf(stderr, "Unable to initgroups for user id %i (%s).\n", (int) pwd->pw_uid, username); result = 0; } } if (result && setuid(pwd->pw_uid)) { fprintf(stderr, "Error setting the user id %i (%s).\n", (int) pwd->pw_uid, username); result = 0; } } } #if 0 if (result) { gid_t groupIDs[NGROUPS_MAX]; int i, count; pwd = getpwuid(getuid()); grp = getgrgid(getgid()); if (pwd && grp) fprintf(stderr, "Running as %s:%s\n", pwd->pw_name, grp->gr_name); if ((count = getgroups(NGROUPS_MAX, groupIDs)) == -1) perror("getgroups error"); else { for (i = 0; i < count; i++) { grp = getgrgid(groupIDs[i]); printf("Group ID #%d: %d %s\n", i + 1, (int) groupIDs[i], grp->gr_name); } } exit(0); } #endif if (result == 0) { if (startup_err_str) free(startup_err_str); exit(EXIT_FAILURE); } logfilehandle = openlogfile(logfile, LOG_DAEMON, loglevel); writelogfile(LOG_CRIT, 0, "Smsd v%s started.", smsd_version); // 3.1.9: Change the current working directory if (chdir("/") < 0) { char *p = "Unable to change the current working directory to \"/\"."; fprintf(stderr, "%s\n", p); writelogfile(LOG_CRIT, 0, p); exit(EXIT_FAILURE); } pwd = getpwuid(getuid()); grp = getgrgid(getgid()); if (pwd && grp) writelogfile(LOG_CRIT, 0, "Running as %s:%s.", pwd->pw_name, grp->gr_name); if (strstr(smsd_version, "beta")) { writelogfile(LOG_CRIT, 0, "# You are running a beta version of SMS Server Tools 3."); writelogfile(LOG_CRIT, 0, "# All feedback is valuable."); writelogfile(LOG_CRIT, 0, "# Please provide you feedback on SMSTools3 Community. Thank you."); } if (startup_check(read_translation()) > 0) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated."); exit(EXIT_FAILURE); } if (strcmp(datetime_format, DATETIME_DEFAULT)) { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, 0); writelogfile(LOG_INFO, 0, "Using datetime format \"%s\". It produces \"%s\".", datetime_format, timestamp); } if (strcmp(logtime_format, LOGTIME_DEFAULT)) { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); writelogfile(LOG_INFO, 0, "Using logtime format \"%s\". It produces \"%s\".", logtime_format, timestamp); } if (strcmp(date_filename_format, DATE_FILENAME_DEFAULT)) { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, date_filename_format); writelogfile(LOG_INFO, 0, "Using date_filename format \"%s\". It produces \"%s\".", date_filename_format, timestamp); } // 3.1.5: Shared memory is created after main process is running: //initstats(); //loadstats(); #ifdef TERMINAL terminal = 1; #endif #ifdef DEBUGMSG terminal = 1; #endif if (strcmp(logfile, "1") == 0 || strcmp(logfile, "2") == 0) terminal = 1; if (printstatus) terminal = 1; if (*communicate) terminal = 1; if (terminal) writelogfile(LOG_CRIT, 0, "Running in terminal mode."); else { i = fork(); if (i < 0) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated because of the fork() failure."); exit(EXIT_FAILURE); } if (i > 0) exit(EXIT_SUCCESS); i = setsid(); if (i < 0) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated because of the setsid() failure."); exit(EXIT_FAILURE); } } #ifdef USE_ICONV if (!iconv_init()) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated because of the iconv_open() failure."); exit(EXIT_FAILURE); } #endif time(&process_start_time); if (write_pid(pidfile) == 0) { fprintf(stderr, "Smsd mainprocess terminated because the pid file %s cannot be written.\n", pidfile); writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated because the pid file %s cannot be written.", pidfile); exit(EXIT_FAILURE); } if (!terminal) { close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); i = open("/dev/null", O_RDWR); dup(i); dup(i); } if (*communicate) { for (i = 0; i < NUMBER_OF_MODEMS; i++) if (strcmp(communicate, devices[i].name) == 0) break; if (i >= NUMBER_OF_MODEMS) { fprintf(stderr, "Unable to talk with %s, device not found.\n", communicate); writelogfile(LOG_CRIT, 0, "Unable to talk with %s, device not found.", communicate); // 3.1: exit(EXIT_FAILURE); } } // 3.1.5: Create shared memory now, filename gets pid of mainprocess: initstats(); loadstats(); if (!(*communicate) && keep_messages) writelogfile(LOG_CRIT, 0, "This is a test run: messages are kept and smsd will stop after reading."); // 3.1.7: if (use_linux_ps_trick) { // Make readable process name for (i = 0; i < argc; i++) memset(argv[i], 0, strlen(argv[i])); strcpy(argv[0], "smsd: MAINPROCESS"); } // Start sub-processes for each modem for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (devices[i].name[0]) { // 3.1: If talking with one modem, other threads are not started: if (*communicate) if (strcmp(communicate, devices[i].name) != 0) continue; pid = fork(); if (pid > 0) device_pids[i] = pid; if (pid == 0) { if (use_linux_ps_trick) { memset(argv[0] + sizeof("smsd: ") - 1, 0, sizeof("MAINPROCESS") - 1); strcpy(argv[0] + sizeof("smsd: ") - 1, devices[i].name); } else { // 3.1.4: int idx; for (idx = 0; idx < argc; idx++) { if (strncmp(argv[idx], "MAINPROCESS", 11) == 0) { size_t l = strlen(devices[i].name); if (l > strlen(argv[idx])) l = strlen(argv[idx]); strncpy(argv[idx], devices[i].name, l); while (argv[idx][l]) argv[idx][l++] = '_'; break; } else if (!strncmp(argv[idx], "-nMAINPROCESS", 13)) { size_t l = strlen(devices[i].name); if (l > strlen(argv[idx]) - 2) l = strlen(argv[idx] - 2); strncpy(argv[idx] + 2, devices[i].name, l); while (argv[idx][l + 2]) argv[idx][l++ + 2] = '_'; break; } } } // ------ time(&process_start_time); process_id = i; strcpy(process_title, DEVICE.name); if (strcmp(communicate, process_title) == 0) { if (DEVICE.logfile[0]) { if (DEVICE.loglevel != -1) loglevel = DEVICE.loglevel; logfilehandle = openlogfile(DEVICE.logfile, LOG_DAEMON, loglevel); } if (talk_with_modem() == 0) writelogfile(LOG_CRIT, 0, "Unable to talk with modem."); } else { modem_handle = -1; // 3.1.?: if (DEVICE.conf_identity[0] && DEVICE.modem_disabled == 0) { if (try_openmodem()) { char answer[500]; char *p; int retries = 0; writelogfile(LOG_INFO, 0, "Checking if modem in %s is ready", DEVICE.device); do { retries++; put_command("AT\r", answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (!strstr(answer, "OK") && !strstr(answer, "ERROR")) { if (terminate) break; // if Modem does not answer, try to send a PDU termination character put_command("\x1A\r", answer, sizeof(answer), 1, EXPECT_OK_ERROR); if (terminate) break; } } while (retries <= 5 && !strstr(answer,"OK")); if (!strstr(answer,"OK")) { p = get_gsm_error(answer); writelogfile0(LOG_ERR, 1, tb_sprintf("Modem is not ready to answer commands%s%s. Stopping.", (*p)? ", " : "", p)); //alarm_handler0(LOG_ERR, tb); exit(127); } put_command("AT+CIMI\r", answer, SIZE_IDENTITY, 1,EXPECT_OK_ERROR); while (*answer && !isdigitc(*answer)) strcpyo(answer, answer +1); if (strstr(answer, "ERROR")) { put_command("AT+CGSN\r", answer, SIZE_IDENTITY, 1, EXPECT_OK_ERROR); while (*answer && !isdigitc(*answer)) strcpyo(answer, answer +1); } try_closemodem(1); if (!strstr(answer, "ERROR")) { if ((p = strstr(answer, "OK"))) *p = 0; cut_ctrl(answer); cutspaces(answer); if (!strcmp(DEVICE.conf_identity, answer)) writelogfile(LOG_INFO, 0, "Checking identity: OK."); else { int n; int found = 0; writelogfile(LOG_INFO, 0, "Checking identity: No match, searching new settings."); for (n = 0; n < NUMBER_OF_MODEMS; n++) { if (devices[n].name[0]) { if (!strcmp(devices[n].conf_identity, answer)) { writelogfile(LOG_INFO, 0, "Applying new settings, continuing as %s, process_id %i --> %i.", devices[n].name, process_id, n); strcpyo(devices[n].device, DEVICE.device); process_id = n; strcpy(process_title, DEVICE.name); /* strcpy(process_title, "!"); strcat(process_title, DEVICE.name); strcpy(DEVICE.name, process_title); */ found = 1; break; } } } if (!found) { writelogfile(LOG_CRIT, 1, "Did not find new settings. Stopping."); exit(127); } } } else { writelogfile(LOG_CRIT, 1, "Cannot check identity. Stopping."); exit(127); } } } if (DEVICE.logfile[0]) { if (DEVICE.loglevel != -1) loglevel = DEVICE.loglevel; logfilehandle = openlogfile(DEVICE.logfile, LOG_DAEMON, loglevel); } devicespooler(); send_stopstring(); try_closemodem(1); statistics[i]->status = 'b'; } strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&process_start_time)); writelogfile(LOG_CRIT, 0, "Modem handler %i terminated. PID: %i, was started %s.", process_id, (int)getpid(), timestamp); flush_smart_logging(); if (DEVICE.logfile[0]) closelogfile(); exit(127); } } } // Start main program process_id=-1; mainspooler(); writelogfile(LOG_CRIT, 0, "Smsd mainprocess is awaiting the termination of all modem handlers. PID: %i.", (int)getpid()); waitpid(0,0,0); savestats(); #ifndef NOSTATS MM_destroy(); #endif remove_pid(pidfile); if (*infofile) unlink(infofile); strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&process_start_time)); writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated. PID %i, was started %s.", (int)getpid(), timestamp); flush_smart_logging(); closelogfile(); return 0; } smstools-3.1.15/src/smsd_cfg.c000077500000000000000000002731611223712572200162240ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "extras.h" #include "cfgfile.h" #include "smsd_cfg.h" #include "stats.h" #include "version.h" #include "blacklist.h" #include "whitelist.h" #include "alarm.h" #include "logging.h" int conf_ask = 0; char *yesno_error = "Invalid %s value: %s\n"; #define strcpy2(dest, value) copyvalue(dest, sizeof(dest) -1, value, name) char *tb_sprintf(char* format, ...) { va_list argp; va_start(argp, format); vsnprintf(tb, sizeof(tb), format, argp); va_end(argp); return tb; } void startuperror(char* format, ...) { va_list argp; char text[2048]; va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); // Perhaps more safe way than passing a null pointer to realloc: if (!startup_err_str) { startup_err_str = (char *)malloc(strlen(text) +1); if (startup_err_str) *startup_err_str = 0; } else startup_err_str = (char *)realloc((void *)startup_err_str, strlen(startup_err_str) + strlen(text) +1); if (startup_err_str) { strcat(startup_err_str, text); startup_err_count++; } } // Helper function for copying to prevent buffer overflows: char *copyvalue(char *dest, size_t maxlen, char *value, char *keyword) { if (strlen(value) > maxlen) { if (keyword) startuperror("Too long value for \"%s\" (%s)\n", keyword, value); else startuperror("Too long value (%s)\n", value); return NULL; } // 3.1.10: allow overlapped buffers: //snprintf(dest, maxlen -1, "%s", value); memmove(dest, value, strlen(value) +1); return dest; } int set_level(char *section, char *name, char *value) { int result = 0; char *p; int i; if ((p = malloc(strlen(value) +1))) { strcpy(p, value); for (i = 0; p[i]; i++) p[i] = toupper((int)p[i]); if ((result = atoi(value)) < 1) { if (strstr(p, "DEBUG")) result = 7; else if (strstr(p, "INFO")) result = 6; else if (strstr(p, "NOTICE")) result = 5; else if (strstr(p, "WARNING")) result = 4; else if (strstr(p, "ERROR")) result = 3; else if (strstr(p, "CRITICAL")) result = 2; } free(p); } if (result < 1) startuperror("Invalid value for %s %s: %s\n", section, name, value); return result; } void initcfg() { int i; int j; autosplit=3; receive_before_send=0; store_received_pdu=1; store_sent_pdu = 1; validity_period=255; delaytime=10; delaytime_mainprocess = -1; blocktime=60*60; blockafter = 3; errorsleeptime=10; blacklist[0]=0; whitelist[0]=0; eventhandler[0]=0; checkhandler[0]=0; alarmhandler[0]=0; logfile[0]=0; loglevel=-1; // Will be changed after reading the cfg file if stil -1 log_unmodified = 0; alarmlevel=LOG_WARNING; strcpy(d_spool,"/var/spool/sms/outgoing"); strcpy(d_incoming,"/var/spool/sms/incoming"); *d_report = 0; *d_phonecalls = 0; *d_saved = 0; strcpy(d_checked,"/var/spool/sms/checked"); d_failed[0]=0; d_sent[0]=0; d_stats[0]=0; suspend_filename[0]=0; stats_interval=60*60; status_interval=1; // 3.1.5: If shared memory is not in use, stats is not useable (all zero): #ifndef NOSTATS stats_no_zeroes=0; #else stats_no_zeroes=1; #endif decode_unicode_text=0; internal_combine = 1; internal_combine_binary = -1; keep_filename = 1; store_original_filename = 1; date_filename = 0; regular_run[0] = 0; regular_run_interval = 5 * 60; admin_to[0] = 0; filename_preview = 0; incoming_utf8 = 0; outgoing_utf8 = 1; log_charconv = 0; log_read_from_modem = 0; log_single_lines = 1; executable_check = 1; keep_messages = 0; *priviledged_numbers = 0; ic_purge_hours = 24; ic_purge_minutes = 0; ic_purge_read = 1; ic_purge_interval = 30; strcpy(shell, "/bin/sh"); *adminmessage_device = 0; smart_logging = 0; status_signal_quality = 1; status_include_counters = 1; hangup_incoming_call = 0; max_continuous_sending = 5 *60; voicecall_hangup_ath = 0; trust_outgoing = 0; ignore_outgoing_priority = 0; spool_directory_order = 0; trim_text = 1; message_count = 0; username[0] = 0; groupname[0] = 0; strcpy(infofile, "/var/run/smsd.working"); strcpy(pidfile, "/var/run/smsd.pid"); terminal = 0; os_cygwin = 0; *international_prefixes = 0; *national_prefixes = 0; for (i = 0; i < NUMBER_OF_MODEMS; i++) { queues[i].name[0]=0; queues[i].directory[0]=0; for (j=0; j 0) tmp[n] = 0; else *tmp = 0; cut_ctrl(tmp); cutspaces(tmp); if (!(*tmp)) strcpy(tmp, "1"); if (strcmp("0", tmp) == 0) { printf("Exiting...\n"); fflush(stdout); exit(0); } i = atoi(tmp); if (i < 1 || i > m) { printf("Invalid selection.\n"); fflush(stdout); sleep(1); continue; } if (i != m) getsubparam_delim(value, i, tmp, sizeof(tmp), '|'); else { printf("Enter value: "); fflush(stdout); if ((n = read(STDIN_FILENO, tmp, sizeof(tmp) -1)) > 0) tmp[n] = 0; else *tmp = 0; cut_ctrl(tmp); cutspaces(tmp); if (!(*tmp)) { printf("Empty value is not enough.\n"); fflush(stdout); sleep(1); continue; } } strcpy(value, tmp); break; } } } return value; } int readcfg() { FILE* File; char devices_list[4096]; char name[64]; char value[4096]; char tmp[4096]; char device_name[32]; int result; int j, q; char *p; int newdevice; int read_default; #define NEWDEVICE devices[newdevice] // 3.1.7: no need to change devices list in smsd.conf when communicating: // *devices_list = 0; strcpy(devices_list, communicate); *device_name = 0; File=fopen(configfile,"r"); if (File) { /* read global parameter */ // 3.1beta7: all errors are reported, not just the first one. while ((result = my_getline(File, name, sizeof(name), value, sizeof(value))) != 0) { if (result == -1) { startuperror("Syntax error: %s\n", value); continue; } if (strcasecmp(name,"devices")==0) strcpy2(devices_list, ask_value(0, name, value)); else if (strcasecmp(name,"spool")==0) strcpy2(d_spool, ask_value(0, name, value)); else if (strcasecmp(name,"outgoing")==0) strcpy2(d_spool, ask_value(0, name, value)); else if (strcasecmp(name,"stats")==0) strcpy2(d_stats, ask_value(0, name, value)); else if (strcasecmp(name,"suspend")==0) strcpy2(suspend_filename, ask_value(0, name, value)); else if (strcasecmp(name,"failed")==0) strcpy2(d_failed, ask_value(0, name, value)); else if (strcasecmp(name,"incoming")==0) strcpy2(d_incoming, ask_value(0, name, value)); else if (strcasecmp(name,"report")==0) strcpy2(d_report, ask_value(0, name, value)); else if (strcasecmp(name,"phonecalls")==0) strcpy2(d_phonecalls, ask_value(0, name, value)); else if (strcasecmp(name,"saved")==0) strcpy2(d_saved, ask_value(0, name, value)); else if (strcasecmp(name,"checked")==0) strcpy2(d_checked, ask_value(0, name, value)); else if (strcasecmp(name,"sent")==0) strcpy2(d_sent, ask_value(0, name, value)); else if (strcasecmp(name,"mypath")==0) // Removed in > 3.0.1 because this is not used. Setting is accepted because of backward compatibility. ; else if (strcasecmp(name,"delaytime")==0) delaytime=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"delaytime_mainprocess")==0) delaytime_mainprocess=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"blocktime")==0) blocktime=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"blockafter")==0) blockafter=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"stats_interval")==0) stats_interval=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"status_interval")==0) status_interval=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"stats_no_zeroes")==0) { if ((stats_no_zeroes = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"errorsleeptime")==0) errorsleeptime=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"eventhandler")==0) strcpy2(eventhandler, ask_value(0, name, value)); else if (strcasecmp(name,"checkhandler")==0) strcpy2(checkhandler, ask_value(0, name, value)); else if (strcasecmp(name,"alarmhandler")==0) strcpy2(alarmhandler, ask_value(0, name, value)); else if (strcasecmp(name,"blacklist")==0) strcpy2(blacklist, ask_value(0, name, value)); else if (strcasecmp(name,"whitelist")==0) strcpy2(whitelist, ask_value(0, name, value)); else if (strcasecmp(name,"logfile")==0) strcpy2(logfile, ask_value(0, name, value)); else if (strcasecmp(name,"loglevel")==0) loglevel = set_level("global", name, ask_value(0, name, value)); else if (strcasecmp(name,"log_unmodified")==0) { if ((log_unmodified = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"alarmlevel")==0) alarmlevel = set_level("global", name, ask_value(0, name, value)); else if (strcasecmp(name,"autosplit")==0) autosplit=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"receive_before_send")==0) { if ((receive_before_send = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"store_received_pdu")==0) store_received_pdu=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"store_sent_pdu")==0) store_sent_pdu=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"validity")==0) { if ((validity_period = parse_validity(ask_value(0, name, value), -1)) == -1) startuperror("Invalid validity period: %s\n", value); } else if (strcasecmp(name,"decode_unicode_text")==0) { if ((decode_unicode_text = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"internal_combine")==0) { if ((internal_combine = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"internal_combine_binary")==0) { if ((internal_combine_binary = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"keep_filename")==0) { if ((keep_filename = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"store_original_filename")==0) { if ((store_original_filename = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"date_filename")==0) date_filename = atoi(ask_value(0, name, value)); else if (strcasecmp(name,"regular_run")==0) strcpy2(regular_run, ask_value(0, name, value)); else if (strcasecmp(name,"regular_run_interval")==0) { if ((regular_run_interval = atoi(ask_value(0, name, value))) <= 0) startuperror("Invalid global regular_run_interval: %s\n", value); } else if (strcasecmp(name,"admin_to")==0) strcpy2(admin_to, ask_value(0, name, value)); else if (strcasecmp(name,"filename_preview")==0) filename_preview=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"incoming_utf8")==0) { if ((incoming_utf8 = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"outgoing_utf8")==0) { if ((outgoing_utf8 = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"log_charconv")==0) { if ((log_charconv = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"log_read_from_modem")==0) { if ((log_read_from_modem = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"log_single_lines")==0) { if ((log_single_lines = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"executable_check")==0) { if ((executable_check = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"keep_messages")==0) { if ((keep_messages = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"user")==0) strcpy2(username, ask_value(0, name, value)); else if (strcasecmp(name,"group")==0) strcpy2(groupname, ask_value(0, name, value)); else if (strcasecmp(name,"infofile")==0) strcpy2(infofile, ask_value(0, name, value)); else if (strcasecmp(name,"pidfile")==0) strcpy2(pidfile, ask_value(0, name, value)); else if (strcasecmp(name,"terminal")==0) { if ((terminal = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"os_cygwin")==0) { if ((os_cygwin = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"language_file")==0) strcpy2(language_file, ask_value(0, name, value)); else if (strcasecmp(name,"datetime")==0) strcpy2(datetime_format, ask_value(0, name, value)); else if (strcasecmp(name,"datetime_format")==0) strcpy2(datetime_format, ask_value(0, name, value)); else if (strcasecmp(name,"logtime_format")==0) strcpy2(logtime_format, ask_value(0, name, value)); else if (strcasecmp(name,"date_filename_format")==0) strcpy2(date_filename_format, ask_value(0, name, value)); else if (strcasecmp(name,"international_prefixes")==0 || strcasecmp(name,"national_prefixes")==0) { ask_value(0, name, value); p = value; while (*p) { if (is_blank(*p)) strcpyo(p, p +1); else p++; } while ((p = strstr(value, ",,"))) strcpyo(p, p +1); if (strcasecmp(name,"international_prefixes")==0) p = international_prefixes; else p = national_prefixes; sprintf(p, "%s%c", value, 0); while (*p) { if (*p == ',') *p = 0; p++; } } else if (strcasecmp(name,"priviledged_numbers")==0) { ask_value(0, name, value); for (j = 1; ; j++) { if (j >= 25) // It's from A to Y and Z is last in sorting... { startuperror("Too many global priviledged numbers.\n"); break; } if (getsubparam(value, j, tmp, sizeof(tmp))) { // If not empty, buffer is terminated with double-zero. p = priviledged_numbers; while (*p) p = strchr(p, 0) +1; if ((ssize_t)strlen(tmp) <= SIZE_PRIVILEDGED_NUMBERS -2 -(p - priviledged_numbers)) { strcpy(p, tmp); *(p +strlen(tmp) +1) = 0; } else startuperror("Not enough space for global priviledged incoming numbers.\n"); } else break; } } else if (strcasecmp(name,"enable_smsd_debug")==0) { if ((enable_smsd_debug = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (!strcasecmp(name, "ignore_exec_output")) { if ((ignore_exec_output = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (!strcasecmp(name, "umask")) { conf_umask = (mode_t) strtol(ask_value(0, name, value), NULL, 0); if (errno == EINVAL) startuperror("Invalid value for umask: %s\n", value); } else if (strcasecmp(name,"ic_purge_hours")==0) ic_purge_hours=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"ic_purge_minutes")==0) ic_purge_minutes=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"ic_purge_read")==0) { if ((ic_purge_read = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"ic_purge_interval")==0) ic_purge_interval=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"shell")==0) strcpy2(shell, ask_value(0, name, value)); else if (strcasecmp(name,"adminmessage_device")==0) strcpy2(adminmessage_device, ask_value(0, name, value)); else if (strcasecmp(name,"smart_logging")==0) { if ((smart_logging = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"status_signal_quality")==0) { if ((status_signal_quality = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"status_include_counters")==0) { if ((status_include_counters = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"trust_outgoing")==0) { if ((trust_outgoing = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"ignore_outgoing_priority")==0) { if ((ignore_outgoing_priority = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"spool_directory_order")==0) { if ((spool_directory_order = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"trim_text")==0) { if ((trim_text = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"hangup_incoming_call")==0) { if ((hangup_incoming_call = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"max_continuous_sending")==0) max_continuous_sending=atoi(ask_value(0, name, value)); else if (strcasecmp(name,"voicecall_hangup_ath")==0) { if ((voicecall_hangup_ath = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"use_linux_ps_trick")==0) { if ((use_linux_ps_trick = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"logtime_us")==0) { if ((logtime_us = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"logtime_ms")==0) { if ((logtime_ms = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"shell_test")==0) { if ((shell_test = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); } else startuperror("Unknown global setting: %s\n", name); } // 3.1.14: if (*logtime_format == 0) { snprintf(logtime_format, sizeof(logtime_format), "%s", LOGTIME_DEFAULT); if (strlen(logtime_format) < sizeof(logtime_format) - 7) { if (logtime_us) strcat(logtime_format, ".timeus"); else if (logtime_ms) strcat(logtime_format, ".timems"); } } /* read queue-settings */ if (gotosection(File, "queues") || gotosection(File, "queue")) { // 3.1beta3, 3.0.9: inform if there is too many queues defined. for (q = 0; ; q++) { if ((result = my_getline(File, name, sizeof(name), value, sizeof(value))) != 1) break; if (q >= NUMBER_OF_MODEMS) { startuperror("Too many queues defined. Increase NUMBER_OF_MODEMS value in src/Makefile.\n"); break; } strcpy2(queues[q].name, name); strcpy2(queues[q].directory, value); } if (result==-1) startuperror("Syntax error: %s\n",value); } /* read provider-settings */ if (gotosection(File, "providers") || gotosection(File, "provider")) { // TODO: better syntax checking for config file. result=my_getline(File,name,sizeof(name),value,sizeof(value)); while (result==1) { q = getqueue(name,tmp); if (q >= 0) { // 3.1beta3, 3.0.9: inform if there is too many parameters. for (j = 1; ; j++) { if (getsubparam(value, j, tmp, sizeof(tmp))) { if (j > NUMS) { startuperror("Too many parameters for provider %s.\n", name); break; } // 3.1beta4, 3.0.9: remove whitespaces: p = tmp; while (*p) { if (is_blank(*p)) strcpyo(p, p +1); else p++; } #ifdef DEBUGMSG printf("!! queues[%i].numbers[%i]=%s\n", q, j-1, tmp); #endif strcpy2(queues[q].numbers[j-1], tmp); } else break; } } else startuperror("Missing queue for %s.\n",name); result=my_getline(File,name,sizeof(name),value,sizeof(value)); } if (result==-1) startuperror("Syntax error: %s\n",value); } // 3.1.12: if ((p = strchr(devices_list, '*')) && strchr(devices_list, '-')) { int i, max; *p = 0; strcpy(tmp, devices_list); i = atoi(p +1); max = atoi(strstr(p +1, "-") + 1); *devices_list = 0; while (i <= max) { sprintf(value, "%s%i,", tmp, i); if (sizeof(devices_list) - strlen(devices_list) <= strlen(value)) break; strcpy(strchr(devices_list, 0), value); i++; } if (*devices_list) devices_list[strlen(devices_list) - 1] = 0; } // If devices_list is empty, getsubparam still returns 1 while getting the first name. // Now this list is checked with it's own error message: if (devices_list[0] == 0) startuperror("There are no devices specified.\n"); else { // 3.1.5: check if too many devices are specified: result = 0; while (getsubparam(devices_list, result +1, device_name, sizeof(device_name))) result++; if (result > NUMBER_OF_MODEMS) { startuperror("Too many devices specified. Increase NUMBER_OF_MODEMS value in src/Makefile.\n"); result = 0; } } if (result) { /* read device-settings */ for (newdevice = 0; newdevice < NUMBER_OF_MODEMS; newdevice++) { if (getsubparam(devices_list, newdevice +1, device_name, sizeof(device_name))) { // 3.1beta7: Check device name, it's also used to create a filename: for (j = 0; device_name[j] != 0; j++) if (!isalnumc(device_name[j]) && !strchr("_-.", device_name[j])) break; if (device_name[j] != 0) startuperror("Invalid characters in device name: \n", device_name); else if (!strcmp(device_name, "default")) startuperror("Device name cannot be \"default\"."); else if (!strcmp(device_name, "ALL")) startuperror("Device name cannot be \"ALL\"."); //else if (!gotosection(File, device_name)) // startuperror("Could not find device [%s].\n", device_name); else { // 3.1.12: modem section is no more mandatory. int device_found = 1; for (read_default = 1; read_default >= 0; read_default--) { if (read_default) { if (!gotosection(File, "default")) continue; strcpy2(NEWDEVICE.name, "default"); } else { if (!gotosection(File, device_name)) device_found = 0; strcpy2(NEWDEVICE.name, device_name); } // 3.1beta7: all errors are reported, not just the first one. while (device_found && (result = my_getline(File, name, sizeof(name), value, sizeof(value))) != 0) { if (result == -1) { startuperror("Syntax error: %s\n", value); continue; } if (strcasecmp(name,"number")==0) strcpy2(NEWDEVICE.number, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"device")==0) { ask_value(NEWDEVICE.name, name, value); // 3.1.12: special devicename: if ((p = strstr(value, "modemname"))) { if (strlen(value) -9 +strlen(device_name) < sizeof(tmp)) { sprintf(tmp, "%.*s%s%s", (int)(p -value), value, device_name, p +9); strcpy(value, tmp); } } strcpy2(NEWDEVICE.device, value); } else if (strcasecmp(name,"device_open_retries")==0) NEWDEVICE.device_open_retries=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"device_open_errorsleeptime")==0) NEWDEVICE.device_open_errorsleeptime=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"device_open_alarm_after")==0) NEWDEVICE.device_open_alarm_after=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"queues")==0) { ask_value(NEWDEVICE.name, name, value); // 3.1.5: special queuename: if ((p = strstr(value, "modemname"))) { if (strlen(value) -9 +strlen(device_name) < sizeof(tmp)) { sprintf(tmp, "%.*s%s%s", (int)(p -value), value, device_name, p +9); strcpy(value, tmp); } } // Inform if there are too many queues defined. for (j = 1; ; j++) { if (getsubparam(value, j, tmp, sizeof(tmp))) { if (j > NUMBER_OF_MODEMS) { startuperror("Too many queues defined for device %s. Increase NUMBER_OF_MODEMS value in src/Makefile.\n", NEWDEVICE.name); break; } strcpy2(NEWDEVICE.queues[j -1], tmp); // Check if given queue is available. if (getqueue(NEWDEVICE.queues[j -1], tmp) < 0) startuperror("Queue %s not found for device %s.\n", NEWDEVICE.queues[j -1], device_name); } else break; } } else if (strcasecmp(name,"incoming")==0) { ask_value(NEWDEVICE.name, name, value); if (strcasecmp(value,"high") ==0) NEWDEVICE.incoming=2; else { NEWDEVICE.incoming=atoi(value); if (NEWDEVICE.incoming==0) // For backward compatibility to older version with boolean value { if ((NEWDEVICE.incoming = yesno_check(value)) == -1) startuperror(yesno_error, name, value); } } } else if (strcasecmp(name,"outgoing")==0) { if ((NEWDEVICE.outgoing = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"cs_convert")==0) { if ((NEWDEVICE.cs_convert = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"pin")==0) strcpy2(NEWDEVICE.pin, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"pinsleeptime")==0) NEWDEVICE.pinsleeptime=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"mode")==0) { ask_value(NEWDEVICE.name, name, value); if (strcasecmp(value,"ascii")==0) startuperror("Ascii mode is not supported anymore.\n"); if ((strcasecmp(value,"old")!=0) && (strcasecmp(value,"new")!=0)) startuperror("Invalid mode=%s.\n",value); else strcpy2(NEWDEVICE.mode,value); } else if (strcasecmp(name,"smsc")==0) strcpy2(NEWDEVICE.smsc, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"baudrate")==0) NEWDEVICE.baudrate=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"send_delay")==0) NEWDEVICE.send_delay=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"send_handshake_select")==0) { if ((NEWDEVICE.send_handshake_select = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"memory_start")==0) NEWDEVICE.read_memory_start=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"init")==0) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.initstring, sizeof(NEWDEVICE.initstring) -2, value, name); strcat(NEWDEVICE.initstring,"\r"); } else if (strcasecmp(name,"init1")==0) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.initstring, sizeof(NEWDEVICE.initstring) -2, value, name); strcat(NEWDEVICE.initstring,"\r"); } else if (strcasecmp(name,"init2")==0) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.initstring2, sizeof(NEWDEVICE.initstring2) -2, value, name); strcat(NEWDEVICE.initstring2,"\r"); } else if (strcasecmp(name,"start")==0) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.startstring, sizeof(NEWDEVICE.startstring) -2, value, name); strcat(NEWDEVICE.startstring,"\r"); } else if (strcasecmp(name,"startsleeptime")==0) NEWDEVICE.startsleeptime=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"stop")==0) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.stopstring, sizeof(NEWDEVICE.stopstring) -2, value, name); strcat(NEWDEVICE.stopstring,"\r"); } else if (strcasecmp(name,"eventhandler")==0) strcpy2(NEWDEVICE.eventhandler, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"eventhandler_ussd")==0) strcpy2(NEWDEVICE.eventhandler_ussd, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"ussd_convert")==0) NEWDEVICE.ussd_convert=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"report")==0) { if ((NEWDEVICE.report = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"phonecalls")==0) { ask_value(NEWDEVICE.name, name, value); if (strcasecmp(value,"clip") ==0) NEWDEVICE.phonecalls=2; else { NEWDEVICE.phonecalls=atoi(value); if (NEWDEVICE.phonecalls==0) { if ((NEWDEVICE.phonecalls = yesno_check(value)) == -1) startuperror(yesno_error, name, value); } } } else if (strcasecmp(name,"phonecalls_purge")==0) strcpy2(NEWDEVICE.phonecalls_purge, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"phonecalls_error_max")==0) NEWDEVICE.phonecalls_error_max=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"rtscts")==0) { if ((NEWDEVICE.rtscts = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"primary_memory")==0) { ask_value(NEWDEVICE.name, name, value); while ((p = strchr(value,'\"'))) strcpyo(p, p+1); strcpy2(NEWDEVICE.primary_memory,value); } else if (strcasecmp(name,"secondary_memory")==0) { ask_value(NEWDEVICE.name, name, value); while ((p = strchr(value,'\"'))) strcpyo(p, p+1); strcpy2(NEWDEVICE.secondary_memory,value); } else if (strcasecmp(name,"secondary_memory_max")==0) NEWDEVICE.secondary_memory_max=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"pdu_from_file")==0) strcpy2(NEWDEVICE.pdu_from_file, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"sending_disabled")==0) { if ((NEWDEVICE.sending_disabled = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"modem_disabled")==0) { if ((NEWDEVICE.modem_disabled = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"decode_unicode_text")==0) { if ((NEWDEVICE.decode_unicode_text = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"internal_combine")==0) { if ((NEWDEVICE.internal_combine = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"internal_combine_binary")==0) { if ((NEWDEVICE.internal_combine_binary = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"pre_init")==0) { if ((NEWDEVICE.pre_init = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"admin_to")==0) strcpy2(NEWDEVICE.admin_to, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"message_limit")==0) NEWDEVICE.message_limit=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"message_count_clear")==0) NEWDEVICE.message_count_clear=atoi(ask_value(NEWDEVICE.name, name, value)) *60; else if (strcasecmp(name,"keep_open")==0) { if ((NEWDEVICE.keep_open = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"regular_run")==0) strcpy2(NEWDEVICE.dev_rr, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"regular_run_post_run")==0) strcpy2(NEWDEVICE.dev_rr_post_run, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"regular_run_interval")==0) { if ((NEWDEVICE.dev_rr_interval = atoi(ask_value(NEWDEVICE.name, name, value))) <= 0) startuperror("Invalid regular_run_interval for %s: %s\n", NEWDEVICE.name, value); } else if (strcasecmp(name,"regular_run_cmdfile")==0) strcpy2(NEWDEVICE.dev_rr_cmdfile, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"regular_run_cmd")==0) { ask_value(NEWDEVICE.name, name, value); // If not empty, buffer is terminated with double-zero. if (*value) { p = NEWDEVICE.dev_rr_cmd; while (*p) p = strchr(p, 0) +1; if ((ssize_t)strlen(value) <= SIZE_RR_CMD -2 -(p - NEWDEVICE.dev_rr_cmd)) { strcpy(p, value); *(p +strlen(value) +1) = 0; } else startuperror("Not enough space for %s regular_run_cmd value: %s\n", NEWDEVICE.name, value); } } else if (strcasecmp(name,"regular_run_logfile")==0) strcpy2(NEWDEVICE.dev_rr_logfile, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"regular_run_loglevel")==0) { ask_value(NEWDEVICE.name, name, value); NEWDEVICE.dev_rr_loglevel = set_level(NEWDEVICE.name, name, value); } else if (strcasecmp(name,"regular_run_statfile")==0) strcpy2(NEWDEVICE.dev_rr_statfile, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"logfile")==0) { ask_value(NEWDEVICE.name, name, value); // 3.1.12: special filename: if ((p = strstr(value, "modemname"))) { if (strlen(value) -9 +strlen(device_name) < sizeof(tmp)) { sprintf(tmp, "%.*s%s%s", (int)(p -value), value, device_name, p +9); strcpy(value, tmp); } } strcpy2(NEWDEVICE.logfile, value); } else if (strcasecmp(name,"loglevel")==0) NEWDEVICE.loglevel = set_level(NEWDEVICE.name, name, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"check_network")==0) { // For backward compatibility to older version with boolean value ask_value(NEWDEVICE.name, name, value); if ((NEWDEVICE.check_network = yesno_check(value)) == -1) NEWDEVICE.check_network=atoi(value); } else if (strcasecmp(name,"messageids")==0) NEWDEVICE.messageids = atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"voicecall_vts_list")==0) { if ((NEWDEVICE.voicecall_vts_list = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"identity")==0) strcpy2(NEWDEVICE.conf_identity, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"check_memory_method")==0) NEWDEVICE.check_memory_method = atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"cmgl_value")==0) strcpy2(NEWDEVICE.cmgl_value, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"priviledged_numbers")==0) { ask_value(NEWDEVICE.name, name, value); for (j = 1; ; j++) { if (j >= 25) { startuperror("Too many priviledged numbers in device %s.\n", NEWDEVICE.name); break; } if (getsubparam(value, j, tmp, sizeof(tmp))) { // If not empty, buffer is terminated with double-zero. p = NEWDEVICE.priviledged_numbers; while (*p) p = strchr(p, 0) +1; if ((ssize_t)strlen(tmp) <= SIZE_PRIVILEDGED_NUMBERS -2 -(p - NEWDEVICE.priviledged_numbers)) { strcpy(p, tmp); *(p +strlen(tmp) +1) = 0; } else startuperror("Not enough space for priviledged incoming numbers in device %s.\n", NEWDEVICE.name); } else break; } } else if (strcasecmp(name,"read_timeout")==0) NEWDEVICE.read_timeout=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"ms_purge_hours")==0) NEWDEVICE.ms_purge_hours=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"ms_purge_minutes")==0) NEWDEVICE.ms_purge_minutes=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"ms_purge_read")==0) { if ((NEWDEVICE.ms_purge_read = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"detect_message_routing")==0) { if ((NEWDEVICE.detect_message_routing = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"detect_unexpected_input")==0) { if ((NEWDEVICE.detect_unexpected_input = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"unexpected_input_is_trouble")==0) { if ((NEWDEVICE.unexpected_input_is_trouble = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"adminmessage_limit")==0) NEWDEVICE.adminmessage_limit=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"adminmessage_count_clear")==0) NEWDEVICE.adminmessage_count_clear=atoi(ask_value(NEWDEVICE.name, name, value)) *60; else if (strcasecmp(name,"voicecall_ignore_modem_response")==0) { if ((NEWDEVICE.voicecall_ignore_modem_response = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"voicecall_hangup_ath")==0) { if ((NEWDEVICE.voicecall_hangup_ath = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"voicecall_vts_quotation_marks")==0) { if ((NEWDEVICE.voicecall_vts_quotation_marks = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"voicecall_cpas")==0) { if ((NEWDEVICE.voicecall_cpas = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"voicecall_clcc")==0) { if ((NEWDEVICE.voicecall_clcc = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"status_signal_quality")==0) { if ((NEWDEVICE.status_signal_quality = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"status_include_counters")==0) { if ((NEWDEVICE.status_include_counters = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"communication_delay")==0) NEWDEVICE.communication_delay=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"hangup_incoming_call")==0) { if ((NEWDEVICE.hangup_incoming_call = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"max_continuous_sending")==0) NEWDEVICE.max_continuous_sending=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"socket_connection_retries")==0) NEWDEVICE.socket_connection_retries=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"socket_connection_alarm_after")==0) NEWDEVICE.socket_connection_alarm_after=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"socket_connection_errorsleeptime")==0) NEWDEVICE.socket_connection_errorsleeptime=atoi(ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"report_device_details")==0) { if ((NEWDEVICE.report_device_details = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"using_routed_status_report")==0) { if ((NEWDEVICE.using_routed_status_report = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"routed_status_report_cnma")==0) { if ((NEWDEVICE.routed_status_report_cnma = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"needs_wakeup_at")==0) { if ((NEWDEVICE.needs_wakeup_at = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"keep_messages")==0) { if ((NEWDEVICE.keep_messages = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"trust_spool")==0) { if ((NEWDEVICE.trust_spool = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"smsc_pdu")==0) { if ((NEWDEVICE.smsc_pdu = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"telnet_login")==0) strcpy2(NEWDEVICE.telnet_login, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"telnet_login_prompt")==0) strcpy2(NEWDEVICE.telnet_login_prompt, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"telnet_login_prompt_ignore")==0) strcpy2(NEWDEVICE.telnet_login_prompt_ignore, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"telnet_password")==0) strcpy2(NEWDEVICE.telnet_password, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"telnet_password_prompt")==0) strcpy2(NEWDEVICE.telnet_password_prompt, ask_value(NEWDEVICE.name, name, value)); else if (strcasecmp(name,"signal_quality_ber_ignore")==0) { if ((NEWDEVICE.signal_quality_ber_ignore = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"verify_pdu")==0) { if ((NEWDEVICE.verify_pdu = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); } else if (strcasecmp(name,"loglevel_lac_ci")==0) NEWDEVICE.loglevel_lac_ci=set_level(NEWDEVICE.name, name, value); else if (strcasecmp(name,"log_not_registered_after")==0) NEWDEVICE.log_not_registered_after=atoi(ask_value(NEWDEVICE.name, name, value)); else startuperror("Unknown setting for modem %s: %s\n", NEWDEVICE.name, name); } } } } else break; } } fclose(File); set_alarmhandler(alarmhandler, alarmlevel); // if loglevel is unset, then set it depending on if we use syslog or a logfile if (loglevel==-1) { if (logfile[0] == 0 || enable_smsd_debug) loglevel=LOG_DEBUG; else loglevel=LOG_WARNING; } if (conf_ask > 1) { printf("Smsd will now try to start.\n"); fflush(stdout); } } else { fprintf(stderr,"Cannot open config file for read.\n"); return 0; } return 1; } int getqueue(char* name, char* directory) // Name can also be a phone number { int i; int j; #ifdef DEBUGMSG printf("!! getqueue(name=%s,... )\n",name); #endif // If no queues are defined, then directory is always d_checked if (queues[0].name[0]==0) { strcpy(directory,d_checked); #ifdef DEBUGMSG printf("!! Returns -2, no queues, directory=%s\n",directory); #endif return -2; } // Short number is also accepted as a number: // 3.1beta4: A number can probably start with # or *: //if (is_number(name) || (*name == 's' && is_number(name +1))) if (isdigitc(*name) || (*name && strchr("#*", *name)) || (strlen(name) > 1 && *name == 's' && isdigitc(*(name +1)))) { #ifdef DEBUGMSG printf("!! Searching by number\n"); #endif i=0; while (queues[i].name[0] && (i < NUMBER_OF_MODEMS)) { j=0; while (queues[i].numbers[j][0] && (j0); } int check_directory(char *dir) { int result = 0; char fname[PATH_MAX]; int fd; DIR* dirdata; FILE *fp; if (dir && *dir) { if (!(dirdata = opendir(dir))) result = 2; else { closedir(dirdata); strcpy(fname, dir); if (fname[strlen(fname) -1] != '/') strcat(fname, "/"); strcat(fname, "test.XXXXXX"); if ((fd = mkstemp(fname)) == -1) result = 3; else { close(fd); unlink(fname); // 3.1.5: mkstemp creates with 600, check if with 644 or 666 can be created: if (!(fp = fopen(fname, "w"))) result = 4; else { fclose(fp); unlink(fname); } } } } else result = 1; return result; } void remove_lockfiles(char *dir) { DIR* dirdata; struct dirent* ent; struct stat statbuf; char tmpname[PATH_MAX]; if (dir && *dir) { if ((dirdata = opendir(dir))) { while ((ent = readdir(dirdata))) { sprintf(tmpname, "%s%s%s", dir, (dir[strlen(dir) -1] != '/')? "/" : "", ent->d_name); stat(tmpname, &statbuf); if (S_ISDIR(statbuf.st_mode) == 0) if (strcmp(tmpname +strlen(tmpname) -5, ".LOCK") == 0) if (unlink(tmpname) != 0) startuperror("Cannot unlink file %s: %s\n", tmpname, strerror(errno)); } closedir(dirdata); } } } void wrlogfile(int *result, char* format, ...) { va_list argp; char text[2048]; va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); fprintf(stderr, "%s\n", text); writelogfile(LOG_CRIT, 0, "%s", text); if (result) (*result)++; } int startup_check(int result) { // result has initial value and total number of problems is returned. char *msg_dir = "%s directory %s cannot be opened."; char *msg_file = "%s directory %s is not writable."; int i; int x; int y; FILE *fp; char tmp[PATH_MAX]; char fname[PATH_MAX]; char *p; char *p2; int d_incoming_ok = 0; int d_saved_ok = 0; struct stat statbuf; char timestamp[81]; time_t now; if (startup_err_str) { wrlogfile(NULL, "There was %i error%s while reading the config file:", startup_err_count, (startup_err_count > 1)? "s" : ""); p = startup_err_str; while (p && *p) { if ((p2 = strchr(p, '\n'))) *p2 = 0; wrlogfile(&result, "- %s", p); p = (p2)? p2 +1 : NULL; } free(startup_err_str); startup_err_str = NULL; startup_err_count = 0; } // After this a lockfile errors are collected to startup_err_str. if ((i = check_directory(d_spool)) == 1) wrlogfile(&result, "Spool (outgoing) directory definition is missing."); else if (i > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Spool", d_spool); else if (i == 0) remove_lockfiles(d_spool); if ((i = check_directory(d_stats)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Stats", d_stats); if ((i = check_directory(d_failed)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Failed", d_failed); else if (i == 0) remove_lockfiles(d_failed); if ((i = check_directory(d_incoming)) == 1) wrlogfile(&result, "Incoming directory definition is missing."); else if (i > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Incoming", d_incoming); else if (i == 0) { remove_lockfiles(d_incoming); d_incoming_ok = 1; } if (queues[0].name[0] == 0) { if ((i = check_directory(d_checked)) == 1) wrlogfile(&result, "Checked directory definition is missing."); else if (i > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Checked", d_checked); else if (i == 0) remove_lockfiles(d_checked); } if ((i = check_directory(d_sent)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Sent", d_sent); else if (i == 0) remove_lockfiles(d_sent); if ((i = check_directory(d_report)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Report", d_report); else if (i == 0) remove_lockfiles(d_report); if ((i = check_directory(d_phonecalls)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Phonecalls", d_phonecalls); else if (i == 0) remove_lockfiles(d_phonecalls); if ((i = check_directory(d_saved)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Saved", d_saved); else if (i == 0) { remove_lockfiles(d_saved); d_saved_ok = 1; } x = 0; while (queues[x].name[0] && (x < NUMBER_OF_MODEMS)) { if ((i = check_directory(queues[x].directory)) == 1) wrlogfile(&result, "Queue %s directory definition is missing.", queues[x].name); else if (i > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "Queue", queues[x].directory); else if (i == 0) { remove_lockfiles(queues[x].directory); // 3.1.5: Check if same (similar typed) directory is used for more than one queue: i = 0; while (queues[i].name[0] && (i < NUMBER_OF_MODEMS)) { if (i != x) if (!strcmp(queues[i].directory, queues[x].directory)) wrlogfile(&result, "Queue %s has same directory with queue %s.", queues[i].name, queues[x].name); i++; } } // Should also check that all queue names have a provider setting too: //if (queues[x].numbers[0][0] == 0) // wrlogfile(&result, "Queue %s has no provider number(s) defined.", queues[x].name); // 3.1.7: If providers are not set for the queue, use "catch-all". if (queues[x].numbers[0][0] == 0) { for (y = 1; ; y++) { if (getsubparam("0,1,2,3,4,5,6,7,8,9,s", y, tmp, sizeof(tmp))) { if (y > NUMS) { wrlogfile(&result, "A definition NUM is too small."); break; } snprintf(queues[x].numbers[y - 1], SIZE_NUM, "%s", tmp); } else break; } } // 3.1.7: Check if there are queues which are not served by any modem: p = 0; snprintf(tmp, sizeof(tmp), "Queues are used, but %s is not served by any modem.", queues[x].name); for (y = 0; y < NUMBER_OF_MODEMS; y++) { if (devices[y].name[0]) { for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (!strcmp(devices[y].queues[i], queues[x].name)) { if (devices[y].outgoing) { *tmp = 0; break; } else strcat_realloc(&p, devices[y].name, (p)? ", " : ""); } } } } if (*tmp) { if (p) wrlogfile(&result, "%s Modem%s %s have outgoing disabled.", tmp, (strstr(p, ","))? "s" : "", p); else wrlogfile(&result, "%s", tmp); } free(p); x++; } if (*eventhandler && executable_check) { if (!(fp = fopen(eventhandler, "r"))) wrlogfile(&result, "Eventhandler %s cannot be read: %s", eventhandler, strerror(errno)); else { fclose(fp); if (!is_executable(eventhandler)) wrlogfile(&result, "Eventhandler %s is not executable for smsd.", eventhandler); } } if (*checkhandler && executable_check) { if (!(fp = fopen(checkhandler, "r"))) wrlogfile(&result, "Checkhandler %s cannot be read: %s", checkhandler, strerror(errno)); else { fclose(fp); if (!is_executable(checkhandler)) wrlogfile(&result, "Checkhandler %s is not executable for smsd.", checkhandler); } } if (*alarmhandler && executable_check) { if (!(fp = fopen(alarmhandler, "r"))) wrlogfile(&result, "Alarmhandler %s cannot be read: %s", alarmhandler, strerror(errno)); else { fclose(fp); if (!is_executable(alarmhandler)) wrlogfile(&result, "Alarmhandler %s is not executable for smsd.", alarmhandler); } } if (*regular_run && executable_check) { if (!(fp = fopen(regular_run, "r"))) wrlogfile(&result, "Regular run %s cannot be read: %s", regular_run, strerror(errno)); else { fclose(fp); if (!is_executable(regular_run)) wrlogfile(&result, "Regular run %s is not executable for smsd.", regular_run); } } for (x = 0; x < NUMBER_OF_MODEMS; x++) { if (devices[x].name[0]) { if (devices[x].device[0] == 0) wrlogfile(&result, "%s has no device specified.", devices[x].name); #ifdef DISABLE_INET_SOCKET if (DEVICE_X_IS_SOCKET) wrlogfile(&result, "Device %s of %s specifies an inet socket, but sockets are not available in this compilation.", devices[x].device, devices[x].name); #else if (DEVICE_X_IS_SOCKET) if (!strchr(devices[x].device, ':')) wrlogfile(&result, "%s has illegal internet host %s specified, must be @:", devices[x].name, devices[x].device); #endif if (queues[0].name[0]) if (devices[x].queues[0][0] == 0) wrlogfile(&result, "Queues are used, but %s has no queue(s) defined.", devices[x].name); if (devices[x].eventhandler[0] && strcmp(eventhandler, devices[x].eventhandler) != 0 && executable_check) { if (!(fp = fopen(devices[x].eventhandler, "r"))) wrlogfile(&result, "%s eventhandler %s cannot be read: %s", devices[x].name, devices[x].eventhandler, strerror(errno)); else { fclose(fp); if (!is_executable(devices[x].eventhandler)) wrlogfile(&result, "%s eventhandler %s is not executable for smsd.", devices[x].name, devices[x].eventhandler); } } if (devices[x].eventhandler_ussd[0] && strcmp(eventhandler, devices[x].eventhandler_ussd) != 0 && strcmp(devices[x].eventhandler, devices[x].eventhandler_ussd) != 0 && executable_check) { if (!(fp = fopen(devices[x].eventhandler_ussd, "r"))) wrlogfile(&result, "%s eventhandler_ussd %s cannot be read: %s", devices[x].name, devices[x].eventhandler_ussd, strerror(errno)); else { fclose(fp); if (!is_executable(devices[x].eventhandler_ussd)) wrlogfile(&result, "%s eventhandler_ussd %s is not executable for smsd.", devices[x].name, devices[x].eventhandler_ussd); } } if (devices[x].pdu_from_file[0]) { strcpy(tmp, devices[x].pdu_from_file); if ((p = strrchr(tmp, '/'))) { *p = 0; if ((i = check_directory(tmp)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "pdu_from_file", tmp); } if (!value_in(devices[x].check_memory_method, 2, CM_NO_CPMS, CM_CPMS)) wrlogfile(&result, "Device %s uses pdu_from_file but it can be used only with check_memory_method values %i or %i.", devices[x].name, CM_NO_CPMS, CM_CPMS); } if (devices[x].dev_rr[0] && executable_check) { if (!(fp = fopen(devices[x].dev_rr, "r"))) wrlogfile(&result, "%s regular_run file %s cannot be read: %s", devices[x].name, devices[x].dev_rr, strerror(errno)); else { fclose(fp); if (!is_executable(devices[x].dev_rr)) wrlogfile(&result, "%s regular_run file %s is not executable for smsd.", devices[x].name, devices[x].dev_rr); } } if (devices[x].dev_rr_post_run[0] && executable_check && strcmp(devices[x].dev_rr, devices[x].dev_rr_post_run)) { if (!(fp = fopen(devices[x].dev_rr_post_run, "r"))) wrlogfile(&result, "%s regular_run_post_run file %s cannot be read: %s", devices[x].name, devices[x].dev_rr_post_run, strerror(errno)); else { fclose(fp); if (!is_executable(devices[x].dev_rr_post_run)) wrlogfile(&result, "%s regular_run_post_run file %s is not executable for smsd.", devices[x].name, devices[x].dev_rr_post_run); } } if (devices[x].dev_rr_cmdfile[0]) { strcpy(tmp, devices[x].dev_rr_cmdfile); if ((p = strrchr(tmp, '/'))) { *p = 0; if ((i = check_directory(tmp)) > 1) wrlogfile(&result, (i == 2)? msg_dir : msg_file, "regular_run_cmdfile", tmp); } } if (devices[x].dev_rr_logfile[0]) { if (!(fp = fopen(devices[x].dev_rr_logfile, "a"))) wrlogfile(&result, "%s regular_run_logfile %s cannot be written: %s", devices[x].name, devices[x].dev_rr_logfile, strerror(errno)); else fclose(fp); } if (devices[x].dev_rr_statfile[0]) { if (!(fp = fopen(devices[x].dev_rr_statfile, "a"))) wrlogfile(&result, "%s regular_run_statfile %s cannot be written: %s", devices[x].name, devices[x].dev_rr_statfile, strerror(errno)); else fclose(fp); // Devices cannot have the same statfile because it's overwritten by each process. for (y = 0; y < NUMBER_OF_MODEMS; y++) { if (y == x) continue; if (devices[y].name[0]) if (strcmp(devices[y].dev_rr_statfile, devices[x].dev_rr_statfile) == 0) wrlogfile(&result, "Devices %s and %s has the same regular_run_statfile %s.", devices[x].name, devices[y].name, devices[x].dev_rr_statfile); } } if (devices[x].messageids < 1 || devices[x].messageids > 3) wrlogfile(&result, "Device %s has invalid value for messageids (%i).", devices[x].name, devices[x].messageids); if (value_in(devices[x].check_memory_method, 5, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) if (devices[x].cmgl_value[0] == 0) wrlogfile(&result, "Device %s uses check_memory_method %i but cmgl_value is not defined.", devices[x].name, devices[x].check_memory_method); if (!value_in(devices[x].check_memory_method, 8, CM_NO_CPMS, CM_CPMS, CM_CMGD, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) wrlogfile(&result, "Device %s has invalid value for check_memory_method (%i).", devices[x].name, devices[x].check_memory_method); if (devices[x].priviledged_numbers[0]) if (!value_in(devices[x].check_memory_method, 3, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) wrlogfile(&result, "Device %s has priviledged_numbers defined but it can only be used with check_memory_method values %i, %i or %i.", devices[x].name, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM); if (devices[x].read_timeout < 1) wrlogfile(&result, "Device %s has invalid value for read_timeout (%i).", devices[x].name, devices[x].read_timeout); if (devices[x].ms_purge_hours < 0) wrlogfile(&result, "Device %s has invalid value for ms_purge_hours (%i).", devices[x].name, devices[x].ms_purge_hours); if (devices[x].ms_purge_minutes < 0) wrlogfile(&result, "Device %s has invalid value for ms_purge_minutes (%i).", devices[x].name, devices[x].ms_purge_minutes); if (!value_in(devices[x].check_network, 3, 0, 1, 2)) wrlogfile(&result, "Device %s has invalid value for check_network (%i).", devices[x].name, devices[x].check_network); // 3.1.7: Devices cannot have the same port. for (y = 0; y < NUMBER_OF_MODEMS; y++) { if (y == x) continue; if (devices[y].name[0] && devices[y].device[0]) if (strcmp(devices[y].device, devices[x].device) == 0) wrlogfile(&result, "Devices %s and %s has the same port ( device = %s ).", devices[x].name, devices[y].name, devices[x].device); } #ifndef USE_ICONV if (devices[x].ussd_convert == 1) wrlogfile(&result, "Device %s uses ussd_convert = 1, but it's only available when USE_ICONV is defined.", devices[x].name); #endif // 3.1.12: Both cpas and clcc cannot be defined. if (devices[x].voicecall_cpas && devices[x].voicecall_clcc) wrlogfile(&result, "Devices %s has both voicecall_cpas and voicecall_clcc defined. Decide which one to use.", devices[x].name); } } // Administrative alerts from mainspooler: // If adminmessage_device is specified, it must exist and be usable. // Later is checked if any device is sending administrative messages and // if mainspooler can use this device. if (*adminmessage_device) { i = 0; for (x = 0; x < NUMBER_OF_MODEMS && !i; x++) { if (!strcmp(devices[x].name, adminmessage_device)) { if (devices[x].outgoing == 0) { wrlogfile(&result, "Mainspooler uses %s to send administrative messages, but this device has outgoing disabled.", adminmessage_device); break; } if (devices[x].admin_to[0] || admin_to[0]) i = 1; else { wrlogfile(&result, "Mainspooler uses %s to send administrative messages, but this device has no admin_to specified.", adminmessage_device); break; } } } if (!i) wrlogfile(&result, "Mainspooler has invalid adminmessage_device setting (%s): device not found.", adminmessage_device); } if (*whitelist) { if (!(fp = fopen(whitelist, "r"))) wrlogfile(&result, "Whitelist %s cannot be read: %s", whitelist, strerror(errno)); else fclose(fp); } if (*blacklist) { if (!(fp = fopen(blacklist, "r"))) wrlogfile(&result, "Blacklist %s cannot be read: %s", blacklist, strerror(errno)); else fclose(fp); } if (*infofile) { if (!(fp = fopen(infofile, "w"))) wrlogfile(&result, "Infofile %s cannot be created: %s", infofile, strerror(errno)); else fclose(fp); unlink(infofile); } if (store_received_pdu < 0 || store_received_pdu > 3) wrlogfile(&result, "Invalid value for store_received_pdu."); if (store_sent_pdu < 0 || store_sent_pdu > 3) wrlogfile(&result, "Invalid value for store_sent_pdu."); if (ic_purge_hours < 0) wrlogfile(&result, "Invalid value for ic_purge_hours (%i).", ic_purge_hours); if (ic_purge_minutes < 0) wrlogfile(&result, "Invalid value for ic_purge_minutes (%i).", ic_purge_minutes); if (ic_purge_interval < 0) wrlogfile(&result, "Invalid value for ic_purge_interval (%i).", ic_purge_interval); if (smart_logging) { if (logfile[0] == 0 || strcmp(logfile, "syslog") == 0 || strcmp(logfile, "0") == 0) wrlogfile(&result, "Smart logging cannot be used when syslog is used for logging."); for (x = 0; x < NUMBER_OF_MODEMS; x++) if (devices[x].name && devices[x].logfile[0]) if (strcmp(devices[x].logfile, "syslog") == 0 || strcmp(devices[x].logfile, "0") == 0) wrlogfile(&result, "Smart logging cannot be used when syslog is used for logging, device %s.", devices[x].name); } if (executable_check) { if (!is_executable(shell)) wrlogfile(&result, "Shell %s does not exist or is not executable for smsd.", shell); else if (shell_test) { char *error = 0; char tmp_data[PATH_MAX]; char tmp_script[PATH_MAX]; char tmp[PATH_MAX +PATH_MAX]; int fd; int i; sprintf(tmp_data, "%s/smsd_data.XXXXXX", "/tmp"); if ((fd = mkstemp(tmp_data)) == -1) error = "Cannot create test data file."; else { close(fd); // 3.1.14: Use incoming directory instead of /tmp which may be mounted noexec: sprintf(tmp_script, "%s/smsd_script.XXXXXX", d_incoming); if ((fd = mkstemp(tmp_script)) == -1) error = "Cannot create test script file."; else { snprintf(tmp, sizeof(tmp), "#!%s\necho OK > \"$1\"\nexit 0\n", shell); if (write(fd, tmp, strlen(tmp)) < (ssize_t)strlen(tmp)) error = "Cannot write to test script file."; close(fd); if (!error) { snprintf(tmp, sizeof(tmp), "%s %s", tmp_script, tmp_data); chmod(tmp_script, 0700); i = my_system(tmp, "startup_check (shell)"); if (i) error = "Failed to execute test script."; else { if ((fd = open(tmp_data, O_RDONLY)) < 0) error = "Cannot read test data file."; else { read(fd, tmp, sizeof(tmp)); if (strncmp(tmp, "OK", 2)) error = "Did not work."; close(fd); } } } unlink(tmp_script); } unlink(tmp_data); } if (error) wrlogfile(&result, "Shell %s testing failed: %s", shell, error); } } // Format strings for strftime: // Not much can be checked, only if it's completelly wrong... time(&now); if (!strchr(datetime_format, '%') || strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&now)) == 0 || !strcmp(timestamp, datetime_format)) wrlogfile(&result, "Format string datetime is completelly wrong: \"%s\"", datetime_format); if (!strchr(logtime_format, '%') || strftime(timestamp, sizeof(timestamp), logtime_format, localtime(&now)) == 0 || !strcmp(timestamp, logtime_format)) wrlogfile(&result, "Format string logtime_format is completelly wrong: \"%s\"", logtime_format); if (!strchr(date_filename_format, '%') || strftime(timestamp, sizeof(timestamp), date_filename_format, localtime(&now)) == 0 || !strcmp(timestamp, date_filename_format)) wrlogfile(&result, "Format string date_filename_format is completelly wrong: \"%s\"", date_filename_format); if (filename_preview < 0 || filename_preview >= SIZE_FILENAME_PREVIEW) wrlogfile(&result, "Value for filename_preview is illegal: \"%d\". It can be 1 ... %u.", filename_preview, SIZE_FILENAME_PREVIEW - 1); if (startup_err_str) { wrlogfile(NULL, "There was %i error%s while removing .LOCK files.", startup_err_count, (startup_err_count > 1)? "s" : ""); p = startup_err_str; while (p && *p) { if ((p2 = strchr(p, '\n'))) *p2 = 0; wrlogfile(&result, "- %s", p); p = (p2)? p2 +1 : NULL; } free(startup_err_str); startup_err_str = NULL; startup_err_count = 0; } if (d_incoming_ok && d_saved_ok) { // 3.1beta7: Search concatenation files from incoming directory. // If zero sized files found, they can be removed. // If files with data found, they can be moved to d_saved directory. // Existing zero sized file is overwritten, but a file containing data produces fatal error. for (x = 0; x < NUMBER_OF_MODEMS; x++) { if (devices[x].name[0]) { sprintf(fname, CONCATENATED_DIR_FNAME, d_incoming, devices[x].name); if (stat(fname, &statbuf) == 0) { if (statbuf.st_size == 0) { if (unlink(fname) != 0) startuperror("Cannot unlink concatenation storage %: %s\n", fname, strerror(errno)); } else { i = 1; sprintf(tmp, CONCATENATED_DIR_FNAME, d_saved, devices[x].name); if (stat(tmp, &statbuf) == 0) { if (statbuf.st_size != 0) { i = 0; wrlogfile(&result, "Concatenation storage of %s cannot be moved from incoming to saved directory, " "destination exists and has also some data ", devices[x].name); } } if (i) { if (movefile(fname, d_saved)) { // movefile does not inform if removing source file failed: if (stat(fname, &statbuf) == 0) { if (unlink(fname) != 0) { startuperror("Failed to move concatenation storage, cannot unlink source file %: %s\n", fname, strerror(errno)); i = 0; } } if (i) writelogfile(LOG_WARNING, 0, "Moved concatenation storage of %s from incoming to saved directory", devices[x].name); } else wrlogfile(&result, "Failed to move concatenation storage of %s from incoming to saved directory.", devices[x].name); } } } } } } if (result > 0) { wrlogfile(NULL, "There was %i major problem%s found.", result, (result > 1)? "s" : ""); fprintf(stderr, "Cannot start. See the log file for details.\n"); } else { // Report some settings: char buffer[PATH_MAX]; mode_t mode; mode_t m; // 3.1.7: Mask can be set: // mode = umask(0); // umask(mode); if (!conf_umask) { mode = umask(0); umask(mode); } else { umask(conf_umask); mode = umask(conf_umask); // Fixed in 3.1.9. } m = 0666 & ~mode; sprintf(buffer, "File mode creation mask: 0%o (0%o, %c%c%c%c%c%c%c%c%c).", (int)mode, (int)m, (m & 0x100)? 'r':'-', (m & 0x80)? 'w':'-', (m & 0x40)? 'x':'-', (m & 0x20)? 'r':'-', (m & 0x10)? 'w':'-', (m & 0x8)? 'x':'-', (m & 0x4)? 'r':'-', (m & 0x2)? 'w':'-', (m & 0x1)? 'x':'-'); writelogfile0(LOG_WARNING, 0, buffer); #ifdef DEBUGMSG printf("!! %s\n", buffer); #endif if (validity_period < 255) { report_validity(tmp, validity_period); sprintf(buffer, "Default validity period is set to %s.", tmp); writelogfile0(LOG_WARNING, 0, buffer); #ifdef DEBUGMSG printf("!! %s\n", buffer); #endif } if (*international_prefixes) { p = international_prefixes; *tmp = 0; do { if (*tmp) strcat(tmp, ","); strcat(tmp, p); p += strlen(p) +1; } while (*p); sprintf(buffer, "Using international prefixes: %s", tmp); writelogfile0(LOG_WARNING, 0, buffer); } if (*national_prefixes) { p = national_prefixes; *tmp = 0; do { if (*tmp) strcat(tmp, ","); strcat(tmp, p); p += strlen(p) +1; } while (*p); sprintf(buffer, "Using national prefixes: %s", tmp); writelogfile0(LOG_WARNING, 0, buffer); } if (*priviledged_numbers) { sprintf(buffer, "Global priviledged_numbers: "); p = priviledged_numbers; while (*p) { if (p != priviledged_numbers) strcat(buffer, ","); strcat(buffer, p); p = strchr(p, 0) +1; } writelogfile0(LOG_WARNING, 0, buffer); // Check and report if global value is used or not. // Not an error even if it's not used. *tmp = 0; for (x = 0; x < NUMBER_OF_MODEMS; x++) if (devices[x].name[0] && devices[x].priviledged_numbers[0] == 0) if (value_in(devices[x].check_memory_method, 3, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM)) sprintf(strchr(tmp, 0), "%s%s", (*tmp)? "," : "", devices[x].name); if (*tmp) writelogfile(LOG_WARNING, 0, "Devices using global priviledged_numbers: %s", tmp); else writelogfile(LOG_WARNING, 0, "Note that no any device is using global priviledged_numbers."); } if (*adminmessage_device) writelogfile(LOG_WARNING, 0, "Mainspooler uses %s to send administrative messages.", adminmessage_device); else { // Check if any device is sending administrative messages. // If one found, it can be used by mainspooler if shared memory is availalbe. *tmp = 0; for (x = 0; x < NUMBER_OF_MODEMS; x++) { if (devices[x].name[0]) { if ((devices[x].admin_to[0] || admin_to[0]) && devices[x].outgoing) { strcpy(tmp, devices[x].name); break; } } } if (*tmp) { #ifndef NOSTATS strcpy(adminmessage_device, tmp); writelogfile(LOG_WARNING, 0, "Mainspooler will use %s to send administrative messages.", adminmessage_device); #else writelogfile(LOG_WARNING, 0, "Note that at least %s will send administrative messages, but mainspooler will not because shared memory is not available.", tmp); #endif } } if (strstr(smsd_version, "beta")) { if (queues[0].name[0]) { writelogfile(LOG_WARNING, 0, "Queue definitions:", tmp); for (x = 0; ; x++) { if (queues[x].name[0]) { *tmp = 0; for (y = 0; y < NUMS; y++) { if (queues[x].numbers[y][0] == 0) break; sprintf(strchr(tmp, 0), "%s%s", (*tmp)? "," : "", queues[x].numbers[y]); } writelogfile(LOG_WARNING, 0, "%s \"%s\" %s", queues[x].name, tmp, queues[x].directory); } else break; } } } } return result; } smstools-3.1.15/src/smsd_cfg.h000077500000000000000000000520021223712572200162160ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef SMSD_CFG_H #define SMSD_CFG_H #include #include #include #ifndef __FreeBSD__ #define DEFAULT_CONFIGFILE "/etc/smsd.conf" #else #define DEFAULT_CONFIGFILE "%%PREFIX%%/etc/smsd.conf" #endif #define DATETIME_DEFAULT "%y-%m-%d %H:%M:%S" #define LOGTIME_DEFAULT "%Y-%m-%d %H:%M:%S" #define DATE_FILENAME_DEFAULT "%Y-%m-%d" #define CONCATENATED_DIR_FNAME "%s/%s-concatenated" #define MM_CORE_FNAME "/tmp/mm_smsd_%i" /* %i is PID */ #define NUMS 64 #define SIZE_NUM 16 #define DEVICE devices[process_id] #define DEVICE_IS_SOCKET (devices[process_id].device[0] == '@') #define DEVICE_X_IS_SOCKET (devices[x].device[0] == '@') #define STATISTICS statistics[process_id] // Maximum size of a message text #define MAXTEXT 39016 // Maxmum size of a single sms, can be 160/140 or less #define maxsms_pdu 160 #define maxsms_ucs2 140 #define maxsms_binary 140 // Sizes for some buffers: #define SIZE_TO 100 #define SIZE_FROM 100 #define SIZE_SMSC 100 #define SIZE_QUEUENAME 100 #define SIZE_UDH_DATA 500 #define SIZE_UDH_TYPE 4096 #define SIZE_RR_CMD 513 #define SIZE_MACROS 4096 #define SIZE_HEADER 101 #define SIZE_MESSAGEIDS 4096 #define SIZE_IDENTITY 100 #define SIZE_TB 1024 #define SIZE_LOG_LINE 16384 #define SIZE_PRIVILEDGED_NUMBERS 512 #define SIZE_SMSD_DEBUG 100 #define SIZE_SHARED_BUFFER 256 #define SIZE_FILENAME_PREVIEW 256 #define SIZE_PB_ENTRY 101 #define SIZE_CHECK_MEMORY_BUFFER 512 // 3.1.12: Changed size from 16384 when CMGL* method is used: #define SIZE_CHECK_MEMORY_BUFFER_CMGL 65536 // Check memory methods: #define CM_NO_CPMS 0 #define CM_S_NO_CPMS "Fixed values are used because CPMS does not work." #define CM_CPMS 1 #define CM_S_CPMS "CPMS is used." #define CM_CMGD 2 #define CM_S_CMGD "CMGD is used." #define CM_CMGL 3 #define CM_S_CMGL "CMGL is used." #define CM_CMGL_DEL_LAST 4 #define CM_S_CMGL_DEL_LAST "CMGL is used and messages are deleted after all messsages are read." #define CM_CMGL_CHECK 31 #define CM_S_CMGL_CHECK "CMGL is used and messages are taken from the list." #define CM_CMGL_DEL_LAST_CHECK 41 #define CM_S_CMGL_DEL_LAST_CHECK "CMGL is used and messages are taken from the list, messages are deleted after all messages are read." #define CM_CMGL_SIMCOM 5 #define CM_S_CMGL_SIMCOM "CMGL is used. SIM600 compatible, see the manual for details." // 3.1.12: #define select_check_memory_buffer_size() (value_in(DEVICE.check_memory_method, 5, CM_CMGL, CM_CMGL_DEL_LAST, CM_CMGL_CHECK, CM_CMGL_DEL_LAST_CHECK, CM_CMGL_SIMCOM))? SIZE_CHECK_MEMORY_BUFFER_CMGL : SIZE_CHECK_MEMORY_BUFFER #define LENGTH_PDU_DETAIL_REC 70 // For put_command() calls: #define EXPECT_OK_ERROR "(OK)|(ERROR)" #define TELNET_LOGIN_PROMPT_DEFAULT "login:" #define TELNET_LOGIN_PROMPT_IGNORE_DEFAULT "Last login:" #define TELNET_PASSWORD_PROMPT_DEFAULT "Password:" #define isdigitc(ch) isdigit((int)(ch)) #define isalnumc(ch) isalnum((int)(ch)) char process_title[32]; // smsd for main task, name of a modem for other tasks. int process_id; // -1 for main task, all other have numbers starting with 0. // This is the same as device, can be used like devices[process_id]... time_t process_start_time; int modem_handle; // Handle for modem. int put_command_timeouts; typedef struct { char name[32]; // Name of the queue char numbers[NUMS][SIZE_NUM]; // Phone numbers assigned to this queue char directory[PATH_MAX]; // Queue directory } _queue; typedef struct { char name[32]; // Name of the modem char number[32]; // 3.1.4: SIM card's telephone number. char device[PATH_MAX]; // Serial port name int device_open_retries; // 3.1.7: Defines count of retries when opening a device fails. int device_open_errorsleeptime; // 3.1.7: Sleeping time after opening error. int device_open_alarm_after; // 3.1.7: Defines after how many failures an alarmhandler is called. char identity[SIZE_IDENTITY]; // Identification asked from the modem (CIMI) char conf_identity[SIZE_IDENTITY]; // Identification set in the conf file (CIMI) //char identity_header[SIZE_TO];// Title of current identification (IMSI) char queues[NUMBER_OF_MODEMS][32]; // Assigned queues int incoming; // Try to receive SMS. 0=No, 1=Low priority, 2=High priority int outgoing; // = 0 if a modem is not used to send messages. int report; // Ask for delivery report 0 or 1 (experimental state) int phonecalls; // Check for phonebook status for calls, 0 or 1. 3.1.7: value 2 = +CLIP report is handled. char phonecalls_purge[32]; // Defines if a purge command should be used. yes / no / command to use. yes = AT^SPBD="MC" int phonecalls_error_max; // 3.1.7: Max nr of errors before phonecalls are ignored. char pin[16]; // SIM PIN int pinsleeptime; // Number of seconds to sleep after a PIN is entered. char mode[10]; // Command set version old or new char smsc[16]; // Number of SMSC int baudrate; // Baudrate int send_delay; // Makes sending characters slower (milliseconds) int send_handshake_select; // 3.1.9. int cs_convert; // Convert character set 0 or 1 (iso-9660) char initstring[100]; // first Init String char initstring2[100]; // second Init String char eventhandler[PATH_MAX]; // event handler program or script char eventhandler_ussd[PATH_MAX]; // 3.1.7: event handler program or script for USSD answers int ussd_convert; // 3.1.7: Convert string from USSD answer int rtscts; // hardware handshake RTS/CTS, 0 or 1 int read_memory_start; // first memory space for sms char primary_memory[10]; // primary memory, if dual-memory handler is used char secondary_memory[10]; // secondary memory, if dual-memory handler is used int secondary_memory_max; // max value for secondary memory, if dual-memory handler is used and modem does not tell correct max value char pdu_from_file[PATH_MAX]; // for testing purposes: incoming pdu can be read from file if this is set. int sending_disabled; // 1 = do not actually send a message. For testing purposes. int modem_disabled; // 1 = disables modem handling. For testing purposes. Outgoing side acts like with sending_disabled, // incoming side reads messages only from file (if defined). NOTE: device name should still be defined // as it's opened and closed. You can use some regular file for this. Must be an existing file. int decode_unicode_text; // 1 if unicode text is decoded internally. int internal_combine; // 1 if multipart message is combined internally. int internal_combine_binary; // 1 if multipart binary message is combined internally. Defaults to internal_combine. int pre_init; // 1 if pre-initialization is used with a modem. int check_network; // 0 if a modem does not support AT+CREG command. char admin_to[SIZE_TO]; // Destination number for administrative messages. int message_limit; // Limit counter for outgoing messages. 0 = no limit. int message_count_clear; // Period to automatically clear message counter. Value is MINUTES. int keep_open; // 1 = do not close modem while idle. char dev_rr[PATH_MAX]; // Script/program which is run regularly. char dev_rr_post_run[PATH_MAX]; // 3.1.7: Script/program which is run regularly (POST_RUN). int dev_rr_interval; // Number of seconds between running a regular_run script/progdam. char dev_rr_cmdfile[PATH_MAX];// char dev_rr_cmd[SIZE_RR_CMD]; // char dev_rr_logfile[PATH_MAX]; int dev_rr_loglevel; // defaults to 5, LOG_NOTICE. Has only effect when a main log is used. char dev_rr_statfile[PATH_MAX]; char logfile[PATH_MAX]; // Name or Handle of Log File int loglevel; // Log Level (9=highest). Verbosity of log file. int messageids; // Defines how message id's are stored: 1 = first, 2 = last (default), 3 = all. int voicecall_vts_list; // Defines how VTS command is sent: 1 = as a list like "1,2,3,4", 2 = single note with one VTS command (default). int voicecall_ignore_modem_response; // Delay defined with TIME: is not breaked even if modem gives some response. int voicecall_hangup_ath; // If ATH is used instead of AT+CHUP. int voicecall_vts_quotation_marks; // Defines if AT+VTS="n" command is given with quotation marks. int voicecall_cpas; // Defines if AT+CPAS is used to detect when a call is answered (phone returns OK after ATD). int voicecall_clcc; // 3.1.12: Defines if AT+CLCC is used to detect when a call is answered (phone returns OK after ATD). int check_memory_method; // 0 = CPMS not supported, 1 = CPMS supported and must work (default), 2 = CMGD used to check messages, 3 = CMGL is used. char cmgl_value[32]; // With check_memory_method 3, correct value for AT+CMGL= must be given here. char priviledged_numbers[SIZE_PRIVILEDGED_NUMBERS]; // Priviledged numbers in incoming messages. int read_timeout; // Timeout for reading from a modem, in seconds. int ms_purge_hours; // Wich check_memory_method 5 (SIM600), messages with missing part(s) are removed from a int ms_purge_minutes; // modem after timeout defined with these two settings. Both values 0 disables this feature. int ms_purge_read; // 1 if available parts are read when purge timeout is reached. 0 if parts are only deleted. int detect_message_routing; // 0 if CMT/CDS detection is disabled. int detect_unexpected_input; // 0 if if detection is disabled. int unexpected_input_is_trouble; // 0 if unexpected input / routed message should NOT activate trouble.log int adminmessage_limit; // Limit counter for administrative alert messages. 0 = no limit. int adminmessage_count_clear; // Period to automatically clear administrative alert counter. Value is MINUTES. int status_signal_quality; // 1 = signal quality is written to status file. int status_include_counters; // 1 = succeeded, failed and received counters are included in the status line. int communication_delay; // Time between each put_command (milliseconds), some modems need this. int hangup_incoming_call; // 1 = if detected unexpected input contains RING and we want to end call. int max_continuous_sending; // Defines when sending is breaked to do check/do other tasks. Time in seconds. int socket_connection_retries; // 3.1.7: Defines count of retries when socket connection fails. int socket_connection_errorsleeptime; // 3.1.7: Sleeping time after socket connetcion error. int socket_connection_alarm_after; // 3.1.7: Defines after how many failures an alarmhandler is called. int report_device_details; // Defines if device details are logged when modem process is starting. int using_routed_status_report; // Disables a warning about routed status reports. int routed_status_report_cnma; // Defines if +CNMA acknowledgement is needed to send. int needs_wakeup_at; // After idle time, some modems may not answer to the first AT command. int keep_messages; // Defines if messages are not deleted. Smsd continues running. char startstring[100]; // 3.1.7: Command(s) to send to the modem when a devicespooler is starting. int startsleeptime; // 3.1.7: Second to wait after startstring is sent. char stopstring[100]; // 3.1.7: Command(s) to send to the modem when a devicespooler is stopping. int trust_spool; // 3.1.9 int smsc_pdu; // 3.1.12: 1 if smsc is included in the PDU. char telnet_login[64]; // 3.1.12: Settings for telnet. char telnet_login_prompt[64]; char telnet_login_prompt_ignore[64]; char telnet_password[64]; char telnet_password_prompt[64]; int signal_quality_ber_ignore; // 3.1.14. int verify_pdu; // 3.1.14. int loglevel_lac_ci; // 3.1.14. int log_not_registered_after; // 3.1.14. } _device; // NOTE for regular run intervals: effective value is at least delaytime. char configfile[PATH_MAX]; // Path to config file char d_spool[PATH_MAX]; // Spool directory char d_failed[PATH_MAX]; // Failed spool directory char d_incoming[PATH_MAX]; // Incoming spool directory char d_report[PATH_MAX]; // Incoming report spool directory char d_phonecalls[PATH_MAX]; // Incoming phonecalls data directory char d_saved[PATH_MAX]; // Directory for smsd's internal use, concatenation storage files etc. char d_sent[PATH_MAX]; // Sent spool directory char d_checked[PATH_MAX]; // Spool directory for checked messages (only used when no provider queues used) char eventhandler[PATH_MAX]; // Global event handler program or script char alarmhandler[PATH_MAX]; // Global alarm handler program or script char checkhandler[PATH_MAX]; // Handler that checks if the sms file is valid. int alarmlevel; // Alarm Level (9=highest). Verbosity of alarm handler. char logfile[PATH_MAX]; // Name or Handle of Log File int loglevel; // Log Level (9=highest). Verbosity of log file. _queue queues[NUMBER_OF_MODEMS]; // Queues _device devices[NUMBER_OF_MODEMS]; // Modem devices int delaytime; // sleep-time after workless int delaytime_mainprocess; // sleep-time after workless, main process. If -1, delaytime is used. int blocktime; // sleep-time after multiple errors int blockafter; // Block modem after n errors int errorsleeptime; // sleep-time after each error int autosplit; // Splitting of large text messages 0=no, 1=yes 2=number with text, 3=number with UDH int receive_before_send; // if 1 smsd tries to receive one message before sending int store_received_pdu; // 0=no, 1=unsupported pdu's only, 2=unsupported and 8bit/unicode, 3=all int store_sent_pdu; // 0=no, 1=failed pdu's only, 2=failed and 8bit/unicode, 3=all int validity_period; // Validity period for messages. int decode_unicode_text; // 1 if unicode text is decoded internally. int internal_combine; // 1 if multipart message is combined internally. int internal_combine_binary; // 1 if multipart binary message is combined internally. Defaults to internal_combine. int keep_filename; // 0 if unique filename is created to each directory when a message file is moved. int store_original_filename; // 1 if an original filename is saved to message file when it's moved from // outgoing directory to spooler. Works together with keep_filename. int date_filename; // 1 or 2 if YYYYMMDD is included to the filename of incoming message. char regular_run[PATH_MAX]; // Script/program which is run regularly. int regular_run_interval; // Number of seconds between running a regular_run script/progdam. char admin_to[SIZE_TO]; // Destination number for administrative messages. int filename_preview; // Number of chars of message text to concatenate to filename. int incoming_utf8; // 1 if incoming files are saved using UTF-8 character set. int outgoing_utf8; // 1 if outgoing files are automatically converted from UTF-8 to ISO and GSM. int log_charconv; // 1 if character set conversion is logged. int log_single_lines; // 1 if linefeeds are removed from the modem response to be logged. int executable_check; // 0 if eventhandler and other executables are NOT checked during the startup checking. int keep_messages; // For testing purposes: messages are not deleted and smsd stops after first run. char priviledged_numbers[SIZE_PRIVILEDGED_NUMBERS]; // Priviledged numbers in incoming messages. int ic_purge_hours; // If internal_combine is used, concatenation storage is checked every ic_purge_interval minutes int ic_purge_minutes; // and if there is message parts older than defined, they are handled or deleted. int ic_purge_read; // 1 = message parts are stored as single messages. 0 = parts are just deleted. int ic_purge_interval; // char shell[PATH_MAX]; // Shell used to run eventhandler, defaults to /bin/sh char adminmessage_device[32]; // Name of device used to send administrative messages of mainspooler. int smart_logging; // 1 = if loglevel is less than 7, degug log is written is there has been any errors. int status_signal_quality; // 1 = signal quality is written to status file. int status_include_counters; // 1 = succeeded, failed and received counters are included in the status line. int hangup_incoming_call; // 1 = if detected unexpected input contains RING and we want to end call. int max_continuous_sending; // Defines when sending is breaked to do check/do other tasks. Time in minutes. int voicecall_hangup_ath; // If ATH is used instead of AT+CHUP. // 3.1.5: int trust_outgoing; // 1 = it's _sure_ that files are created by rename AND permissions are correct. Speeds up spooling. // 3.1.5: int ignore_outgoing_priority; // 1 = Priority: high header is not checked. Speeds up spooling. // 3.1.7: int ignore_exec_output; // 1 = stdout and stderr of eventhandlers is _not_ checked. // 3.1.7: mode_t conf_umask; // File mode creation mask for smsd and modem processes. // 3.1.7: int trim_text; // 1 = trailing whitespaces are removed from text: // 3.1.7: int use_linux_ps_trick; // 1 = change argv[0] to "smsd: MAINPROCESS", "smsd: GSM1" etc. // 3.1.7: int log_unmodified; // 3.1.7: char suspend_filename[PATH_MAX]; // 3.1.9: int spool_directory_order; // 3.1.9: 1 if read_from_modem is logged. int log_read_from_modem; int message_count; // Counter for sent messages. Multipart message is one message. int terminate; // The current process terminates if this is 1 char username[65]; // user and group name which are used to run. char groupname[65]; // (max length is just a guess) char infofile[PATH_MAX]; // Hepler file for stopping the smsd smoothly. char pidfile[PATH_MAX]; // File where a process id is stored. // Command line arguments: char arg_username[65]; char arg_groupname[65]; char arg_infofile[PATH_MAX]; char arg_pidfile[PATH_MAX]; char arg_logfile[PATH_MAX]; int arg_terminal; // 3.1.7: char arg_7bit_packed[512]; int do_encode_decode_arg_7bit_packed; int terminal; // 1 if smsd is communicating with terminal. pid_t device_pids[NUMBER_OF_MODEMS]; // Pid's of modem processes. char run_info[PATH_MAX]; // Information about external script/program execution. char communicate[32]; // Device name for terminal communication mode. char international_prefixes[PATH_MAX +1]; char national_prefixes[PATH_MAX +1]; // Storage for startup errors: char *startup_err_str; int startup_err_count; // Storage for PDU's: char *incoming_pdu_store; char *outgoing_pdu_store; char *routed_pdu_store; // Storage for getfile errors: char *getfile_err_store; // Text buffer for error messages: char tb[SIZE_TB]; // Buffer for SIM memory checking: char *check_memory_buffer; size_t check_memory_buffer_size; int os_cygwin; // 1 if we are on Cygwin. char language_file[PATH_MAX]; // File name of translated headers. char yes_chars[SIZE_HEADER]; // Characters which mean "yes" in the yesno() question. char no_chars[SIZE_HEADER]; // See details inside read_translation() function. char yes_word[SIZE_HEADER]; // "yes" printed as an output. char no_word[SIZE_HEADER]; // "no" char datetime_format[SIZE_HEADER]; // strftime format string for time stamps (not inside status reports). char logtime_format[SIZE_HEADER]; // 3.1.7: strftime format string for logging time stamps char date_filename_format[SIZE_HEADER]; // 3.1.7: strftime format string for date_filename int translate_incoming; // 0 if incoming message headers are NOT transtaled. // 3.1.14: int logtime_us; int logtime_ms; // 3.1.14: int shell_test; // Next two are for debugging purposes: int enable_smsd_debug; char smsd_debug[SIZE_SMSD_DEBUG]; // Header of an outgoing message file. /* initialize all variable with default values */ void initcfg(); /* read the config file */ int readcfg(); /* Retuns the array-index and the directory of a queue or -1 if not found. Name is the name of the queue or a phone number. */ int getqueue(char* name, char* directory); /* Returns the array-index of a device or -1 if not found */ int getdevice(char* name); /* Show help */ void help(); /* parse arguments */ void parsearguments(int argc,char** argv); int startup_check(int result); void abnormal_termination(int all); char *tb_sprintf(char* format, ...); int savephonecall(char *entry_number, int entry_type, char *entry_text); #endif smstools-3.1.15/src/stats.c000077500000000000000000000264041223712572200155710ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include "alarm.h" #include "smsd_cfg.h" #include "stats.h" #include #include "logging.h" #include "modeminit.h" #include "extras.h" #include #ifndef NOSTATS #include #endif char newstatus[NUMBER_OF_MODEMS +1] = {0}; char oldstatus[NUMBER_OF_MODEMS +1] = {0}; char *statistics_current_version = "VERSION 3.1.5-1"; void initstats() { int i; // if the mm Library is not available the statistic funktion does not work. // Use unshared memory instead disabling all statistc related functions. // This is much easier to program. #ifndef NOSTATS // 3.1.5: get rid of tempnam: //MM_create(DEVICES*sizeof(_stats),tempnam(0,0)); char filename[PATH_MAX]; // mm library also defaults to /tmp directory with pid, // .sem will be added to name inside mm. sprintf(filename, MM_CORE_FNAME, (int)getpid()); // 3.1.5: return value is now checked: if (MM_create(NUMBER_OF_MODEMS *sizeof(_stats) +SIZE_SHARED_BUFFER, filename) == 0) { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot create shared memory for statistics.")); alarm_handler0(LOG_ERR, tb); exit(EXIT_FAILURE); } #endif for (i = 0; i < NUMBER_OF_MODEMS; i++) { #ifndef NOSTATS if ((statistics[i]=(_stats*)MM_malloc(sizeof(_stats)))) #else if ((statistics[i]=(_stats*)malloc(sizeof(_stats)))) #endif { statistics[i]->succeeded_counter = 0; statistics[i]->failed_counter = 0; statistics[i]->received_counter = 0; statistics[i]->multiple_failed_counter = 0; statistics[i]->status = '-'; statistics[i]->usage_s = 0; statistics[i]->usage_r = 0; statistics[i]->message_counter = 0; statistics[i]->last_init = 0; statistics[i]->ssi = -1; statistics[i]->ber = -1; } else { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot reserve memory for statistics.")); alarm_handler0(LOG_ERR, tb); #ifndef NOSTATS MM_destroy(); #endif exit(EXIT_FAILURE); } } #ifndef NOSTATS if ((shared_buffer = (char *)MM_malloc(SIZE_SHARED_BUFFER))) #else if ((shared_buffer = (char *)malloc(SIZE_SHARED_BUFFER))) #endif *shared_buffer = 0; else { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot reserve memory for shared buffer.")); alarm_handler0(LOG_ERR, tb); #ifndef NOSTATS MM_destroy(); #endif exit(EXIT_FAILURE); } rejected_counter=0; start_time=time(0); last_stats=time(0); } void resetstats() { int i; for (i = 0; i < NUMBER_OF_MODEMS; i++) { statistics[i]->succeeded_counter = 0; statistics[i]->failed_counter = 0; statistics[i]->received_counter = 0; statistics[i]->multiple_failed_counter = 0; statistics[i]->status = '-'; statistics[i]->usage_s = 0; statistics[i]->usage_r = 0; // message_counter remains untouched. // last_init and signal quality remains untouched. } rejected_counter = 0; start_time = time(0); last_stats = time(0); } void savestats() { char filename[PATH_MAX]; FILE *fp; int i; time_t now; if (d_stats[0] && stats_interval) { now = time(0); sprintf(filename, "%s/stats.tmp", d_stats); if ((fp = fopen(filename, "w"))) { fwrite(statistics_current_version, strlen(statistics_current_version) +1, 1, fp); fwrite(&now, sizeof(now), 1, fp); fwrite(&start_time, sizeof(start_time), 1, fp); for (i = 0; i < NUMBER_OF_MODEMS; i++) fwrite(statistics[i], sizeof(_stats), 1, fp); fclose(fp); } else { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot write tmp file for statistics. %s %s", filename, strerror(errno))); alarm_handler0(LOG_ERR, tb); } } } void loadstats() { char filename[PATH_MAX]; FILE *fp; int i; time_t saved_time; char tmp[81]; if (d_stats[0] && stats_interval) { sprintf(filename, "%s/stats.tmp", d_stats); if ((fp = fopen(filename,"r"))) { fread(tmp, strlen(statistics_current_version) +1, 1, fp); if (strncmp(statistics_current_version, tmp, strlen(statistics_current_version))) writelogfile0(LOG_ERR, 0, tb_sprintf("Not loading statistics tmp file because version has changed.")); else { fread(&saved_time, sizeof(time_t), 1, fp); fread(&start_time, sizeof(time_t), 1, fp); start_time = time(0) -(saved_time -start_time); for (i = 0; i < NUMBER_OF_MODEMS; i++) { fread(statistics[i], sizeof(_stats), 1, fp); statistics[i]->status = '-'; statistics[i]->last_init = 0; statistics[i]->ssi = -1; statistics[i]->ber = -1; } } fclose(fp); } } } void print_status() { int j; if (printstatus) { strcpy(oldstatus,newstatus); for (j = 0; j < NUMBER_OF_MODEMS; j++) newstatus[j]=statistics[j]->status; newstatus[NUMBER_OF_MODEMS] = 0; if (strcmp(oldstatus,newstatus)) { printf("%s\n",newstatus); } } } void checkwritestats() { time_t now; time_t next_time; char filename[PATH_MAX]; char s[20]; FILE* datei; int i; int sum_counter; if (d_stats[0] && stats_interval) { next_time=last_stats+stats_interval; next_time=stats_interval*(next_time/stats_interval); // round value now=time(0); if (now>=next_time) // reached timepoint of next stats file? { // Check if there was activity if user does not want zero-files if (stats_no_zeroes) { sum_counter=rejected_counter; for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (devices[i].name[0]) { sum_counter+=statistics[i]->succeeded_counter; sum_counter+=statistics[i]->failed_counter; sum_counter+=statistics[i]->received_counter; sum_counter+=statistics[i]->multiple_failed_counter; } } if (sum_counter==0) { resetstats(); last_stats=now; return; } } last_stats=time(0); // %Y used instead of %y to avoid compiler warning message in some environments. strftime(s,sizeof(s),"%Y%m%d.%H%M%S",localtime(&next_time)); strcpyo(s, s +2); syslog(LOG_INFO,"Writing stats file %s",s); strcpy(filename,d_stats); strcat(filename,"/"); strcat(filename,s); datei=fopen(filename,"w"); if (datei) { fprintf(datei,"runtime,rejected\n"); //fprintf(datei,"%li,%i\n\n", now -start_time, rejected_counter); fprintf(datei, "%lld,%i\n\n", (long long int)now -start_time, rejected_counter); fprintf(datei,"name,succeeded,failed,received,multiple_failed,usage_s,usage_r\n"); for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (devices[i].name[0]) fprintf(datei,"%s,%i,%i,%i,%i,%i,%i\n", devices[i].name, statistics[i]->succeeded_counter, statistics[i]->failed_counter, statistics[i]->received_counter, statistics[i]->multiple_failed_counter, statistics[i]->usage_s, statistics[i]->usage_r); } fclose(datei); resetstats(); last_stats=now; } else { writelogfile0(LOG_ERR, 0, tb_sprintf("Cannot write statistic file. %s %s",filename,strerror(errno))); alarm_handler0(LOG_ERR, tb); } } } } // 3.1.1: void update_message_counter(int messages, char *modemname) { char filename[PATH_MAX]; FILE *fp; char temp[256]; int counter = 0; char *p; if (*d_stats) { sprintf(filename, "%s/%s.counter", d_stats, modemname); if ((fp = fopen(filename, "r"))) { if (fgets(temp, sizeof(temp), fp)) { if (!(p = strchr(temp, ':'))) p = temp; else p++; counter = atoi(p); } fclose(fp); } // 3.1.7: always create a file counter += messages; if ((fp = fopen(filename, "w"))) { fprintf(fp, "%s: %i\n", modemname, counter); fclose(fp); } STATISTICS->message_counter = counter; } } // 3.1.5: void write_status() { #ifndef NOSTATS int i; char fname_tmp[PATH_MAX]; char fname[PATH_MAX]; FILE *fp; char *status; char buffer[256]; time_t t; static size_t longest_modemname = 0; int include_counters; if (!printstatus && d_stats[0]) { strcpy(oldstatus, newstatus); for (i = 0; i < NUMBER_OF_MODEMS; i++) newstatus[i] = statistics[i]->status; newstatus[NUMBER_OF_MODEMS] = 0; if (strcmp(oldstatus, newstatus)) { sprintf(fname_tmp, "%s/status.tmp", d_stats); if ((fp = fopen(fname_tmp, "w"))) { if (!longest_modemname) for (i = 0; i < NUMBER_OF_MODEMS; i++) if (devices[i].name[0]) if (strlen(devices[i].name) > longest_modemname) longest_modemname = strlen(devices[i].name); t = time(0); strftime(buffer, sizeof(buffer), datetime_format, localtime(&t)); fprintf(fp, "Status:%s\t%s,\t%s\n", (longest_modemname >= 7)? "\t" : "", buffer, newstatus); for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (devices[i].name[0] == 0) continue; switch (newstatus[i]) { case 's': status = "Sending"; break; case 'r': status = "Receiving"; break; case 'i': status = "Idle"; break; case 'b': status = "Blocked"; break; case 't': status = "Trouble"; break; default: status = "Unknown"; } if (statistics[i]->last_init) strftime(buffer, sizeof(buffer), datetime_format, localtime(&(statistics[i]->last_init))); else strcpy(buffer, "-"); fprintf(fp, "%s:%s\t%s,\t%s", devices[i].name, (strlen(devices[i].name) < 7 && longest_modemname >= 7)? "\t" : "", buffer, status); include_counters = 0; if (devices[i].status_include_counters == 1 || (devices[i].status_include_counters == -1 && status_include_counters)) include_counters = 1; if (include_counters || statistics[i]->ssi >= 0) fprintf(fp, ",%s", (strlen(status) < 7)? "\t" : ""); if (include_counters) fprintf(fp, "\t%i,\t%i,\t%i", statistics[i]->message_counter, statistics[i]->failed_counter, statistics[i]->received_counter); if (statistics[i]->ssi >= 0) { if (include_counters) fprintf(fp, ","); explain_csq_buffer(buffer, 1, statistics[i]->ssi, statistics[i]->ber, devices[i].signal_quality_ber_ignore); fprintf(fp, "\t%s", buffer); } fprintf(fp, "\n"); } fclose(fp); sprintf(fname, "%s/status", d_stats); rename(fname_tmp, fname); } } } #endif } smstools-3.1.15/src/stats.h000077500000000000000000000047401223712572200155750ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef STATS_H #define STATS_H #include typedef struct { int succeeded_counter; // Number of sent SM int failed_counter; // Number of not sent SM (ERROR from modem) int received_counter; // Number of received SM int multiple_failed_counter; // Number of multiple failed SM (3 consecutive ERROR's from modem) int usage_s; // Modem usage to send SM in seconds int usage_r; // Modem usage to receive SM in seconds char status; // s=send r=receive i=idle b=blocked -=not running t=trouble int message_counter; // This is a number of sent messages, stored in GSM1.counter file. // Note: first 3 counters are cleared every time when stats_interval is reached. time_t last_init; int ssi; int ber; } _stats; _stats* statistics[NUMBER_OF_MODEMS]; // Statistic data (shared memory!) int rejected_counter; // Statistic counter, rejected SM, number does not fit into any queue time_t start_time; // Start time of smsd, allows statistic functions int printstatus; // if 1 smsd outputs status on stdout time_t last_stats; // time when the last stats file was created char d_stats[PATH_MAX]; // path to statistic files int stats_interval; // time between statistic files in seconds. int stats_no_zeroes; // Suppress files that contain only zeroes int status_interval; // time between updating status file in seconds char *shared_buffer; /* Creates shared memory variables for statistic data */ void initstats(); /* Resets statistic data to 0 */ void resetstats(); /* saves the current statistic counter into a tmp file */ void savestats(); /* load the statistic counter from the tmp file */ void loadstats(); /* Prints modem status to stdout */ void print_status(); /* Checks if next statistic file should be written and writes it */ void checkwritestats(); void update_message_counter(int messages, char *modemname); void write_status(); #endif // STATS_H smstools-3.1.15/src/version.h000077500000000000000000000001161223712572200161150ustar00rootroot00000000000000#ifndef VERSION_H #define VERSION_H #define smsd_version "3.1.15" #endif smstools-3.1.15/src/whitelist.c000077500000000000000000000041301223712572200164370ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include "whitelist.h" #include "extras.h" #include "logging.h" #include "alarm.h" #include "smsd_cfg.h" /* Used with >= 3.1x */ int inwhitelist_q(char* msisdn, char *queuename) { FILE* file; char line[256]; char* posi; char current_queue[32]; int result = 1; int i; if (whitelist[0]) // is a whitelist file specified? { file=fopen(whitelist,"r"); if (file) { *current_queue = 0; result = 0; while (fgets(line,sizeof(line),file)) { posi=strchr(line,'#'); // remove comment if (posi) *posi=0; cutspaces(line); i = strlen(line); if (i > 0) { if (line[0] == '[' && line[i -1] == ']' && (size_t)(i -2) < sizeof(current_queue)) { line[i -1] = 0; strcpy(current_queue, line +1); } else if (strncmp(msisdn,line,strlen(line))==0) { result = 1; break; } else if (msisdn[0]=='s' && strncmp(msisdn+1,line,strlen(line))==0) { result = 1; break; } } } fclose(file); if (result == 1 && *current_queue && !(*queuename)) strcpy(queuename, current_queue); } else { writelogfile0(LOG_CRIT, 0, tb_sprintf("Stopping. Cannot read whitelist file %s.",whitelist)); alarm_handler0(LOG_CRIT, tb); abnormal_termination(1); } } return result; } smstools-3.1.15/src/whitelist.h000077500000000000000000000013071223712572200164470ustar00rootroot00000000000000/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2 from Stefan Frings http://www.meinemullemaus.de/ SMS Server Tools version 2 and below are Copyright (C) Stefan Frings. This program is free software unless you got it under another license directly from the author. You can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. Either version 2 of the License, or (at your option) any later version. */ #ifndef WHITE_H #define WHITE_H char whitelist[256]; // Filename of the white-list /* Used with >= 3.1x */ int inwhitelist_q(char* msisdn, char *queuename); #endif smstools-3.1.15/uninstall.sh000077500000000000000000000024201223712572200160350ustar00rootroot00000000000000#!/bin/sh #Do not run directly. This is a helper script for make. BINDIR=$1 if [ -z "$BINDIR" ]; then BINDIR=/usr/local/bin fi echo "You are going to delete all files from the SMS Server Tools." echo "This script deletes also the config file and stored messages." echo "Are you sure to proceed? [yes/no]" read answer if [ "$answer" != "yes" ]; then echo "Did NOT proceed, exiting..." exit 1 fi echo "Deleting binary program files" # For Cygwin "smsd" and "smsd.exe" are the same while searching files, # but rm needs a complete name. [ -f $BINDIR/smsd.exe ] && rm $BINDIR/smsd.exe [ -f $BINDIR/smsd ] && rm $BINDIR/smsd [ -f $BINDIR/putsms ] && rm $BINDIR/putsms [ -f $BINDIR/getsms ] && rm $BINDIR/getsms echo "Deleting some scripts" [ -f $BINDIR/pkill ] && echo "skipped $BINDIR/pkill, other programs might need it." [ -f $BINDIR/sendsms ] && rm $BINDIR/sendsms [ -f $BINDIR/sms2html ] && rm $BINDIR/sms2html [ -f $BINDIR/sms2unicode ] && rm $BINDIR/sms2unicode [ -f $BINDIR/unicode2sms ] && rm $BINDIR/unicode2sms echo "Deleting config file" [ -f /etc/smsd.conf ] && rm /etc/smsd.conf echo "Deleting start-script" [ -d /etc/init.d ] && rm /etc/init.d/sms3 [ -d /sbin/init.d ] && rm /sbin/init.d/sms3 echo "Deleting spool directories" [ -d /var/spool/sms ] && rm -R /var/spool/sms