smstools3/0000755000175000017500000000000013067452715011456 5ustar kekekekesmstools3/LICENSE0000755000175000017500000004467410371202746012475 0ustar kekekeke

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. smstools3/Makefile0000755000175000017500000000062710612645036013117 0ustar kekekeke# 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 smstools3/package.sh0000755000175000017500000000050011366517253013402 0ustar kekekeke#!/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" smstools3/scripts/0000755000175000017500000000000013053576604013144 5ustar kekekekesmstools3/scripts/sendsms0000755000175000017500000000443713102360546014545 0ustar kekekeke#!/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_group="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 group="" if [ -f /etc/group ]; then if grep $smsd_group: /etc/group >/dev/null; then group=$smsd_group 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$group" != x ]; then chgrp $group $TMPFILE fi chmod 0660 $TMPFILE FILE=`mktemp /var/spool/sms/outgoing/send_XXXXXX` mv $TMPFILE $FILE done smstools3/scripts/hex2dec0000755000175000017500000000065010435642422014406 0ustar kekekeke#!/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"; } smstools3/scripts/sms30000755000175000017500000000731711367763450013772 0ustar kekekeke#! /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 smstools3/scripts/sms2xml0000755000175000017500000000136010371202746014470 0ustar kekekeke#!/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 $FILENAMEsmstools3/scripts/callhandler0000755000175000017500000000065611105023242015327 0ustar kekekeke#!/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 smstools3/scripts/eventhandler_report0000755000175000017500000000203611160441570017133 0ustar kekekeke#!/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 smstools3/scripts/forwardsms0000755000175000017500000000050011035673520015245 0ustar kekekeke#!/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 smstools3/scripts/email2sms0000755000175000017500000000215411443645150014762 0ustar kekekeke#!/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 smstools3/scripts/smsresend0000755000175000017500000000214011230355071015056 0ustar kekekeke#!/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 smstools3/scripts/mysmsd0000755000175000017500000000352211016006035014367 0ustar kekekeke#!/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 smstools3/scripts/README0000644000175000017500000000022311406643377014023 0ustar kekekekeThis folder is not actively updated. More examples can be found in the program support website http://smstools3.kekekasvi.com/index.php?p=support smstools3/scripts/eventhandler-utf-80000755000175000017500000000053711166164056016514 0ustar kekekeke#!/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 smstools3/scripts/sms2unicode0000755000175000017500000000174710371202746015327 0ustar kekekeke#!/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 smstools3/scripts/hex2bin0000755000175000017500000000064510435642354014433 0ustar kekekeke#!/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); } } smstools3/scripts/sms2html0000755000175000017500000000226610371202746014642 0ustar kekekeke#!/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 "" smstools3/scripts/sql_demo0000755000175000017500000000156210371202746014672 0ustar kekekeke#!/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 smstools3/scripts/unicode2sms0000755000175000017500000000136210371202746015320 0ustar kekekeke#!/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 smstools3/scripts/pkill0000755000175000017500000000044010475626634014207 0ustar kekekeke#! /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 smstools3/scripts/regular_run0000755000175000017500000000734011161424236015412 0ustar kekekeke#!/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 smstools3/scripts/smsevent0000755000175000017500000000442310722603542014731 0ustar kekekeke#!/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 smstools3/scripts/checkhandler-utf-80000755000175000017500000000043711166164212016441 0ustar kekekeke#!/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 smstools3/scripts/smstest.php0000755000175000017500000000635110640521203015347 0ustar kekekeke 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 " "; ?> smstools3/scripts/load_balancing.sh0000755000175000017500000000454311444137014016414 0ustar kekekeke#!/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 smstools3/doc/0000755000175000017500000000000013071453655012222 5ustar kekekekesmstools3/doc/book.html0000755000175000017500000000034013046431703014032 0ustar kekekeke Redirecting to:
http://stefanfrings.de/smstools/ smstools3/doc/slideshow/0000755000175000017500000000000013046362474014223 5ustar kekekekesmstools3/doc/slideshow/page7.html0000755000175000017500000001160613046362474016123 0ustar kekekeke 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.



smstools3/doc/slideshow/blacklist.gif0000755000175000017500000000063510371202746016662 0ustar kekekekeGIF89a@@,@@ڋf~^h(꩐btzf /xJ,"4d^ͩڃIYGޮ;tYL5.*URu!VVet)9fIgAvzcizJeH&G6Džt8ƨv%@ى Tl- Zh6~4 ,N^t݉ ݬZcŏoL̗ V!bHC@ڷ}&041FiXM RRK]uheX5DNɥ>IsϣiȌP7 VHuU#yD&mVNbʊfr:j4QB 8 >;smstools3/doc/slideshow/page2.jpg0000755000175000017500000011756110656600040015725 0ustar kekekekeJFIFHHC  !"$"$C"X  !"12AQ#aq4BR$35STsVWb7Cru%&6tUceA!1AQaq"23BRr4S#b$C ?.)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JP R)@)JPחkl5"~Yy-Rp`涭 #{/ {[9^|~9֦[lŅB`/a&lJDGo,!I@Z39cmXHktU!ΥՃ$yc1HY\i%qKe# u~H}7h6̚s|SԀJxd9_4k *>)J̮)JP R)@)JP R)@)JP RSM4dK0d(pddRU`g rj$TYt@,KK|%C-PXRU$WLoDȻ37Ǜo*d:r0T'R,<h4bgJfu $f4ۋCr:qO| npxY5~fMD*[hi;T c+6(I;hc,-X"YJR+R)@)JP R)@)JP $³ڟ ?e͸j#_}6)_0Gvu>j|jSU WM毾R]vl5}ڔB|`Ԧg]毾_}v5P:5}>j)`W]MT.λ_}6Jjvu>j|jSU WM毾R]viz|pQ(0(էWTz˨?tݲ&m %(8%ŎNӅG:ǫ"i40qlȶkkw&T G>QPH9 2>8(JKRhrCHƵ5K5-Q_V*Z ]vpYdFILt9!b?8R0I / Ht_RNab;l; #͹Yڞ0o|떡L2ih W#j@'q)\Na$B0kT3( u":FY'Ҁ®JӖ^7{DooBRѻ!3q[]cz@՚=Ҩoɺ%3T Ca P06 57=ԘZkQnڃWSmIiaכARPh!'_ |s@_ԪcWXĝKqoJMԮÊ(oS"2Rng飼<ǩYYQxڪStEwK  }2p9%*i.ʺEb}Wķ895Uš8Wk ϩwo2=]=sQ:JlI;[# RlnunojC/e)-$P~Էƭj\";*/Is9@Z$9E,apVmy*uwS\hHqI:"NSe,+9 N ,t,igh%ZLնVR[mvr^O I(gLq\3[okSt5IOhMnJSHS=07I@<ܵd^-P.rmOʊ'Iϔ2*4-Ox/@bdUZֳvɧeԗ_hm_]_8ՏλGhJjǀ9_SnM ~nkI`~wbSHRȮ)]ymB)m*PdHp y0FJw!@AAh6;Һ-KJEv-j@ZJRAg@+^ݻָn\"JIpc8׌|qQ}ae_z!_-0.e:fFC @A*g #?Y$ 3.߹3O 9< P+TPJRNI۹k\m--ح8Z6%cN#Y1,[.[=Ĭ,/*L[ qs9_N&;φqI)I$7\VACҌE! -OmSxAYm t.0}Pő7f%8 V#č7w3nov(}Ho| Kܳ%+KI (QH+YR9Yrv/*T7D=e -0-qKP,ÎPVR*)H8g20()JJw_l8dFP2CE6NŞ=T3t{oCiBiZ'܂3+e٪+NuF$ݵʔT:% #ww㑌guvTTKN=C{[`^EN9fꕣjmk71i]L ⇠ k.] ?.n %))_mY$O\zjyeҰYeO;w( HHDOa󬨒c̊Ԩ"; m֖G}t-+zҔ1(`^SYՃI{OPҟf?ǵJ WOlR()JJRR()JJRR(ppp~ʉldt)ۮC"Sl:Kip#~>݉׏ה}9b \f[J|;|U999Q>|X?xEҋXT__,/NXLZ χqܔeEpp(2^ ;P =O]+rV$ӓ}_Eܸ(Z=~:gO i2` :܇ PF?k}==t-ȪB<8c$wգ`Bf.k@CmG%$J[|$kANwW56e%ԧVFt7Q[ E9Zk@xFB|r~b5G/?P[Np0nD%cj=%A)un++A)PPT  W*3eלoz?yZ#P{YZB NT9&:g44k3"ʖXFRO<}=m5tV@{NVRJ[8I澒)Q׺J\ugÝֶ62N}MyOy™N?KJe{V ('ծ:n3-JA},'-ĨGa@P+@+N7oe)s4x-I A;N?9ޡ붟|n FBHH% 4 $'JR}:*58i’* B2FPԏƜқ%8VQ3^5&ڣv-S1K3!K 噮`8}%KhBԮ~J4- #^fϴۻRSd8Z Q` ѫZ!s+Fk}ֶu^lШCyz/! ~쇑mah% lXRRpATLXUer{^n7yK5,Iڄ%!N0 <`dN-J.cI;.Ec $2ϗȐ8J|~hV=Mح- 0Ŭm ᒠB?!Jʏ'q95cޓZv}W݉Gn-;<\N$g֦y_zmVIor_b+ ~KͰ9S(%)IeRHm[~INɻZ[b+ RKvۜjܕ8DӽA9w-$!'jKIyJp~B!)W)Q5yJǣJ9V,>[3#0㠺grcTYfCDO*o"gqp(8X؜+!;$𜑲[-#W)PIJČV\Qڑ[x9JPnr'JԮI!~<QG˷9bCebI3P=iǁS!rZL><+8ps`kz–c|=7+YeŻ/]̴Uɧ2!fO*lq/a% 9WzZ,#ti,7$+ ?!~H9)'X Yy\ґ+%u/ܚڝ]Hkײ%YU3r )Nr +%$j/nzhi^ H J▶@qO)Qz[lNmWjSXDkV\擃^B_.ژ-bp\X@'?MgJTej֎Mogޙ]Zv{LKr4Wn>#JZԒ_icjSSQdڟy)l|KU 26(  TWB _aGn\LK NJͰjHy#˩m+v mx-@|jԂrka^X[,͖-rm0-!aE_z@8VIX)9;)vH_V,9V 8$Ȭ='aTGŦ08rIsėw))08 #6;z{-P/:6dEd9(!hRAlP8'Ìu7ex%6pJUNըg}XoB{uwU /2UR2{?Eu9vfc.{nW -%8IJ[| 40ծo1 ;%-~P'z OUŢؗ`ssW6,jS@ nJAze$F =ƞN-9`8e'/ ;RJ?RS$(*3uMN \;67oeA‘rCN>9 yӚ?}r4[8%9R9#( z ,{v7%[6@@D*mMlBԔ0ypF'.V[ˮݔw,*i@Iu8(#z=T-^mg[:{:rSV2%$(q ꅝQՍ[ Y) Z] qxQE'tEJ*-Y<*{4ˍ@qZr$A85u >kmWdLL$ȓ J<^9$”Kj+:o/}Sߖ^ ۇX@)AJR)@+rZa*m|XP@S^KHeJ VUE:S6aLI˷0$;Eqgbˉ!E$7>c8EJVft$ff{n=mvϟ⺿~jkm7AHH|pqUM|Uݛˎ;]T%:\ qg\6sZ.WRڐmK m$G)g׿,-}o7WژHԖfKYq\`+an Etet8P$ʝlϵOr#w2X6*GuJɸgh8vi?~& !\.=>ie!Ч dJFIu)(5TF{;|ؓҔaJRR(p~PH(էWTz˨?ݲ&m %(8=Ŏ| y1ǫbi46.2mmnʛ|Hѵ)eStu/>Qj$4*kSQ^Wp|x֪]'Y}SHd8H R ԻӪeئlfhlV1Zj UN!FRQ﫭cW:BVzMKbefL<̭lv"ޛHv(eo&o=~2Ad| i ~s*l !eS(Ɏ6w q1+KZ~\[rCKf_iаJJn}O$0֧V.]0Rn2*v qsT?rT0 Iq8G^+%WW2J\YkHaX#AG5PihMqnCN:x;ݨO$y>PZ[%v_vi\k3OS.JllځRHD4WH{PNu֩nӫV˒`F09(T&]#Y,Qq,=UmJ׽;ʔyp8Z7)BsT> aQ-) q{ҾqZԎ߬۷gX8% OZ͟nt~BɓrieXM( )@Ҿxۤ&#.\&p]PPeB jH51ܧ.f/J^q,"AŴ lyrU$V\ 0kkumV=_Ԛ{ΙӐy՘2V[FZd-sB^Qẙzv433~ $)m]E**qr1dN?2(񴧃MFv/3@blUJz;w5%Wdbf~[6u%.GO4l) I'SYRd6K1L}I;e)MJwL@)%;@I~t niy?UiV^FN$۴Qq[הl)MMB$mP%Y)YpEݬ3Є- eyZ]Ӯ%%jN۵Ux󩧎 W&վtՆ4S[#CGq =!nFy9ZyN^+~G ;;'%'ڷ[lRёn[Zz|nruԶTӧsdFĒӐME$MjѶ[z,eB;/  RwZ`ZV֖DKL.j%A9>eyr@ tfUor˱S|%BCWN)c⫬>/Vì9>9,{wk&^޵E2楲]lS;NjmΛlaMRBB H cFm8nV/:}N{غwrYۃ)(HY\,n\t}S[K~4KI% I*lV'@8$G'Ҏ*ޫDkWX/ Hh%4U! 0sJVPۂJܯu6'$ԚvSmKs7Tn6P ;+s ]s ^ oܿ18@ZCOY޴9DK0-d9q1rT-+4 M:m}X2 zZwQ\`ko3LzYRNГS[,E5%cwXxi$4Yim8WWA'5KE-W9j]4췝o/PcWTjЗʛ/!H. Ok%mߣiAɻ9)ZS;K|qǧXIַf-0-h4$Jk0xφi++?@kn 6B9b.zjƣQ5?~k7c 9ݹ?Y AmkFgO@اXjwII#('VTޠMzauT%ڢ1UqJSBR `4OB +KCEc]N[aU6#J8 y"ݢQUMW ܡn>>nÂT7cZ#|襦XQ{^q i ZBüQJ> m!j*'*%@Zn =zvS%.i z7xtVw5h]xc-OHSW/g]Rw6TW-A/LNvpx3-,!z$Ps6X8ښK%3/w+'q*滻gYm\̐S8sUu Lpu\t/,fеԣ¸O'}+udSzJ Wy)@)JP WQ%Iy-KH*#&l+ʼnQޑ";O%nRR @ Qّ;%RDԯReOEy.\h>hYB[t?eׁ%dkMW{X\̥c:#PǟKLm+s3hY@()J\/?es\9[+ib Cѐȱ\ߏby5NX¶bǼD6+Ncx}"NNTO5?Ojtx#Ɩ"}U'n}5KӖ5V1YQm"&\#isJ |N~@zA5Jܡ*4Ww.ZKT=x*Șܖ-然m4sʵJB),j졓wՀGm 0xZ]Pclvu>SYq=~<Qi`g3~Q}#.:>++R)@hu&Ut̄$VSJXPy( ΰ#U*UmSZr5W=IRP_k_%?#U*Z}vysH\ݚPZlcO }q[RR()^*2 RYTdo)렵jRO82lhm:PN3)7ȥy7&3ے -6q+*@N kV-HJT?h,ԥ(@)@)JP $³ڟ ?e͸j4إ)@)JP R:*\C1]KJx%qe[V)H)(|#=#}pvBpsTevެ`^j8VLӋ*'vj$npG}φLE#Cj-Hc$VӹW-e9>n-;J@g"å:el94Γ|c%Sw@'ʧRGCnr2gkyh)[č#!~+Άʎ+>0ХKV{j%I?ɝ.O&J~p{mEJ8@PIv䃐I[tj^[kQJR R\{쨖JJ "iU^0ca'1JRTnJIoKJyKQm$-..ܑndךDz<-t-%\PTrm޹=-&(6mJ[$8JIR$+HRT29PҔ()JJRRknVڝh5i=%KF窦핯kFp64?"._=4N}ˉi\rSL.&╫&J嶅[JtVJ(vf)JW`)Qޡ7v"WnCIqlo# ݁8 q۟,Û%-1J r:NDyvfaTVEJOTa&͒w"1*i ,B{Ӏp/܌Qe!/LE s~vR,ҴtZ ]T, m[@R'ːO'Y'sLk*LE)JJRR(*Qhq݁w HuO|VS9Rx#$G,e^^hHc76,7RW!ԴP-n#rvpҀ*4fU FZPS O6IXܭCo08!pz9QVZA^zl]$H=6Ǚ7TpYIAJ8Q9>ɨTʓm!V̫aEC/bHIRm"@ї=/fT]j}$/gjBIy q(p Tf=AycZ$Y:4dR\}4F;A<Ľuets$;%7eTڈ r~SH,eI+eRQw:MOm̊V쵔$4Ӌܜg pBuVeϦsJYIɶFEZ:64Sd61!Ek ۜz멍\귭o7 l ۲-orK+3D2cbSO'g+JT21jR,MJ Stfhoĭ1K1yp.AVA:yx%w !qqQyx)hPNvٷeAq! .ۄ~ (+bEoQ>|bj=G;2ܭvJ-t+q !8 +vquRX9Ŕd}twi6ډYTPW- hHVS7^'Ɩ-΢sL;"BxTǔT^+4waV^ubw.h1*ȘC1y˧3][Eȓ&QڅnӅ.yW 28"ͥGIJNar")+a,!DÎۥޫ$־:S ]DeYT-^?L2|$'jJԏq[!6MV[9|^x3!YBVGƴMr$vnk=-Hv{P’3͋Rq:~\.rOTڗ ~k*JK%EQBӄY26)Z-VLcm┥j2Þ+~騖JJ "iUT^bHѲ0x+qA#jqþ2sȌejC}2}sj}Jo4_2^3Xqv|Թ(B5:Dng+?ςiJҾa+CNR )ܠx,UNN1.Ғ~pDei^`o)ZdG.&9#%J ~S/q\_?j1}*Kvr ).7ڣ 4N-: ˁ!yG#NfWUۈtLd2"$9\Tgh,ti/i9oA['HƞJB((R קw0lkmkT%Iz=Y8b)hd}=:+ vQuդˊօIZII^3|պoVins, `5UBizJҜmޤيwG qB Gɯ-PO{PmWG.3˭+HƸ5EqGʔW0TY3Y@QS:f4 [e\ƳBļ鎦 0@Dnt~6@_ɒ-2b7jJ{kKA ݀ 84҉QC)E}p_j"s4RzQ3iB#i.J[AOwJ@R qz:%r:4ѻao\- i7/1,ҿ5WQj!?!=Wvtf᧮z.U",qR_spzm{"+0Su. R 9q%&d Fi lK%%!82~Q N4S_|JtA+M;#z:ե^"GPb961TFSje $lmC卦#RCBTTVuw€4R5emVxZe) HlVáY/'SyJwWԒp'/ -h$tbw\#v qg8ϩk}XKl6)+>.u.aH8 Q6(ʾCDcjnԻ*[F/Y]t(IoVB꾢LOOZ]m%W )ڜm>>n4f! 0q'0\&,T(LfR]H $*$\;OZ[˿bOt:.q4\~néW d;GV&.=Zk3V7MuM|ΧEDmĈ{,-9Qs#8I6JAc2C w(uRddhVCB  @*V~uK<%Y:2~9GFt_w֌w]= +﮾.<38Jv8!U֠[ëTb/*P-%))YZJNTnCn1őP4}يh ,Եd9ڐ851&;E+HJ Z*(9I#%'>UG/S.%.:q] As~Ww KHqheŅו!%{NV0V-ڀes=ܔ4Ci  >ղa 1Th))A@g8+g?R]BМrBZ~yA\,  *NFLI^Zrᤡ)JP))P%*)$Hʶ9IIC>) H H'NJэiTsJR R\{쨖JIa19i k,V^?ñx08qSҕGN_mumWTK.Fx)EB Y/tiii7iE=6~P4 +M8Z~O+M Ok_&}<,u^9Z g}p5m7T.,]㱵"s`JH?an~O+M ?4'ltmFR"fqv6%ONv%Au#jHJyn:} 6>1&lK{d"-6T1Wt~~c4мV ]k5u/X%F}iGg͵IN?zD!|Kj'$с>o_\cDZ p5{'Mh }?Ƅ9'\JopH_Lz٥nj_,h81W[5iO+M p:4N?@TH>q:7[[[j1{xTNʮ 4d 7C4:'M~c5lvb[LuEC)ZAp6$0y( VX鵏\IK𜊟 uBOu󖓂1VUkn >Tg W;nܔ$R̫Լ})]NJ-JB[;VFBTa9΄i \)-!ABRԆWJRNFMe0H~xY$N/}KZ뼭OI,tIꝐ)lmﲢv+&f-&֛i؅:XB(I@IQYNmxY_QTeԖϬ:Iov*?p])]AEfv :!qG-[Kh KVV}j]u3-Qg]QRCn\m!A!Y#ԋsWiF,7HSjqqe[jJB GBzdQg$4rz;3d9=kuW~ɾ͝}J61+NhgjTt#(ٲtFB6 Ok6>gX[<#|.u[}HjK> KoۋaTTۊm`ĥI!hPJVG>Xl1oan-}ǔqd)J%kQZf)@)JP R)@)J"+P-n"*QmZ]X I#%deiIm39Na.CQ}*T5\'x@Alv⳴$\Mqat;1sS[ÉC$2+QH X HX ׏cVYMJfc-K+C ++ׂ>uÝG$\DK>Ґ p-T)) ۸'5)()JJRRjeBn|Mh?<㭚Vӆhw/?Hrss>GR;㟤i9FGF7ԭ4%/Ʋ FOGks]JQU [;mSv+5VY\Y:eTRG fVn"uvy/2[POzC2$CsIٛTVK*WU-J;eUk*,N!Ƒczw6{dԻr ΖHBROЬ$N9&]'4梾\W[ lY[J^@*8ځbFI++9I(8JRR()JJRRN]d{+~O;0}n> yWvT3Zla(1D-u BO˃uV~ζ\-XZV6؞=8jj_V%UGj@Zm-\(y=B}+"' 6.q/ޯhZ0'r[s`I .$dCIFN.铖mF_RS@{[09Eg[m53rѕF*sIQ ^< s-E/vy2$6Rۏ$mg>ǓpꛮrwSb72TvWjx} KyI-1Ljr}=i]M:%rCJ/@'>VtL K%.T[K|dH$Q9 +ח;*?m+6v֟2Ae@I%r=tR_i[?Ov>[IWm% z{j&ZGXl/2;xt.- 6*$@|ATw5{k&)5k>N Za0Y#iz#Qڠ_u3w l~#al=,Pxs*RKa)ܶiT6uv%7L7sֶKs+&ՐKgpoP 7A GjZ$;yT+L/ģO r>bLKBԕC8RyP)P#Eџ7>|"o/}e΄TKOx$-+ &*^lZqm JP J?jkVfmFR()JCM)$D(#OQgZ.5M- =Ź;@ @PҰ BMWS .ݮLT$V@ZB.@I*TZv}T|JZ!ِGWT^ʛC !i*^bkm VF<&uȲ4bK^24N}+)d/vv SA.JjmP\#KrQg H;>J *?ӾKbe*0RІ؊ܦ2Ty!JY\aK1=]qe hRT?,h-"`lp65<S,z z!m6q%[[`X8Nf*+ٝJ[,TEtjLwd=X :rPHTp7&1J'u* BDys2;o9-p^nԙ.ͽYR<;m8s8JVI9{eS֟٥B{җn*j.nޥ)eEJc '8ZR1tfk1JR)JP pptKc%m7\2Xz[(v44Iy^d4BY,\F[^rlΌꦰv8hmG:9*Ժ7R\:Ψ>kVؚx!%J{"F *vŴIu$Cv"d.[PA9 R^G(ʗ€%_IqJ_%}z GT<\bڽ=Yu{ݢDhrXEu#-\D66ώ(D{jTVTkQ%XT=ˮMWkICRxpT@'' i !CΖ`ʸBiFݧ%Ĥ PGוO͟,FєԜ1bE[z.ԝQ]ފPFi# 0Ԩ;A 11@u{!!Z=)Zԧ]BW%%'?5\›V(mămJ}z.R\^|CԬpQa\҆^"c=.jyWjțrZq!NSn( (-c9|Tq9Õ}ɝy-Z]qSQIK0ڹRxh*H )5Mvcшì66(+O֯B3546t{MG ߞ0@L=<~*SPn)l-TZCoiˎjUVG9Hu[XinZ#ԓTRjqfM%d; 3!w' w Q+֢nˡ[HZA\_)K#! I0xh gU<9B>K`)leA)-dfA%ЮW/Ҟ[lfIm$, AVfأk[W(/n.@-8oñ@׾(_v QCR?ʹIV’rxPNZk0~wqPzW9'3˚xz!ؚ^y4b0kQ J@rI $T ͚e /*zmk۰pwqϯRzwS[vEv]$33/v/NJ;:9VT]G&;:n: {Ec dudNRHSQ-XI779q-w;tA0ɚ^ &VYhO|~U٩^uËɐP%ֆܭ8Oph5mFKqKv–ߩH\57yPc6=2qQJXS,#rIϫg^*j.xpfxu.*ӘlpIvGe* N O|!Ljߌ:վCC[(9ʽkBcݏJ""4w5ƚ/Qcm1 I^6pV35ѷ'0w39f%);8R7 z 4}a\ڠ d/2 9>\q9j.n7[iw*3``6 X ϗ8"U-vio󴊕-(Ypg?LkݢLhRܢK1\CQ(Il^GG¢wYM}b+*~4GA HZ]R D2H8"\R'?ۖEWkICRxpTNNrZUIm[n8NJ{Z AnQ{Q0IHm_%1\y2n!یdka٩. AIPRǨ ~8?#TF鎳{1OD́ST)А6pTp7dk?f7IlS^AR<ʉJFT3׿ٿ7'wuqW$'owdu_uk,#-kXe値-ekPz:VF4I{Qn3%+I@R}I008}?JN>0RS^+dǖӺ63BXm#z ~fGdE#m0|m;TK~CiOȄkZ"@ wrږc ]_ m>jj=i.q }o8~äewRjq}|5E՛؎`a hqzXn85JUr)JPJRR()Jv[7g_OaJV8QB-SvAbj- ^`{ﶭ[J2(>\G_/7kEBje)JB$ cr@)Fr0AНF#f0̸.iPNpAP#իX'j&OQm(Qyz˭Юʺ TNuv$ZR|{ӊ#~e1uaOi-$@$]v[LTN]Ny}{a;D+[fꅪg&ݴo"]6TegSKΘmp&ߚ DKχl,OqVD Y\zWa8P@QBҬnBs\нEElvӌ[Ω╍QGր9fk-y7%WM1z|-wpcpQUzM]%.V":2R{p)##U/dIm<7v5Zy+B] ~s 8Qo]?E:;q'Gr3n0% IJw|#i{TJt\=1 ܼ.Dn1댟LNXœʻ>Kum^ߤ^F}3FuV~mr$Df#PP4V,//Ёn}Ac%"DxDiV~N1=0-wX&ilMRVo%$6``cV~b|Nbݖ hSVEeKwguHZ ڠwv㲏BBR2U@+>סSk uE=ZhF c߯Aw"n^IM`T奐;9)5'F$[E*n!g I%%*A@W:Ͽ?#|sN7 5oe~Ç _$i~mg*CAȕui)S D˭1#JI.鮘{YkQQo'_)TޗurΉJ nDC]oJdSg;=Q!{Zt')mB*_PNFwn)k!Tӥ@zs#.__Na )a 2! FV)XϪZ|ܭt )JVE)J\9Q-AEڼ`"iU_X;Obn1>qitm>wWCO,ΐIN6GXǨ9$+CaK]hr*$6 *!-c$$Nt|q(O:+i,Ҕzcұ龅z29JPrJ[KIJ|Jrz9[=ƥh3Z_QCXs`mcrTxh, $+p3RǂruN [ 9-#MՊ+JRcI~NS5"%kz}FTAOTv놢IJϤlǴّ \nn[e{cC-[gnp  4؎8Ćke*@?Z_- E[?i_y%oi]SA% :RWuۀ9K5t()uvI`$qgpB$6mb;yph*7Ph#3 +gr}3XsEix*ԋڇ'r!Z^D!'v03 }Ĺ6<;]V*pv[i!28'n8bekfҠ5elS,)7WYT'*inB,dN2qX-}> TČ+n[RBҢ O9v[{M*չ !AmniaQRSǾ0yUɱ4ML6wmI)NՃjF[ kajWR┥Kj+:o/}Sߖ^ ۇX@)AJR)@)JP R)@)JP R\{쨖JJ "iU^0ca'1JRU3{-ߧٻgeYimk%jIRmIKK+N ֜lNSVZYZnX)d&[gǙvhaJH߽JMnR>u\9Slf VX)[M!n NRrx̞R()JJRR춭u W J񾟐?YN*; Sngse()3cHk'WW4M'BujE&l*A R*1Ŧ=hJ)#wqʒ Az-)I,!p{ߕ]ל.쑑U]fXkDiU+[ 6z32ҮA|BQGh6obk%6柌!4F@vj}3!E;I<0aU2v'5L:3do_yOS(snKʸ /Oa^t koW*L)JJRR(*s0_r27"R=J9%}rbYKl%O]Xmm1KJз2r+dc4i(MIHqc lzgSk.bS`Ŋn7+]OjSM$/jT 5wͯu"#Ƹ\| Ke9-Dc3(MϠKMVsucmn\)XpaA (P I$``"+N[.Jۙb-[L}.[t-+sw{xbܭwfOT^ķ\'4.KriDv8>@N"mk1v2[NJ$TI*$I9$}k$^ qO<TËb/NA)͡QN2ʞZu*qa(ڐ@* hviE2f{Lm멎<)A(./dz p1@nY1a,4<@mwJR! z0*M\>=խܳE3ݛ*Zސ !Tpda8'9Z N@.קݳ~ſXL"*ZRHp,yX\ xR`.sm9Nj30溜 ( %?o\iw"Ȓե6%n@p f_W'6X2_c! JsNr>U'VY'eʹXuMO,zb PRhz")@)JP R)@)JP R)@+k[+iۯ#MqtE\Z` Il$ R[u-\FDM !Kl)\)#9 oPt=^G-(D8js7Nr}pKRbiKl$˹T6C15p=TT>J)SZVKB.i)5fCھ͇StC5qHSu xY+;^<$'$lbk+4W8l2=RTB#[IS3F*MEGRYdn {<&lS>1QQ q'ggVu{]2VTYpݎZaa}[I_ kޮ]t]y2jI%!XP09]ph)J#PuY\gI/Z]=viBB6('vT~ Po[nw+Z&moW.INݾ7N_Z~-lqX~[ n@ڙqہj[ oʳddu*݌Ľ,aevԜ p*Ev{N,7'.U(Y˓]+?h UGYXuK.˯8 i$Fn٬oMS %*Zv;ڼw𜌞*/Wծz`r[r"23RpJXH[,q/Ajl<G#km.ha狥K7%+>ǰ\2\ES1f!qK'vp8!@FH]R1 q@ˮ۟[ٹs0`%oLĻku(fۧn$Y}Jy#'oC,դ*z&Aɒ섐@z%#Tc/<'u{=[e}]K u7I{&-7IJLY Wm!<9wVnHi!)qXV<BPHA" %>rW*MջCjP Jx%S]ʏf]iob`e[\y mBUԕTFb% G;vhxmFQ=gߖ՝mo>< $D2BQNTd(,|DVb֏e1%&̃)fd))9p!dyy]aT٘%\\IK l?kK9ABF@_] TIR{E"KC!A\ },..?gġO$sطD2Ȍ-\wlA {[;@I8LEEqW-K%)I''hPczm95Wźۈ}-Q ݸz{ے6OWutٴ[岔:<Nm`8+d,y7{rM ]$b*My~iv6X7wf!o;j{UIkjLn'v>>: ĨFYGYZۇYVVA98 JȀڰj5ˉ[(IS$:*o/+rsROӚZȰjF rQYԄ"H<Ҳ\qo6d4XlY@q? :P2f;(HIwBRB CNϨj!rui(ai$o&ܶ6xqR()%Y+ Uk&wCq+w])-!C%((z8ȮA ! iCsV6JWqJp^bIEl6kJ$j$r-JR,Hz}VݳPpV09sJUbE)JJRR()J5^԰pfDl}{FƮK9O!$mEJ5Yo4 `jRm+*պM꩑^c[Z\k$-<>Z;}"u{7թ}梞C- ڧ%*HP  Ժ=SoC[`}%yն ‚`JKh+)<3չh& biǒ)wJJPѽ*;ѤǾSn棸)+J H/8 k^c[ $Bz'{~V1;gK%DV1^ugo9;SKૄ|vZC y7ͩMJRRJdgq#l7Ʀ!ԕ- Kh'Gʀ4F=J.e;3% I$oz[MK-S;E)* q7#0x̲M J@8VӍ1nT݄H1^%8x? PSqsE÷e̓\P%-K p+>vl 'JN]+pps[ ۬,`C H9'ʕK} n:g*|O-eor{UЩH*˨xyInLBX* n'nFҙ黺cҠ܌uCR-Kk.RNJppFlWL8%*JO<UZ.%8ZhWfFT񕯖e/kF+sH$e=DOK]q4Ԭ-"\>A  +k9?/DO짣»=.MJ??ObG')?GKR>ď"O'V#-#W.P&;O(mMg;p0]G_E{q0i[bG')H$e=ވtjVؑI${??OGwq4Ԭ0]yrow%x5rRnݠ'e:֡А( Zgu&Ĩ0TLߢ8;xQZ)ӽB vԦ# l0 *p)%AE +sK^®0ԀcչJ]MӦǞo]uA6S+p`w'^.eۦ zfnlDCT `;o=s኶@Vq5_T؊]Ғ c|?65%L~$Ob}K9v!2q%┢9~Q|u*,)})~–ߢ${xC֦:)'n2&h̷[JBJŤ 99ҤԠ(jj"o5Y0}q'H_T+m(n?:n?:c_=Of5x/?Y`nWΛf5xc_=OE_܇?gFy.%>YugGb0iܯZ+W5't)JWFi"}]j;J۽wee켥odkjZ9yz:%6ݘǜܷ}.@܄-;I'X/>l,b]?[>䨹yVGVz߻>?:V*k?yR9q%ĺA{Z.KxbaWeVͥ ‰[JVJRR()JJRR()JJRR()JJRjqDmQXoCfs%ɰtd##>#?mgMdȄpԀ~YV !غ_CiM ,H+mwrO$YNb}b1&]zjc-O2FLxY(<RϸG#sUL$7JJ8$~<+$&Df\WwK8* 9ی9JT͡JA!!Nϓ)989:Q'N n/;Kz#&keN! RY(͟L\Scs'PYc]ŦE;SJBJV1HBT@'$k(φ!34hF|p YpR@*/tɩ0WR ޤۈP'(*R} ؒ2a%k J*¼&9|co05 8y%yXcZpvr 79yhR)G'TNT+lèy% ?5Un ԩ1owK.2(2r$!9>d4{YxlYhinY$S^| tpgŹQܖVJd%N z+;sՇ SQ%Ȍ% %X #黵}Fď 1ʔe)X󌏐##96Aɧ,PCMp2btF23Wl4xSCZmHހO2~=6&QoɂK+܀cc>_Iִ1reÁ 0Š(e?BV>JyZ0ICMlwu1l}o21,d |Eq ڨi97tRTO̟ZA`eOc8t1{e]ū X 8qY7-ȯRiJ pU5ɗT$FTt vt)[I#E+A؟2 ׿dŖB^pܺ X_@=.a\zާ1}0q |Js*akEatsn1ͻ'w%' 5YY>cØmd:%ǐJd%+IcvSzdC;5iC`+"LNRX x \ i5 33i- SZAwrJn.)`UE}B-^o~&3ąS` ꨳYrj26gIk]]dI% mIWmEA[тHFprvO+VL̈k,ӥRR%DϘkdrַ)ŢC-D[QJVوikV̕ mrS_or7 S鈤-er 'r7dn s,^ipȶjͼ| G˓nKpVDk!gqHIqld#*!hnZvر q-,8X[ Z[!IRRCn'_ܗz|{kMԍ~< Qbv%{(+#9'$әW)zrZC?u(llIY +BOQDZ*l%3L 󕶑gXJ 7-#8ݳ>krܜ0YOsuR ѦS ,}j)JϠJ$2HJ[QUQ)4ڷđ4ڜqiB I-L;lL>a>8R-wǧ:{ϩ/1Ld u)QBAyJ%w3,gv_jmjB9rH&s){Jޣ߿|QqajٳM7ܝ`#S=1/ZNwŊ`}2>8 p$AB# CU;j jvݫ#6mcôRZ w8Ye}盋xk#8ʀOZ&G. ciQ㽓*⬴ O*s,m]B`O{O,&l+ڼ> }ST=)&2~ŗJXn_3) iр0r=s~u.X[fa3ك{48$%n% RO]R5z4kFuRpNզmW6V-h?jlU e.jlԲ܏^=w+'E>۪08m@u GeY+@N| ԯ' ]WKvK:ćXZPZR; <)7Mn6M%mʌ1*_y p$$4()JJRR()JJRR()JJRR()JJRR()JJRR()JJRR()JJRR()J_-HF}G!l^%eЦT/y_6}KJq68NV4GqWY۽{+5|q)Eר[+}JP;A(i}Bjt7[:C.!=/ ; ozW<\l5{:z:n]Uy[2F3-$H 8^}Ej@n.VU௹.3*٨wZ2z+]dz(1-C;1=kx<,d䠭wRzr>"sZJfC\$X)eQ=-gʇT$/VAϗuxZӷ KmqT&a3J_=8S!YSvo?t!"O?`WEk{mm_PtܖS J EY!x)9YoT vl"ݦ[ԉ->6U6)h FRw ?7It'VJzC [>RyѢZmn'hVw$0M_2\o+L^[e+2)o>U:[Zʓ6@* )*ݿNmi\"C#&3ZʶT %5;:1ŵD !չی\d쭎qqRMwm,TuVxn״vor v_ [킼˧5;g8cp8OۏOVτn3ϟ[>/6:Ų͔ T#(J@sUM{:rhhΣ$[mkX ڔ*2AyUM^\Z#Z{-rޖ>Ux&1Nֱ7NܧܕrV-=ArWs2i1]YSkj•Fc\uQ_H:K}m%[ ނ jT<=7)WQ'눞.׫cmglxIaIqaJt2jǥ+d!{(_VNfߙT_5_W˳LHmUfGH nYP!zmD+юoel<6/SoiR_qR-e7y"c)tol9Q;9TB};[uIyN.EJ.a.mAchPiHOIt{:PYj :6ZБ *N=lQm&cMȍ˵;xcP տ3/.rs}!D,% ^Ԝ6[pz{l>"ܮa u78JJP 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
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...



smstools3/doc/slideshow/eventhandler.gif0000755000175000017500000000063510371202746017371 0ustar kekekekeGIF87a@@,@@B.l3oJ&١it*_RMn`l>^&jΩ|W @2 I t}G{rG#vFf4gtEŧ#UxTٵu" y9ttG;{'xۛ!'W!ghbg\,-S켜u(5z1C [*]Rmm;٬ l\+͌+L&;+1X;7E$R zf2җ!}uL5ԶFP.CFF``墓qWC 1O0d8'y#8F%hZaemMJrD*"u,дڽ.ަ{W޻ |;smstools3/doc/slideshow/page2.html0000755000175000017500000000703213046362474016114 0ustar kekekeke 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).



smstools3/doc/slideshow/page5.html0000755000175000017500000000777613046362474016136 0ustar kekekeke 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.



smstools3/doc/slideshow/page8.html0000755000175000017500000001015713046362474016124 0ustar kekekeke 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.



smstools3/doc/slideshow/page4.html0000755000175000017500000001102213046362474016110 0ustar kekekeke 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.



smstools3/doc/slideshow/smstools3-small.jpg0000755000175000017500000001213410656540223017777 0ustar kekekekeJFIFC  !"$"$C""?!1"2Aa3Qq$8S#CDRTrt0!1A"Qaq#B2 ?NFڼb l hdygTc'+~vU]s]HPMGDP#*]XĽ=8*| Gݏ?K?+b+%U)E3ʳyr  Yҧ/P&S_nY.V%#I ^[X `.قX k$>};eߎpdk}=eeC̝ gLMt)t~?N8\g:M}jZfg@jYE@m{߹!2ESvGjzXg12\`A׵Ӻ](c Dx ?޲/w*inO]7Uv(ba`J&|Ρ[OKn +[OqGoL8@~:g;߾lSӿG:v둜v23f~ ;j()j=.9&+> ,a:Ctgcu xx]Cl2p^EW^HRԳ*bjۿ|=߼+K$OP)'@ᐈ©#~#Z~Uepܶ';,q$Kqh/%C32?/6wI붝~Y{u_U8(⤪3 )9RUVǐ @9f̮.< v`TW[PLҙn/R e @$};x߷WϤL[e&ꨊM]$!XUr;xI9n{]JW2 c1A0hYu8p=K}|6kLn"qEC1Y"OBA%2|@'w=._%q赵P NɑS"dn~qҵgO~]?}?>5ݾ(iz--hOM%Bi^DaFfl/ v_ZW[~ؕ~` a`|M#Fϫ{M%U`_[O`veIz/̸hqڷjXFgY8*[`HǑ(Nr.مu<|:${eocɣKe{p[ X# $YI;7 aAV\$sk}Xި\8MC?_fkRv|H)=mب$?O0 ~}A|MKK [ݎ=W_lZQkG+0+u*Ϊp 8׍%g Z6AkWJ#\*IcqH~XdJvX{BL}Gڳ&d}ou~>8ܣ/Oso>?OQ684EAu<cdBxguJ*g˵wN1 o3;d $ cx"[wfR{ʿO}OXz9f>qZGU80]#`crqިjj7ţmnN5?R8VYkKHtG$N|d;vVCnTO-Zڞ$!'ܫlk/ٽ=]M4Ct!vVKrzW86ʉ ib+#mH I8<"D SfEW+#Qeݜ`e-2d^=0TH0gC ԗ?#}󴫦ݗkIs{1D.#U,8ϝ^F?=?qk$K :L" lK9 IV7+t* SP٩eƟ 1pO-[.*A4A~H YvV>]`]bQJ#Fީ&?2scH䆺5 d1 TdtuQK`:Iu)s1SoK\4V6ߞNrΊH NNBpBKh+e kTJzz7 nT=9=p Pqe,$o fU\4>߻*+J{ȱe.31ϋv}VԢ82ʤxIU[@%35~UΒF h:ُz9ɲDVuUUz]|| 3'߿#V&\*4":gdl1~tqNfߴU衽X$(I#) A+@0͓X V\'{+rmں8)\urEr I;dˁ_guĵB8thܶtVT,_ #Gx#rpޡ]jyv (H5r ܷG*Z]E<LDufQ[!NJ9Uy(d@57R418V#?>|ρױrKl/;`x㨘F-i\F\ul$51*\,F tNye!E!onFKp>>u]nNc^$۔4R yYT ʑ[IcxF 25Wp xzYQ:Ԋ}[nC޽lIo9e\'j..ˉwZ\w(" HH!B)c#oVߵ/%MJ~}n3}c$y'?9:Oqe[6`W]ۮ4>kۅcȦ#VK,V٨`N^(jQ[?ustTޠۘ`}(?5+Fٖ=C 4TB{@8 kwҾRRbDT=;+ ȭUAnj=z^VnBf U3LlN}RUGdoT5,v KF=B0NAˍk{| 'I͛F}Yo۳p%[٪穘$oc_qF|M,VxpʀK{fu3 fR9"MuX 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

             
smstools3/doc/slideshow/status.gif0000755000175000017500000000036110371202746016231 0ustar kekekekeGIF89a=),=I8!dY`C뾯i pʺ vx>hRRV 8z+`4>}ࣖ[E7 Wmjj;kwr^o7e$ijulgsBRd}Lt/vJabFGCTI.]AQ8H?Uf:1;smstools3/doc/slideshow/queue.gif0000755000175000017500000000103610371202746016032 0ustar kekekekeGIF89a$,$ ޼{|◌榜gK벰m1`H%Li2ۋꩲ}>(9=Wat\s:eh?:g&ףGf8h3'wשyWYy *:p jJ [0){ŻY|E+u\ ) m5TGwtqԹuɦ#)n;8g;Oy\2Ƶ/|(P<2ȿ`ߞ!ۿ??;smstools3/doc/slideshow/statistic.gif0000755000175000017500000000071110371202746016714 0ustar kekekekeGIF89a`@!,`@I8ͻ`(@hj++mR0pX$A`<*E-P' IJ-ldҼt7w}JQҺvA+NGJl*QrlKYy;{VFH#8趄HyDEfYٶ9V㢚$k"aKqk[! ,@aN`yEca5z)K$jꇽЄ4 fˤ1ńȊLs>G*bqqwEh:=,l`cفye6bjs]1zxr>#؊"L$J5S+ûb]Λ@O6 vw;=-[3(U՗eӧH4IDrCTib2Z3"3q1 K Q! ,@{R4ՙӔ x!q9Z)"ѧvǐ|_=rRXC*Kg 9Z[jgb@b5:nszoj'7ԂwSHyHs19IS"zJ"Q P! ,@`hN ,X'op%2v~`:7˱ָfDNU<2SidM"Lv\aYl~t$|_wW4ԘGd3)VӲ9Y!:2jS*q jP;smstools3/doc/slideshow/move_l.gif0000755000175000017500000000161310371202746016170 0ustar kekekekeGIF89a@! NETSCAPE2.0! ,@ZdsUuSFlQ_ZcE޷,G2Iuʁ|:/sg>,¢%zV%wc;_-jAkhxZ_'8Eؕu(y$wid)e :ZyZz#j;B"aKqk[! ,@Dh^dkyav$6V/xR<:/D aLǃƘͤJ=&Siͺ;b/I6h:=e~>due؂H%5Th3 Ier3zZ2ºj‘)!1KP! ,@o{tEV_N[vu`T'^e|o|詀Ǹ$DDi#tɱ s;ڸ18eN{{sǎgj+'$ƄTgpc(b%SScy12)yéRz2)ںP! ,@`hiȈ3Iy9:g.urjurcߋe Z6#/I1-%*NSkKMbL`󌍅3t(9{tUeTyCC)Ă :J"Z*[Q! ,@{PdSVjOa,l|,5w[M*7DF!GTFEHHNFw~a]O .v <0… :|1ĉ+Z #ZRWi:;smstools3/doc/slideshow/page3.html0000755000175000017500000000723213046362474016117 0ustar kekekeke 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.



smstools3/doc/slideshow/page6.html0000755000175000017500000000725213046362474016124 0ustar kekekeke 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.



smstools3/doc/slideshow/modem.gif0000755000175000017500000000045610371202746016014 0ustar kekekekeGIF89a@/),@/I8ͻ`(dihlTL߯T׮'GX`:!؄2WtFְ$`4ו=<^G{(h ;óƺпӺ͸Ͼ:0EE;smstools3/doc/configure.html0000755000175000017500000040051113102373365015067 0ustar kekekeke SMS Server Tools 3

SMS Server Tools 3

Home

Configuring

Menu:

 All global settings: 
A  adminmessage_device    admin_to    alarmhandler    alarmlevel    alphabet    autosplit   B  blacklist    blockafter    blocktime   C  check_pid_interval    checked    checkhandler    child    child_args   D  date_filename    date_filename_format    datetime_format    decode_unicode_text    delaytime    delaytime_mainprocess    devices   E  errorsleeptime    eventhandler    eventhandler_use_copy    executable_check   F  failed    failed_copy    filename_preview   G  group   H  hangup_incoming_call   I  ic_purge_hours    ic_purge_interval    ic_purge_minutes    ic_purge_read    ignore_exec_output    ignore_outgoing_priority    incoming    incoming_copy    incoming_utf8    infofile    internal_combine    internal_combine_binary    international_prefixes   K  keep_filename    keep_messages   L  language_file    log_charconv    logfile    loglevel    log_read_from_modem    log_read_timing    log_response_time    log_single_lines    logtime_format    logtime_ms    logtime_us    log_unmodified   M  max_continuous_sending   N  national_prefixes    notifier   O  os_cygwin    outgoing   P  phonecalls    pidfile    priviledged_numbers   R  receive_before_send    regular_run    regular_run_interval    report    report_copy   S  saved    sent    sent_copy    shell    shell_test    sleeptime_mainprocess    smart_logging    spool_directory_order    start    start_args    stats    stats_interval    stats_no_zeroes    status_include_counters    status_include_uptime    status_interval    status_signal_quality    store_original_filename    store_received_pdu    store_sent_pdu    suspend   T  terminal    trim_text    trust_outgoing   U  umask    use_linux_ps_trick    user   V  validity    voicecall_hangup_ath   W  whitelist  

 All modem settings: 
a  adminmessage_count_clear    adminmessage_limit    admin_to   b  baudrate   c  check_memory_method    check_network    check_sim    check_sim_cmd    check_sim_keep_open    check_sim_reset    check_sim_retries    check_sim_wait    cmgl_value    communication_delay    cs_convert    cs_convert_optical   d  decode_unicode_text    delaytime    delaytime_random_start    description    detect_message_routing    detect_unexpected_input    device    device_open_alarm_after    device_open_retries   e  eventhandler    eventhandler_ussd   h  hangup_incoming_call   i  ignore_unexpected_input    incoming    init    init2    internal_combine    internal_combine_binary   k  keep_messages    keep_open   l  language    language_ext    logfile    loglevel    loglevel_lac_ci    log_not_registered_after   m  max_continuous_sending    memory_start    message_count_clear    messageids    message_limit    mode    modem_disabled    ms_purge_hours    ms_purge_minutes    ms_purge_read   n  national_toa_unknown    needs_wakeup_at    notice_ucs2   o  outgoing   p  pdu_from_file    phonecalls    phonecalls_error_max    phonecalls_purge    pin    pinsleeptime    poll_faster    pre_init    primary_memory    priviledged_numbers   q  queues   r  read_configuration_after_suspend    read_delay    read_identity_after_suspend    read_timeout    read_timeout_XXX    receive_before_send    regular_run    regular_run_cmd    regular_run_cmdfile    regular_run_interval    regular_run_keep_open    regular_run_logfile    regular_run_loglevel    regular_run_post_run    regular_run_statfile    reply_path    report    report_device_details    report_read_timeouts    routed_status_report_cnma    rtscts   s  secondary_memory    secondary_memory_max    select_pdu_mode    send_delay    send_handshake_select    send_retries    sending_disabled    sentsleeptime    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   t  telnet_cmd    telnet_cmd_prompt    telnet_crlf    telnet_login    telnet_login_prompt    telnet_login_prompt_ignore    telnet_password    telnet_password_prompt    text_is_pdu_key    trust_spool   u  unexpected_input_is_trouble    using_routed_status_report    ussd_convert   v  verify_pdu    voicecall_clcc    voicecall_cpas    voicecall_hangup_ath    voicecall_ignore_modem_response    voicecall_vts_list    voicecall_vts_quotation_marks   w  wakeup_init  


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:

    yes , no
    true , false
    on , off
    1 , 0

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:

    devices = ? GSM1 | GSM1, GSM2, GSM3 | GSM88, GMS99
A question mark tells that more than one value is defined. Possible values are separated using pipe. In this example the smsd will prompt:
    Value for "global devices" (Enter for the first value, 0 to exit):
    1) GSM1
    2) GSM1, GSM2, GSM3
    3) GSM88, GMS99
    4) Other
    Select 1 to 4:
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.

As "default" is now reserved name, it cannot be used as a modem name. Also "modemname" and "ALL" are reserved. After >= 3.1.20 also "communicate" is reserved.

After version 3.1.19 "group" sections can be defined. If the system has devices = ELISA* 11-13, GSM1, DNA33* 1-5, the following groups can be used:

    [ELISA*]
    # Settings for ELISA11, ELISA12 and ELISA13 SIM's

    [DNA*]
    # Settings for DNA331, DNA332 etc. SIM's

Grouping works with device names which do not start with digit. Asterisk is placed to the position where the first digit exists.

All modems read [default] section first, if it exists. Next the group section is read, if it exists. Finally the modems own section is read, if it exists.

After >= 3.1.20 special section [communicate] can define shortcuts which can be used when communicating with a modem (e.g. smsd -C GSM1). Possible keys are Alt-0 ... Alt-9, and the syntax is like the following:

    [communicate]
    a0 = ATZ
    a5 = AT+CPMS?
When communicating starts, available shortcuts are printed to the console. Alt-? also prints available shortcuts. Shortcuts can be used after a modem is opened. Shortcut does not include CR character, it must be pressed by the user.

After >= 3.1.20 also the Ctrl-Z character can be sent using the Alt-Z key, required when terminating the PDU.

Global part

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

A
 top 

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.

alarmlevel = number
Default value: LOG_WARNING.
Specifies what levels start an alarmhandler. You can use value between 2 and 7.
After version >= 3.1 a value can be defined as a word, like LOG_INFO, INFO or info.

alphabet = number
Default value: ISO.
Available from version >= 3.1.16. This setting defines how message body of outgoing file is handled, when there is no Alphabet header included. Choices are ISO, Latin or Ansi for ISO-8859-15, and UTF for UTF-8.

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)

B
 top 

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.

C
 top 

check_pid_interval = number
Default value: 10.
Available from version >= 3.1.17. Defines how often mainprocess will check the pidfile, to see if there was another smsd started. If another smsd is started, current smsd will stop immediately. Value 0 disables this checking. Value is seconds.

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.

child = filename
Default value: not in use.
Available from version >= 3.1.17. This setting creates a child process to the mainspooler. It starts when smsd starts, and stops when smsd stops. If child has created one or more childs, they are stopped too when smsd stops.

Child can be used for various purposes, for example feeding spooler from SQL database, taking care that the number of files in the spooler does not grow too much, and feeding is done only when smsd is running.

Filename must be /path/to/executable only, because executable is checked when smsd starts. Use the setting child_args if any arguments are required.

child_args = string
Default value: empty.
Available from version >= 3.1.17. Defines arguments to the child.

D
 top 

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...

After version >= 3.1.19 multiple wildcard definitions can be used, together with static names. For example:
devices = ELISA* 11-15, SONERA* 11-15, GSM1

E
 top 

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.

eventhandler_use_copy = yes/no
Default value: no.
Available from version >= 3.1.17. If a copy of failed / incoming / sent / report message is created, this setting defines if a copied file is given to the eventhandler instead of original file.

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.

F
 top 

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

failed_copy = directory
Default value: not in use.
Available from version >= 3.1.17. It not empty, copy of all failed messages are stored into this directory. Depending on the setting eventhandler_use_copy smsd does not do anything with these files, so external application can use them in whatever way it wants.

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.

G
 top 

group - see here

H
 top 

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".

I
 top 

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_copy = directory
Default value: not in use.
Available from version >= 3.1.16. It not empty, copy of all incoming messages are stored into this directory. Smsd does not do anything with these files, so external application can use them in whatever way it wants.

Note after >= 3.1.17: the setting eventhandler_use_copy defines which one file is given to the eventhandler.

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.

infofile - see here

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.

K
 top 

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.

L
 top 

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 initialisation 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_read_timing = yes/no
Default value: no.
Available from version >= 3.1.16. This setting is for tuning purposes. When testing and searching suitable value for the poll_faster factor, it's important to see how the modem communicates and how much modem process generates server load.

log_response_time = yes/no
Default value: no.
Available from version >= 3.1.16. With this setting response time in milliseconds is included in the log.

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.

M
 top 

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.

From version >= 3.1.18 continuous sending breaks if a signal USR2 is received. Sending continues as soon as receiving and other tasks are done.

N
 top 

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.

notifier = yes/no
Default value: no.
Available from version >= 3.1.17. If set to yes, mainspooler creates a child process which will use inotifywait to monitor outgoing directory. As soon as a new file appears in the directory, mainprocess gets a signal SIGCONT and continues immediately. New files are spooled as fast as possible, even when mainprocess has long sleeping times to reduce CPU load.

This feature is available only on GNU/Linux systems. When compiling smsd on other systems, edit the Makefile and uncomment the line which defines DISABLE_INOTIFY. By default inotify is enabled in Makefile.

O
 top 

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.

P
 top 

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

Note after >= 3.1.16: If incoming_copy is used, and phonecalls are stored into the incoming directory, phonecalls are also copied. If phonecalls have their own directory, copying is not done.

pidfile - see here

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.

R
 top 

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.

report_copy = directory
Default value: not in use.
Available from version >= 3.1.17. It not empty, copy of all report messages are stored into this directory. Depending on the setting eventhandler_use_copy smsd does not do anything with these files, so external application can use them in whatever way it wants.

S
 top 

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.

sent_copy = directory
Default value: not in use.
Available from version >= 3.1.17. It not empty, copy of all sent messages are stored into this directory. Depending on the setting eventhandler_use_copy smsd does not do anything with these files, so external application can use them in whatever way it wants.

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.

sleeptime_mainprocess = value
Default value: 1
Available from version >= 3.1.17. Value is seconds. This setting defines how long mainprocess sleeps when no any messages are spooled. While looping, another smsd is detected, modem processes are listened and statistic files are written. This setting does not affect for picking up new outgoing files, there is another setting delaytime_mainprocess for it. Also notice that if an external child with inotifywait is used, mainprocess will get the signal CONT and continues spooling immediately after a new file exists. Because of this, delaytime can be high to save resources on small systems.

If it is needed that smsd really sleeps using sleep() while idle, this value can be set to higher than 1. However, in usual systems there is no need for that. Even if value is higher, regular_run script is executed on time, if in use. PID and processes are checked less often, and statistics are written less often if this value is higher than values set to those tasks.

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.

start = filename
Default value: not in use.
Available from version >= 3.1.18. Defines a script/program which is executed when smsd starts. This should be /path/to/executable only. If any arguments are required, use start_args. If return value of this script is anything else than 0, smsd stops.

start_args = string
Default value: empty.
Available from version >= 3.1.18. Defines arguments for start script.

stats = directory
Default value: not in use.
Specifies the directory where smsd stores statistic files. The directory 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_include_uptime = yes/no
Default value: no.
Available from version >= 3.1.16. Defines if start timestamp and uptime are printed to the end of status file, with empty line as delimiter.

For example:

Status:     16-06-07 00:16:49, irrri-------
SONERA:     16-06-07 00:16:45, Idle,      131267, 0, 4666, ssi: -71 dBm (Excellent), ber: < 0.2 %
ELISA:      16-06-07 00:16:45, Receiving, 133877, 3, 4741, ssi: -79 dBm (Good), ber: < 0.2 %
DNA:        16-06-07 00:16:47, Receiving, 127181, 0, 4708, ssi: -65 dBm (Excellent), ber: < 0.2 %
SAUNALAHTI: 16-06-07 00:16:48, Receiving, 128495, 1, 4678, ssi: -79 dBm (Good), ber: < 0.2 %
SAUNA2:     16-06-07 00:16:45, Idle,      5640,   9, 488,  ssi: -77 dBm (Good), ber: < 0.2 %

Start:      16-04-03 19:30:38, up 64 days, 4:46

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 initialisation 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 one message if there is something to send, and receive messages once.

After version >= 3.1.18 configuration changes can be done on the fly, if a modem setting read_configuration_after_suspend is set to yes. Note that if a modem process is idle and delaytime is very long, it takes a while until process goes to suspended. This delay can be bypassed by sending a signal CONT to a process.

After version >= 3.1.19 wildcard definitions can be used in the suspend file, for example: ELISA*: <reason>. The letters before *: are tested, and if the name of a modem matches with them, suspend is applied.

T
 top 

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.

U
 top 

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.

use_linux_ps_trick = yes/no
Default value: no.
Available from version >= 3.1.7. This can be used on GNU/Linux systems to create short process names like:

smsd: MAINPROCESS
smsd: GSM1

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):

  • -ix set infofile to x
  • -px set pidfile to x
  • -ux set username to x
  • -gx set groupname to x
See Running for more information.

V
 top 

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.

W
 top 

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.

Name of a modem can contain alphanumeric characters, underscore, minus-sign and dot. Other characters are not allowed, because the name is used to create filenames too.

Name of a modem cannot be default, because it's a name of a [default] block. Also a name cannot be modemname, because it's a macro for several other settings. And the name cannot be ALL. If the name is not acceptable, smsd gives an error and does not start.

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.17, if using child, the name of it's process is CHILD. And if using notifier on GNU/Linux systems, the name of it's process is NOTIFIER. Avoid using those names as modem names.

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. All modem processes will read and apply [default] settings first.

a
 top 

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.

b
 top 

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.

c
 top 

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.

check_sim = yes/no / once
Default value: no.
check_sim* settings are available from version >= 3.1.21. If the device and smsd are started before the SIM is inserted to the device, communication will fail and modem process stops. With some devices modem process can wait until the SIM becomes ready.

A setting check_sim defines if the SIM is checked every time when a modem is initialized. Value once means that checking is only done when a modem is initialized for the first time.

All devices are not compatible with this feature. Some tested devices, like Huawei E353 and Neoway M590E do not work if the SIM is not in place when a device is switched on. Telit EZ-10 worked well and any reset was not required. SIM800L also worked well, but RADIO_OFF_ON was required. Some devices like M590E stop the communication if the radio is switched off, so that kind of reset cannot be used. If this feature is going to be used, carefull tests should be done.

When an error with SIM is detected, modem port is closed while modem process is waiting. USB modem can be disconnected and reconnected, if the port remains the same.

check_sim_cmd = string
Default value: AT+CPIN?.
Defines a command which is used to check the SIM. Default value AT+CPIN? is suitable for most devices. The command should return ERROR when the SIM is not ready.

check_sim_keep_open = yes/no
Default value: no.
Defines if a modem is closed when SIM was not ready and modem process is waiting for retrying.

check_sim_reset = string / RADIO_OFF_ON / RADIO_OFF_ON_SLOW
Default value: not used.
Defines if reset with AT commands is done before retrying after the failure. Special value RADIO_OFF_ON is translated to AT+CFUN=0;+CFUN=1 which sets the radio off and immediately back to on without delays. Value RADIO_OFF_ON_SLOW is translated to AT+CFUN=0[3]AT+CFUN=1[5] where numbers in square brackets defines a delay in seconds. This setting sets the radio off, waits three seconds, sets the radio back on and waits five seconds.

check_sim_retries = number / forever
Default value: 10.
Defines how many times modem process will retry. With a value forever modem process will retry, let's say, forever.

check_sim_wait = number
Default value: 30.
Defines how many seconds modem process will wait until retry.

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.

cs_convert_optical = yes/no
Default value: yes.
When a character cannot be represented in the GSM character set, it can be approximated through one or several similarly looking characters.

d
 top 

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.

delaytime = number
Default value: use the global part setting.
Available from version >= 3.1.18. Defines how many seconds a modem process sleeps when it has nothing to do. This setting overrides the global delaytime value.

delaytime_random_start = yes/no
Default value: no.
Available from version >= 3.1.18. Defines if the first sleep of a modem process is randomized, using a value between 0 and delaytime. With very large number of modems it may be good that all processes are not working at the same time.

description = string
Default value: empty.
Available from version >= 3.1.16. If set, this description is written to each SMS file as an additional header.

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:

    [default]
    device = /dev/ttymodemname
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.

e
 top 

eventhandler = filename
Default value: empty.
Specifies an eventhandler script like in the global part. After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem. If you use this setting, 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. After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem. 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.

h
 top 

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.

i
 top 

ignore_unexpected_input = string
Default value: none.
Available from version >= 3.1.16. If the device continually sends some unexpected input, and there is not other way to get rid of it, suitable phrase can be defined to keep the log clean. Multiple phrases can be defined with multiple settings.

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. init1 and init define both the same. 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.

k
 top 

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.

l
 top 

language = string or number
Default value: none.
language_ext = string or number
Default value: none.
Available from version >= 3.1.16. These settings set default values for National Language Shift Tables used in the outgoing message.
Value can be number, or variable length string which first macthes (case insensitive).
Choices are:

  • 0 = basic
  • 1 = Turkish
  • 2 = Spanish
  • 3 = Portuguese
  • 4 = Bengali and Assemese, or Bengali, or Assemese
  • 5 = Gujarati
  • 6 = Hindi
  • 7 = Kannada
  • 8 = Malayalam
  • 9 = Oriya
  • 10 = Punjabi
  • 11 = Tamil
  • 12 = Telugu
  • 13 = Urdu

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.

m
 top 

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.

From version >= 3.1.18 continuous sending breaks if a signal USR2 is received. Sending continues as soon as receiving and other tasks are done.

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_*.

n
 top 

national_toa_unknown = yes/no
Default value: no.
Available from version >= 3.1.16. When destination number is national, some operators require that "unknown" format is defined.

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.

notice_ucs2 = number
Default value: 2.
Available from version >= 3.1.16. Message written using UTF-8 alphabet is tried to convert to GSM alphabet, to save sending costs. Any character outside the GSM alphabet is reported, if value of notice_ucs2 is 2. If the value is at least 1, total number of missing characters is reported. Each character is presented in the sent message file using NOTICE: header. With cyrillic languages the conversion is always done, and the information is not important. Notification can be disabled with value 0.

o
 top 

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

p
 top 

pdu_from_file = filename / directoryname/
Default value: not in use.
Available from version >= 3.0. This is for testing purposes.
After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem.
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.

poll_faster = number
Default value: 5.
Available from version >= 3.1.16. This setting speeds up the polling, when it is known what to expect as an answer from the modem. Use the global setting log_read_timing to find out the best performance with your device.

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.

q
 top 

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.
After version >= 3.1.16, when queues are used, this setting is no more mandatory if outgoing is disabled.

r
 top 

read_configuration_after_suspend = yes/no
Default value: no.
Available from version >= 3.1.18. If set to yes, modem process will read the configuration when suspend is going to break or end. Configuration is checked and modem port (device) is tested. If there are any problems, suspend continues. Problems are shown in the log, and after problems are fixed, a signal USR2 must be sent to the modem process. When configuration has no problems, modem process will continue as usual.

Changes for device settings logfile and loglevel do not apply without restarting smsd.

read_delay = number
Default value: 0.
Available from version >= 3.1.16. Some modems may require small delay before reading is started. This time is in milliseconds.

read_identity_after_suspend = yes/no
Default value: yes.
Available from version >= 3.1.18. Defines if IMEI and IMSI are refreshed after the suspend has ended. This is useful if modem was suspended, and the SIM was changed on the fly, without restarting the whole daemon and without reading the whole modem setup.

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.

read_timeout_XXX = number
Default value: varies.
Available from version >= 3.1.16. Defines a timeout for XXX operation. Use report_read_timeouts = yes to see possible operations (XXX) and current values.

This is for problematic environments, usually there is no need to change any value. Minimum value is 1. The value defines how many read_timeout's are spent. The setting read_timeout is in seconds and defaults to 5.

receive_before_send = yes/no
Default value: no.
Available from version >= 3.1.17 as a modem setting. Without this setting a value of global setting is used.
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 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, if regular_run_keep_open is not set to yes.
After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem.

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.
After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem.

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:

    [(>)|(ERROR)]AT+STGR=3,1;
    Smsd is now expecting ">" or "ERROR" instead of "OK" or "ERROR".

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

regular_run_keep_open = yes/no
Default value: no.
Available from version >= 3.1.16. This setting defines if a modem is kept open when modem process is executing external regular_run scripts.

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. After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem.

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. After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem. 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:

  • If regular_run is defined, it's executed with arguments PRE_RUN and regular_run_statfile. A modem is closed while the script is running, depending on the setting regular_run_keep_open. regular_run_statfile is available from the previous run.
  • regular_run_statfile is deleted.
  • If regular_run_cmdfile is defined and the file exists, commands from this file are sent to the modem. If regular_run_logfile is defined, results are written to the file. If regular_run_statfile is defined, results are written to this file too. NOTE: regular_run_cmdfile is deleted after commands are handled. Use PRE_RUN phase or an external process to create this file.
  • If regular_run_post_run is defined, it's executed with arguments POST_RUN and regular_run_statfile.
  • If regular_run_cmd, one or more, is defined, commands are sent to the modem. Results are logged and written to the statfile, if defined.
Order of regular_run_cmd and regular_run_post_run has changed in version 3.1.16.

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. After version >= 3.1.16 special keyword modemname can be used, it's replaced with a name of modem.

reply_path = yes/no
Default value: no.
Available from version >= 3.1.16. Defines a default value for Reply Path (TP-RP) field. Header in the message file overrides this value.

report = yes/no/disabled
Default value: no.
If you enable this, the program requests a status report SM from the SMSC for each sent message.
After version >= 3.1.16 this can have a value disabled, which means that the report is not requested even when the message file has a Report: yes header.

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.

report_read_timeouts = yes/no
Default value: no.
Available from version >= 3.1.16. Defines if all timeout values are written to the log when modem process starts.

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.

s
 top 

secondary_memory - see here

secondary_memory_max - see here

send_delay = number
Default value: 0.
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 transmitter is busy. If because of some reason the old style should be used, this setting allows it.

send_retries = number
Default value: 2.
Available from version >= 3.1.16. Defines how many times smsd will retry if sending fails.

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.

sentsleeptime = number
Default value: 0.
Available from version >= 3.1.16. Some modems may need little delay after each part of SMS is sent. This time is in seconds.

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 = 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 "+".

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.

t
 top 

telnet_cmd = string
Default value: empty.
telnet_cmd_prompt = string
Default value: empty.
Available from version >= 3.1.16. Some devices have more than one modem modules, and the Telnet interface wants to know what module to use. The interface may prompt something like "module1, module2, state1, state2, info.", as PORTech MV-372 does. For example the command module1 should be sent to the interface. Use communication mode or some Telnet client to see what exactly is the prompt. Define it as telnet_cmd_prompt, exactly as it was received. Define suitable command as telnet_cmd. Whenever the prompt occurs in traffic, the cmd is sent and wanted module is selected.

telnet_crlf = yes/no
Default value: yes.
Available from version >= 3.1.16. Defines if CRLF is used instead of LF when communicating with Telnet. This defaults to yes, because many if not all Telnet servers require CRLF.

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.

text_is_pdu_key = string
Default value: not in use.
Available from version >= 3.1.16. In some systems the PDU is generated outside the smsd. As the modem is kept open by smsd, external program cannot use it. In this case external program can create SMS file for smsd, and in this file the message body is PDU in the hex format, and To: number should match with this key.

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.

u
 top 

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.
 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.

v
 top 

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.

w
 top 

wakeup_init = string
Default value: empty.
Available from version >= 3.1.16. Defines a string or command which is sent to the device when it is initialised, before anything else is done. Any or missing answer is accepted, and smsd continues after a small delay.


smstools3/doc/udh.html0000755000175000017500000001021013046362405013657 0ustar kekekeke 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.


smstools3/doc/faq.html0000755000175000017500000002645413046362405013667 0ustar kekekeke 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 (2200F/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:

  • Nokia phones based on S60: can send but cannot receive.
  • Nokia phones based on S40 3rd edition: can send but cannot receive.
  • Nokia phones based on S40 before 3rd edition: can send and can also receive.
  • Sony Ericsson phones except some using Symbian user interface: can send and can also receive.

If you did not find an answer to your question, please visit the SMSTools3 Community.
smstools3/doc/license.html0000755000175000017500000000363513046432110014525 0ustar kekekeke 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://stefanfrings.de/smstools/
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


smstools3/doc/gpl.html0000755000175000017500000004467413046362405013706 0ustar kekekeke

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:

  • a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

  • b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

  • c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)
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:

  • a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

  • b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

  • c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)
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. smstools3/doc/statusmonitor.html0000755000175000017500000000764413046362405016053 0ustar kekekeke 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.


smstools3/doc/history3.html0000755000175000017500000044643613102443132014700 0ustar kekekeke SMS Server Tools 3

SMS Server Tools 3

Home

General

If you have any comments, ideas, needs or other SMS Server Tools 3 related things, please write them to SMSTools3 Community which is a support forum for this software.

Coming to the version 3.2.x, not yet released

Estimated release time for following additions was in the past, but the publishing is delayed.

  1. SQL database based spooling and message handling. Can be used together with current filesystem based spooling, or instead of it. At least MySQL and PostgreSQL will be supported, likely SQLite3, Mini SQL and Microsoft SQL Server too.

Version history

03.05.2017    3.1.21

Configuration file (smsd.conf):

  1. Added several modem settings for checking the SIM:

    check_sim = yes/no / once
    Default value: no.
    If the device and smsd are started before the SIM is inserted to the device, communication will fail and modem process stops. With some devices modem process can wait until the SIM becomes ready.

    A setting check_sim defines if the SIM is checked every time when a modem is initialized. Value once means that checking is only done when a modem is initialized for the first time.

    All devices are not compatible with this feature. Some tested devices, like Huawei E353 and Neoway M590E do not work if the SIM is not in place when a device is switched on. Telit EZ-10 worked well and any reset was not required. SIM800L also worked well, but RADIO_OFF_ON was required. Some devices like M590E stop the communication if the radio is switched off, so that kind of reset cannot be used. If this feature is going to be used, carefull tests should be done.

    When an error with SIM is detected, modem port is closed while modem process is waiting. USB modem can be disconnected and reconnected, if the port remains the same.

    check_sim_cmd = string
    Default value: AT+CPIN?.
    Defines a command which is used to check the SIM. Default value AT+CPIN? is suitable for most devices. The command should return ERROR when the SIM is not ready.

    check_sim_keep_open = yes/no
    Default value: no.
    Defines if a modem is closed when SIM was not ready and modem process is waiting for retrying.

    check_sim_reset = string / RADIO_OFF_ON / RADIO_OFF_ON_SLOW
    Default value: not used.
    Defines if reset with AT commands is done before retrying after the failure. Special value RADIO_OFF_ON is translated to AT+CFUN=0;+CFUN=1 which sets the radio off and immediately back to on without delays. Value RADIO_OFF_ON_SLOW is translated to AT+CFUN=0[3]AT+CFUN=1[5] where numbers in square brackets defines a delay in seconds. This setting sets the radio off, waits three seconds, sets the radio back on and waits five seconds.

    check_sim_retries = number / forever
    Default value: 10.
    Defines how many times modem process will retry. With a value forever modem process will retry, let's say, forever.

    check_sim_wait = number
    Default value: 30.
    Defines how many seconds modem process will wait until retry.

General:

  1. The script sendsms now tries to change the group of outgoing file instead of owner which can be changed only by root. Attributes of a file are set to 0660.

Bug fixes:

  1. After a message was sent, modem process crashed on FreeBSD running on Raspberry Pi, because time to send was written inproperly to the log file (time_t vs. integer). This is fixed and similar missing typecasts are added to couple of other places too.

  2. If the USSD answer was received from the modem in more than one pieces, only the first piece was handled and the result was broken. This is fixed by waiting for the last quotation mark and comma which are very near to the end of answer.

  3. Pre-initialization did not work with all modems. "Echo off" and extended commands are not combined anymore. If pre_init is set to no, "echo off" and "CMEE=1" are not sent to the modem, but "CLIP=1" and/or "CREG=2" are still sent if necessary.

Download


11.04.2017    3.1.20

Sent message file:

  1. New header: Sending_time: number of seconds
    Tells how long it took to send the whole message. If the message has multiple parts and receive_before_send is set to yes, time to receive messages is included in this value.

Configuration file (smsd.conf):

  1. Special section [communicate] can define shortcuts which can be used when communicating with a modem (e.g. smsd -C GSM1). Possible keys are Alt-0 ... Alt-9, and the syntax is like the following:

      [communicate]
      a0 = ATZ
      a5 = AT+CPMS?

    When communicating starts, available shortcuts are printed to the console. Alt-? also prints available shortcuts. Shortcuts can be used after a modem is opened. Shortcut does not include CR character, it must be pressed by the user.

    Also the Ctrl-Z character can be sent using the Alt-Z key, required when terminating the PDU.

General:

  1. Nowadays modems like Huawei are moderately general in simple environments, and list of incoming messages with those devices starts from 0. There is a configuration setting memory_start available, and it defaults to 1 because of backward compatibility. If memory_start is not set, and reading of messages causes an error, notification is written to the log and modem process changes the start to 0 automatically. To avoid future error messages when smsd starts, a configuration setting memory_start = 0 should be applied manually.

  2. When a status of PIN is asked from the modem, the answer "+CPIN: READY" is expected. Now also simply "OK" is accepted.

  3. When checking if a script is executable, it is also checked if a definition incorrectly points to the directory.

Bug fixes:

  1. Even when the outgoing text is UTF-8 encoded, ISO characters are still accepted for backward compatibility. Changes for character conversion in 3.1.16 dropped ISO characters, and this is now fixed.

  2. A field Discharge_timestamp in the messabe body of status report was always using the format "%y-%m-%d %H:%M:%S", even when a custom datetime_format was defined in the configuration file. This is fixed.


10.03.2017    3.1.19

Configuration file (smsd.conf):

  1. Devices setting: multiple wildcard definitions can be used, together with static names. For example:
    devices = ELISA* 11-15, SONERA* 11-15, GSM1

  2. "group" sections can be defined. If the system has devices = ELISA* 11-13, GSM1, DNA33* 1-5, the following groups can be used:

      [ELISA*]
      # Settings for ELISA11, ELISA12 and ELISA13 SIM's

      [DNA*]
      # Settings for DNA331, DNA332 etc. SIM's

    Grouping works with device names which do not start with digit. Asterisk is placed to the position where the first digit exists.

    All modems read [default] section first, if it exists. Next the group section is read, if it exists. Finally the modems own section is read, if it exists.

Other:

  1. Wildcard definitions can be used in the suspend file, for example: ELISA*: <reason>. The letters before *: are tested, and if the name of a modem matches with them, suspend is applied.

Bug fixes:

  1. In the version 3.1.18 reading of modem settings was restructured and the new code forgot to close the handle of configuration file. This is fixed.


03.03.2017    3.1.18

Configuration file (smsd.conf):

  1. New setting for a modem: read_configuration_after_suspend = yes/no
    Default value: no.
    If set to yes, modem process will read the configuration when suspend is going to break or end. Configuration is checked and modem port (device) is tested. If there are any problems, suspend continues. Problems are shown in the log, and after problems are fixed, a signal USR2 must be sent to the modem process. When configuration has no problems, modem process will continue as usual.

    Changes for device settings logfile and loglevel do not apply without restarting smsd.

  2. New setting for a modem: read_identity_after_suspend = yes/no
    Default value: yes.
    Defines if IMEI and IMSI are refreshed after the suspend has ended. This is useful if modem was suspended, and the SIM was changed on the fly, without restarting the whole daemon and without reading the whole modem setup.

  3. New setting for a modem: delaytime = number
    Default value: use the global part setting.
    Defines how many seconds a modem process sleeps when it has nothing to do. This setting overrides the global delaytime value.

  4. New setting for a modem: delaytime_random_start = yes/no
    Default value: no.
    Defines if the first sleep of a modem process is randomized, using a value between 0 and delaytime. With very large number of modems it may be good that all processes are not working at the same time.

  5. New global setting: start = filename
    Default value: not in use.
    Defines a script/program which is executed when smsd starts. This should be /path/to/executable only. If any arguments are required, use start_args. If return value of this script is anything else than 0, smsd stops.

  6. New global setting: start_args = string
    Default value: empty.
    Defines arguments for start script.

Bug fixes:

  1. If smsd was compiled with shared memory (statistics) enabled, and NUMBER_OF_MODEMS in the Makefile was set to 124 or more, there was not enough shared memory available when trying to start smsd. Error message was written to the log and smsd did not start. Calculation of required memory with large setups is fixed, and now hundreds of modems are supported with statistics enabled.

  2. If notifier (released in the version 3.1.17) was used, it did not terminate in some environments when smsd terminated. This was because smsd used a signal SIGTERM to terminate it. Now SIGKILL is used.


24.02.2017    3.1.17

General:

  1. Main loops are fine-tuned to reduce CPU load and increase the performance of spooling.

    When smsd is spending some delay, for example sleeping after error, it sleeps more and less often, meaning that single sleep in the sleeping loop is raised to 10ms from 100µs.

  2. Smsd now respects environment setting TMPDIR or TEMPDIR, if available. Without those variables smsd uses /tmp, as before.

  3. When sending large number of messages continuously, regular run of a modem is executed on schedule, without need to use max_continuous_sending setting.

Configuration file (smsd.conf):

  1. New global setting: notifier = yes/no
    Default value: no.
    If set to yes, mainspooler creates a child process which will use inotifywait to monitor outgoing directory. As soon as a new file appears in the directory, mainprocess gets a signal SIGCONT and continues immediately. New files are spooled as fast as possible, even when mainprocess has long sleeping times to reduce CPU load.

    This feature is available only on GNU/Linux systems. When compiling smsd on other systems, edit the Makefile and uncomment the line which defines DISABLE_INOTIFY. By default inotify is enabled.

  2. New global setting: sleeptime_mainprocess = value
    Default value: 1
    Value is seconds. This setting defines how long mainprocess sleeps when no any messages are spooled. While looping, another smsd is detected, modem processes are listened and statistic files are written. This setting does not affect for picking up new outgoing files, there is another setting delaytime_mainprocess for it. Also notice that if an external child with inotifywait is used, mainprocess will get the signal CONT and continues spooling immediately after a new file exists. Because of this, delaytime can be high to save resources on small systems.

    If it is needed that smsd really sleeps using sleep() while idle, this value can be set to higher than 1. However, in usual systems there is no need for that. Even if value is higher, regular_run script is executed on time, if in use. PID and processes are checked less often, and statistics are written less often if this value is higher than values set to those tasks.

  3. New global setting: check_pid_interval = number
    Default value: 10.
    Defines how often mainprocess will check the pidfile, to see if there was another smsd started. If another smsd is started, current smsd will stop immediately. Value 0 disables this checking. Value is seconds.

  4. New global setting: child = filename
    Default value: not in use.
    This setting creates a child process to the mainspooler. It starts when smsd starts, and stops when smsd stops. If child has created one or more childs, they are stopped too when smsd stops.

    Child can be used for various purposes, for example feeding spooler from SQL database, taking care that the number of files in the spooler does not grow too much, and feeding is done only when smsd is running.

    Filename must be path/to/executable only, because it is checked when smsd starts. Use the setting child_args if any arguments are required.

  5. New global setting: child_args = string
    Default value: empty.
    Defines arguments to the child.

  6. New global settings:
    failed_copy = directory
    report_copy = directory
    sent_copy = directory
    Default value: not in use.
    It not empty, copy of all failed / report / sent messages are stored into this directory. Depending on the setting eventhandler_use_copy smsd does not do anything with these files, so external application can use them in whatever way it wants.

    For incoming messages a setting incoming_copy applies, it was released in the version 3.1.16.

  7. New global setting: eventhandler_use_copy = yes/no
    Default value: no.
    If a copy of failed / incoming / sent / report message is created, this setting defines if a copied file is given to the eventhandler instead of original file.

  8. New setting for a modem: receive_before_send = yes/no
    Default value: no.
    This is now available as a modem setting. Without this setting a value of global setting is used.
    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.

Outgoing message file:

  1. Filenames starting with a dot (so called hidden files) and filenames starting with "LOCKED" are now ignored. Filenames ending with ".LOCK" are ignored too, as before.

08.02.2017    3.1.16

Documentation is now updated and this version has no beta status anymore.


01.02.2017    3.1.16beta2

When communicating with the modem, smsd polls the answer much faster than before, when the expected answer is known. A new setting poll_faster is available, and it's the factor to reduce idle time and increase number of times to listen. The factor defaults to 5, which seems to work well with many modems. Some modems work even faster with greater value, like 30. However, very high values may increase the server load remarkably, so proper value should be measured by device. There is a setting log_read_timing available for tuning purposes.


Handling of alphabets is enhanced and all conversions are now done using internal routines which work well with cyrillic languages too. For backward compatibility, the alphabet of message file still defaults to ISO-8859-15, and this can be changed to UTF-8 in the configuration, or a header Alphabet: UTF can should be used. To receive cyrillic messages directly to UTF-8 character set, use the global settings decode_unicode_text = yes and incoming_utf8 = yes.

When sending text messages, choices are:

Alphabet: ISO

  • depending on the global setting outgoing_utf8 (which defaults to yes), UTF-8 is still accepted and converted.
  • optical replacement is done, depending on the modem setting cs_convert_optical (which defaults to yes).
  • characters are converted to the GSM alphabet, and missing characters are dropped.

Alphabet: UTF

  • like with ISO, but if any characters cannot be converted to the GSM alphabet, whole message is converted to the UCS2 alphabet.
  • if conversion to UCS2 is done, each message part will have less characters and therefore more parts will be required, but the text delivers as it was written.


National Language Shift Tables are now supported. Languages are (European) Turkish, Spanish and Portuguese, and (Indian) Bengali and Assemese, Gujarati, Hindi, Kannada, Malayalam, Oriya, Punjabi, Tamil, Telugu and Urdu. If you do not need this support, and your computer is some embedded device with low memory, you can disable this support in Makefile. Also there is a definition to support European languages only.

When SMS using shift tables is received, it is always stored using UTF-8 alphabet. When sending SMS, there are headers Language and Language_ext available. Default values can also be set in the configuration. When shift tables are used, text body of SMS should be written using UTF-8 alphabet. Tables of the character sets can be found on the doc directory of a package.


Documentation is not yet updated. It will be done after beta status has gone.

Any feedback is valuable.


Configuration file (smsd.conf):

  1. New global setting: alphabet = string.
    Default value: ISO.
    This setting defines how message body of outgoing file is handled, when there is no Alphabet header included. Choices are ISO, Latin or Ansi for ISO-8859-15, and UTF for UTF-8.

  2. New global setting: log_read_timing = yes/no.
    Default value: no.
    This setting is for tuning purposes. When testing and searching suitable value for the poll_faster factor, it's important to see how the modem communicates and how much modem process generates server load.

  3. New global setting: incoming_copy = string.
    Default value: empty.
    It not empty, copy of all incoming messages are stored into this directory. Smsd does not do anything with these files, so external application can use them in whatever way it wants.

  4. New setting for a modem: poll_faster = number.
    Default value: 5.
    This setting speeds up the polling, when it is known what to expect as an answer from the modem. Use the global setting log_read_timing to find out the best performance with your device.

  5. New setting for a modem: cs_convert_optical = yes/no.
    Default value: yes.
    If set to yes, when a character cannot be represented in the GSM character set, it can be approximated through one or several similarly looking characters.

  6. New setting for a modem: national_toa_unknown = yes/no.
    Default value: no.
    When destination number is national, some operators require that "unknown" format is defined.

  7. New setting for a modem: ignore_unexpected_input = string.
    Default value: none.
    If the device continually sends some unexpected input, and there is not other way to get rid of it, suitable phrase can be defined to keep the log clean. Multiple phrases can be defined with multiple settings.

  8. New settings for a modem:
    language = string or number.
    Default value: none.
    language_ext = string or number.
    Default value: none.
    These settings set default values for the outgoing message file. For more details see the headers section.

  9. New setting for a modem: regular_run_keep_open = yes/no.
    Default value: no.
    This setting defines if a modem is kept open when modem process is executing external regular_run scripts.

  10. New setting for a modem: select_pdu_mode = yes/no.
    Default value: yes.
    Defines if PDU mode is selected each time when communication with the modem is started.

  11. New setting for a modem: description = string.
    Default value: empty.
    If set, this description is written to each SMS file as an additional header.

  12. New setting for a modem: reply_path = yes/no.
    Default value: no.
    Defines a default value for Reply Path (TP-RP) field. Header in the message file overrides this value.

  13. New setting for a modem: sentsleeptime = number.
    Default value: 0.
    Some modems may need little delay after each part of SMS is sent. This time is in seconds.

  14. New setting for a modem: read_delay = number.
    Default value: 0.
    Some modems may require small delay before reading is started. This time is in milliseconds.

  15. New setting for a modem: text_is_pdu_key = string.
    Default value: none.
    In some systems the PDU is generated outside the smsd. As the modem is kept open by smsd, external program cannot use it. In this case external program can create SMS file for smsd, and in this file the message body is PDU in the hex format, and To: number should match with this key.

  16. New setting for a modem: notice_ucs2 = number.
    Default value: 2.
    Message written using UTF-8 alphabet is tried to convert to GSM alphabet, to save sending costs. Any character outside the GSM alphabet is reported, if value of notice_ucs2 is 2. If the value is at least 1, total number of missing characters is reported. Each character is presented in the sent message file using NOTICE: header. With cyrillic languages the conversion is always done, and the information is not important. Notification can be disabled with value 0.

  17. Modem setting: report = yes/no/disabled.
    This can now have a value disabled, which means that the report is not asked even when the message file has a Report: yes header.

  18. More modem settings can now have a special keyword modemname, which is replaced with a name of a modem.
    All of those settings are: device, queues, logfile, eventhandler, eventhandler_ussd, pdu_from_file, regular_run, regular_run_post_run, regular_run_cmdfile, regular_run_logfile and regular_run_statfile.

Outgoing message file:

  1. When reading headers, Byte Order Mark is removed, if the file starts with it. Also the EOF character is removed from the end of message body, if cs_convert is set.

  2. New headers:
    Language: value
    Language_ext: value
    Value can be number, or variable length string which first macthes.
    Choices are:

    • 0 = basic
    • 1 = Turkish
    • 2 = Spanish
    • 3 = Portuguese
    • 4 = Bengali and Assemese, or Bengali, or Assemese
    • 5 = Gujarati
    • 6 = Hindi
    • 7 = Kannada
    • 8 = Malayalam
    • 9 = Oriya
    • 10 = Punjabi
    • 11 = Tamil
    • 12 = Telugu
    • 13 = Urdu

    Usually it is not necessary to set Language_ext value. When Language is set, Language_ext defaults to the same, and if only Language_ext is defined, Language defaults to basic character set. If nothing is set, default values are taken from the configuration.

  3. New header: Text_is_pdu: yes. If this feature is enabled by defining text_is_pdu_key in the configuration, and To: number matches that key, message body is handled as ready made PDU.

( For more information about the next 6 settings, see the 3GPP TS 23.040 standard. )

  1. New header: Message_Reference: number. Sets TP-MR field in the PDU. Number can be 0...255.

  2. New header: Reject_Duplicates: yes. Sets TP-RD bit in the PDU.

  3. New header: Reply_path: yes. Sets TP-RP bit in the PDU.

  4. New header: Class: number. Sets the Message Class, 0...3.

  5. New header: DCS_hex: value. Sets Data Coding Scheme in the PDU. Note that value must be represented as two hexadecimal digits.

  6. New header: Ping: yes. Selects the Short Message Type 0, which is also known as a silent SMS. As this kind of SMS is not stored by the receiving device, report is always requested, even if report was disabled in the configuration.

Bug fixes:

  1. When communicating with device, ECHO ON is NOT set automatically when device is socket. This is because network modems may require login and/or password and it is not known when the setting can be made.


08.06.2016    3.1.16beta

The main focus with this version is on Telnet. Most devices need CRLF when communicating, and this is now default. Also subnegotiation is fixed, as it did not work properly in all cases. With these changes SMSTools3 now works well with devices like ConiuGo GPRS GSM Quadband Modem LAN. Thanks to wireless netcontrol GmbH for the test device.

Telnet devices with multiple modules will also work, however this enhancement is not tested by me with such a device. Module selection is based on the patch which was published by the user unterwulf on the support forum on this post.

Documentation is not yet updated. It will be done after beta status has gone.

Any feedback is valuable.

  1. New setting for a modem: telnet_crlf = yes/no.
    Default value: yes.
    Defines if CRLF is used instead of LF when communicating with Telnet. This defaults to yes, because many if not all Telnet servers require CRLF.

  2. New settings for a modem:
    telnet_cmd_prompt = string.
    Default value: empty.
    telnet_cmd = string.
    Default value: empty.
    Some devices have more than one modem modules, and the Telnet interface wants to know what module to use. The interface may prompt something like "module1, module2, state1, state2, info.", as PORTech MV-372 does. For example the command module1 should be sent to the interface. Use communication mode or some Telnet client to see what exactly is the prompt. Define it as telnet_cmd_prompt, exactly as it was received. Define suitable command as telnet_cmd. Whenever the prompt occurs in traffic, the cmd is sent and wanted module is selected.

  3. Multiple socket devices are now allowed to use the same IP and port.

  4. New setting for a modem: wakeup_init = string.
    Default value: empty.
    Defines a string or command which is sent to the device when it is initialized, before anything else is done. Any or missing answer is accepted, and smsd continues after a small delay.

  5. New header in incoming/sent/failed message files: IMEI: number. Tells the IMEI of a modem which handled the message.

  6. New setting for a modem: report_read_timeouts = yes/no.
    Default value: no.
    Defines if all timeout values are written to the log when modem process starts.

  7. New setting for a modem: read_timeout_XXX = number.
    Default value: varies.
    Defines a timeout for XXX operation. Use report_read_timeouts = yes to see possible operations (XXX) and current values.

    This is for problematic environments, usually there is no need to change any value. Minimum value is 1. The value defines how many read_timeout's are spent. The setting read_timeout is in seconds and defaults to 5.

  8. New setting for a modem: send_retries = number.
    Default value: 2.
    Defines how many times smsd will retry if sending fails.

  9. New header in outgoing message file: Retries: number. Defines how many times smsd will retry if sending fails. This overrides send_retries setting.

  10. New global setting: log_response_time = yes/no.
    Default value: no.
    With this setting response time in milliseconds is included in the log.

  11. New global setting: status_include_uptime = yes/no.
    Default value: no.
    Defines if start timestamp and uptime are printed to the end of status file, with empty line as delimiter. For example:

    Status:     16-06-07 00:16:49, irrri-------
    SONERA:     16-06-07 00:16:45, Idle,      131267, 0, 4666, ssi: -71 dBm (Excellent), ber: < 0.2 %
    ELISA:      16-06-07 00:16:45, Receiving, 133877, 3, 4741, ssi: -79 dBm (Good), ber: < 0.2 %
    DNA:        16-06-07 00:16:47, Receiving, 127181, 0, 4708, ssi: -65 dBm (Excellent), ber: < 0.2 %
    SAUNALAHTI: 16-06-07 00:16:48, Receiving, 128495, 1, 4678, ssi: -79 dBm (Good), ber: < 0.2 %
    SAUNA2:     16-06-07 00:16:45, Idle,      5640,   9, 488,  ssi: -77 dBm (Good), ber: < 0.2 %
    
    Start:      16-04-03 19:30:38, up 64 days, 4:46
    
    This setting defaults to no, so that existing external parsers or applications are not affected.

  12. When communicating with device, like smsd -C GSM1, alternative for Ctrl-Z can be defined using -zx argument. Single byte character is accepted as x.

  13. When communicating with device, ECHO ON is set automatically.

  14. When performing outgoing character set conversion with GSM alphabet, Nonbreakable space and Tab are changed to Space.

  15. When moving files between directories, fopen failures are logged and smsd retries after a small delay.

  16. Empty outgoing files are ignored as before, and now files smaller than 8 bytes are ignored too.

  17. If a file has no access, smsd retries once after a small delay.

  18. Temporary files are now using TMPDIR or TEMPDIR environment variable for the path, if available.

  19. When smsd processes sleeps, they sleep longer time at once and less often to reduce CPU load.

  20. Mainspooler will reap stopped (<defunct>) modem processes.

  21. Smart logging is done even when the loglevel is below 7 (debug).

  22. Total time of continuous trouble is logged.

  23. "device busy, waiting" is not logged anymore.

  24. When a modem is first time initialized, smsd tries to set verbose mode if necessary.

Bug fixes:

  1. Fixed a Telnet subnegotiation processing method.

  2. When a file is locked, fsync is used instead of sync after close. This caused remarkable delays on large servers when smsd was running as root.

  3. When queues are used, queues setting for a modem is no more mandatory if outgoing is disabled.

  4. When defining queues or priviledged_numbers for modems, initial values from [default] section are now completely ignored.


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.


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:

      devices = USB0, USB1, USB2

      [default]
      device = /dev/ttymodemname produces: device = /dev/ttyUSB0 and so on...
      logfile = /var/log/smstools/GSM-modemname.log produces: logfile = /var/log/smstools/GSM-USB0.log and so on...

  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:

    • Subject: <modemname>
    • Modem: <modemname>
    • Number: <phone number> (if defined in the configuration)
    • IMSI: <string> (if available)

  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.

    • -Ex encodes a given string to the GSM 7bit packed format. The string can be written using ISO or UTF-8 alphabet.
    • -Dx decodes a given string from GSM 7bit packed format. The result uses ISO alphabet, or UTF-8 if incoming_utf8 = yes is defined in the configuration file.

    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:

    • If regular_run is defined, it's executed with arguments PRE_RUN and regular_run_statfile. A modem is closed while the script is running. regular_run_statfile is available from the previous run.
    • regular_run_statfile is deleted.
    • If regular_run_cmdfile is defined and the file exists, commands from this file are sent to the modem. If regular_run_logfile is defined, results are written to the file. If regular_run_statfile is defined, results are written to this file too. NOTE: regular_run_cmdfile is deleted after commands are handled. Use PRE_RUN phase or an external process to create this file.
    • If regular_run_cmd, one or more, is defined, commands are sent to the modem. Results are logged and written to the statfile, if defined.
    • If regular_run_post_run is defined, it's executed with arguments POST_RUN and regular_run_statfile.

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 Data 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.

    • An incorrect sender number produces only a warning.
    • It now shows error position in the message file.
    • All possible data is checked (unused too) to detect invalid characters and a broken and/or incompatible PDU.

  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:

    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):
    • $1=ALARM
    • $2=2007-07-06
    • $3=12:00:00
    • $4=2
    • $5=smsd
    • $6=Conflict with .LOCK file in the spooler: /var/spool/sms/outgoing/test_file /var/spool/sms/checked

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.

    • In case of errors a new header Error: explanation is printed to the message file. With existense of this header it's easy to detect that there is no usual message content in the file. Text part will tell more details of errors found.
    • In the successfully processed message file there can be one or more Warning: explanation headers telling that some minor issues has detected, but the message is still processed.

  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.


smstools3/doc/compiling.html0000755000175000017500000000466313046362405015077 0ustar kekekeke 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.


smstools3/doc/run.html0000755000175000017500000002355513046362405013723 0ustar kekekeke 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):

  • $1=ALARM
  • $2=2007-07-06
  • $3=12:00:00
  • $4=2
  • $5=smsd
  • $6=Conflict with .LOCK file in the spooler: /var/spool/sms/outgoing/test_file /var/spool/sms/checked

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):

    Enter /etc/init.d/sms3 start to start smsd in background.
    Enter /etc/init.d/sms3 stop to stop smsd.

    NOTE: On some installations (Debian, Ubuntu for example) script is using name smstools.

    In case of Unix, you might create links in your runlevel directories (for example /etc/rc3.d or /etc/init.d/rc3.d) if the program shall start and stop automatically together with the operating system.

    With sms3 script you can ensure that:

    • If smsd is already running, duplicate daemon is not started.
    • When smsd is stopped while it is sending a multipart messsage, the script will wait until all parts are sent. Information of the process is printed to terminal, so you can see why the daemon is not stopped immediately.
    • In case of troubles there is force-stop argument available. This handles all smsd processes, not just the main process.

    NOTE: When the smsd main process receives a termination signal, it sends it to all subprocesses. After a signal is received, no more new jobs are started. Already started jobs are completed, which usually will not take too much time.

    NOTE: If you are running smsd in heavy traffic environment, and you OS does not wait processes while it is shutting down, it is recommended to stop the smsd with /etc/init.d/sms3 stop before shutting down the OS.

Method 2:

    Run /usr/local/bin/smsd -s to start the program in a command window.
    The smsd will run in foreground and status monitor is printed to terminal.
    Press Ctrl-C to stop the program.

Method 3:

    Enter /usr/local/bin/smsd to start the daemon in background.
    Enter pkill smsd to stop it.

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:

  • Recommended way: Define user account settings in sms3 script and use it to start the smsd by the root.
  • Start the smsd by the unpriviledged user.

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:

  • There is To: field for primary number.
  • There is also Alternate_to: field for an alternate number. The smsd does not use this field by itself.
  • Status report is requested: Report: yes (or smsd.conf report = yes).
  • The smsd adds Sent: timestamp and Message_id: nr fields automatically to the sent message file.

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:
  • Alternate_to number is taken to the memory and removed from the first message.
  • The modified first message is copied to the temp.
  • To number in the temp message is replaced with a number which was an alternate to.
  • A temp message is moved to the outgoing folder.
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.


smstools3/doc/hardwarecomp.html0000755000175000017500000001342313046362405015564 0ustar kekekeke 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).

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

    You must cut pin 4 (wich is DTR) of the serial connector because the phone disables AT command set if the pin is connected and active. The GM22 does not support ascii mode. Mode=new and baudrate=9600 are the correct settings.

2) Ericsson R320s

    Replace put_command("AT+CREG?\r"...with put_command("AT+CREG?\n"... in modeminit.c. Set baudrate=115200 and mode=new. This phone was reported as unstable by one user.

3) Vodafone Mobile Connect Card

    Enter "memory_start=0" into the config file. If you removed the PIN protection from your SIM card, this modem says that the PIN is locked, which is not true. Remove the pin=1234 line from the config file, then the program will ignore that instead of stopping with an error message. The GNU/Linux driver for 3G/UMTS version can be loaded with the command "modprobe usbserial vendor=0x0af0 product=0x5000". It's assumed that loading the driver works similar for the other versions of that card. The device name is /dev/usb/ttyUSB0.

4) Siemens MC60, M55

    Siemens MC60 and M55 are reported by the user to work with the following settings:
      init = AT+CPMS="ME","ME","ME"
      init2 = AT+CNMI=1,1,0,2
      incoming = high
      report = yes
      baudrate = 115200
      send_delay = 20
      rtscts = no
      (a three pin cable was used)

5) Digi Portserver II

    After >= 3.1.5 use a modem setting send_delay = 0 to get higher speed for sending.

6) SIMCOM SIM600

    After >= 3.1.5 use a modem setting check_memory_method = 5. See the How to configure for details.

smstools3/doc/national_language_shift_tables.html0000755000175000017500000044141113060527261021310 0ustar kekekeke SMS Server Tools 3

SMS Server Tools 3

Home

National Language Shift Tables

SMS Server Tools 3 version 3.1.19.
Character set is UTF-8.
Codes shown are UCS-2BE.
Based on 3GPP TS 23.038 version 13.0.0 Release 13.
Tables marked with *) are shown using a font from Google Fonts Early Access.

Tables are:

basic (0)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00@
 
Δ
0394
SP
 
0
 
¡
 
P
 
¿
 
p
 
0x00|
 
0x01£
 
_
 
!
 
1
 
A
 
Q
 
a
 
q
 
0x01
0x02$
 
Φ
03A6
"
 
2
 
B
 
R
 
b
 
r
 
0x02
0x03¥
 
Γ
0393
#
 
3
 
C
 
S
 
c
 
s
 
0x03
0x04è
 
Λ
039B
¤
 
4
 
D
 
T
 
d
 
t
 
0x04^
 
0x05é
 
Ω
03A9
%
 
5
 
E
 
U
 
e
 
u
 
0x05
20AC
0x06ù
 
Π
03A0
&
 
6
 
F
 
V
 
f
 
v
 
0x06
0x07ì
 
Ψ
03A8
'
 
7
 
G
 
W
 
g
 
w
 
0x07
0x08ò
 
Σ
03A3
(
 
8
 
H
 
X
 
h
 
x
 
0x08{
 
0x09Ç
 
Θ
0398
)
 
9
 
I
 
Y
 
i
 
y
 
0x09}
 
0x0ALF
 
Ξ
039E
*
 
:
 
J
 
Z
 
j
 
z
 
0x0AFF
 
0x0BØ
 
+
 
;
 
K
 
Ä
 
k
 
ä
 
0x0B
0x0Cø
 
Æ
 
,
 
<
 
L
 
Ö
 
l
 
ö
 
0x0C[
 
0x0DCR
 
æ
 
-
 
=
 
M
 
Ñ
 
m
 
ñ
 
0x0D~
 
0x0EÅ
 
ß
 
.
 
>
 
N
 
Ü
 
n
 
ü
 
0x0E]
 
0x0Få
 
É
 
/
 
?
 
O
 
§
 
o
 
à
 
0x0F\
 

 top 

Turkish (1)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00@
 
Δ
0394
SP
 
0
 
İ
0130
P
 
ç
 
p
 
0x00|
 
0x01£
 
_
 
!
 
1
 
A
 
Q
 
a
 
q
 
0x01
0x02$
 
Φ
03A6
"
 
2
 
B
 
R
 
b
 
r
 
0x02
0x03¥
 
Γ
0393
#
 
3
 
C
 
S
 
c
 
s
 
0x03Ş
015E
ç
 
ş
015F
0x04
20AC
Λ
039B
¤
 
4
 
D
 
T
 
d
 
t
 
0x04^
 
0x05é
 
Ω
03A9
%
 
5
 
E
 
U
 
e
 
u
 
0x05
20AC
0x06ù
 
Π
03A0
&
 
6
 
F
 
V
 
f
 
v
 
0x06
0x07ı
0131
Ψ
03A8
'
 
7
 
G
 
W
 
g
 
w
 
0x07Ğ
011E
ğ
011F
0x08ò
 
Σ
03A3
(
 
8
 
H
 
X
 
h
 
x
 
0x08{
 
0x09Ç
 
Θ
0398
)
 
9
 
I
 
Y
 
i
 
y
 
0x09}
 
İ
0130
ı
0131
0x0ALF
 
Ξ
039E
*
 
:
 
J
 
Z
 
j
 
z
 
0x0AFF
 
0x0BĞ
011E
+
 
;
 
K
 
Ä
 
k
 
ä
 
0x0B
0x0Cğ
011F
Ş
015E
,
 
<
 
L
 
Ö
 
l
 
ö
 
0x0C[
 
0x0DCR
 
ş
015F
-
 
=
 
M
 
Ñ
 
m
 
ñ
 
0x0D~
 
0x0EÅ
 
ß
 
.
 
>
 
N
 
Ü
 
n
 
ü
 
0x0E]
 
0x0Få
 
É
 
/
 
?
 
O
 
§
 
o
 
à
 
0x0F\
 

 top 

Spanish (2)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00@
 
Δ
0394
SP
 
0
 
¡
 
P
 
¿
 
p
 
0x00|
 
0x01£
 
_
 
!
 
1
 
A
 
Q
 
a
 
q
 
0x01Á
 
á
 
0x02$
 
Φ
03A6
"
 
2
 
B
 
R
 
b
 
r
 
0x02
0x03¥
 
Γ
0393
#
 
3
 
C
 
S
 
c
 
s
 
0x03
0x04è
 
Λ
039B
¤
 
4
 
D
 
T
 
d
 
t
 
0x04^
 
0x05é
 
Ω
03A9
%
 
5
 
E
 
U
 
e
 
u
 
0x05Ú
 

20AC
ú
 
0x06ù
 
Π
03A0
&
 
6
 
F
 
V
 
f
 
v
 
0x06
0x07ì
 
Ψ
03A8
'
 
7
 
G
 
W
 
g
 
w
 
0x07
0x08ò
 
Σ
03A3
(
 
8
 
H
 
X
 
h
 
x
 
0x08{
 
0x09Ç
 
Θ
0398
)
 
9
 
I
 
Y
 
i
 
y
 
0x09ç
 
}
 
Í
 
í
 
0x0ALF
 
Ξ
039E
*
 
:
 
J
 
Z
 
j
 
z
 
0x0AFF
 
0x0BØ
 
+
 
;
 
K
 
Ä
 
k
 
ä
 
0x0B
0x0Cø
 
Æ
 
,
 
<
 
L
 
Ö
 
l
 
ö
 
0x0C[
 
0x0DCR
 
æ
 
-
 
=
 
M
 
Ñ
 
m
 
ñ
 
0x0D~
 
0x0EÅ
 
ß
 
.
 
>
 
N
 
Ü
 
n
 
ü
 
0x0E]
 
0x0Få
 
É
 
/
 
?
 
O
 
§
 
o
 
à
 
0x0F\
 
Ó
 
ó
 

 top 

Portuguese (3)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00@
 
Δ
0394
SP
 
0
 
Í
 
P
 
~
 
p
 
0x00|
 
0x01£
 
_
 
!
 
1
 
A
 
Q
 
a
 
q
 
0x01À
 
Â
 
0x02$
 
ª
 
"
 
2
 
B
 
R
 
b
 
r
 
0x02Φ
03A6
0x03¥
 
Ç
 
#
 
3
 
C
 
S
 
c
 
s
 
0x03Γ
0393
0x04ê
 
À
 
º
 
4
 
D
 
T
 
d
 
t
 
0x04^
 
0x05é
 

221E
%
 
5
 
E
 
U
 
e
 
u
 
0x05ê
 
Ω
03A9
Ú
 

20AC
ú
 
0x06ú
 
^
 
&
 
6
 
F
 
V
 
f
 
v
 
0x06Π
03A0
0x07í
 
\
 
'
 
7
 
G
 
W
 
g
 
w
 
0x07Ψ
03A8
0x08ó
 

20AC
(
 
8
 
H
 
X
 
h
 
x
 
0x08Σ
03A3
{
 
0x09ç
 
Ó
 
)
 
9
 
I
 
Y
 
i
 
y
 
0x09ç
 
Θ
0398
}
 
Í
 
í
 
0x0ALF
 
|
 
*
 
:
 
J
 
Z
 
j
 
z
 
0x0AFF
 
0x0BÔ
 
+
 
;
 
K
 
Ã
 
k
 
ã
 
0x0BÔ
 
Ã
 
ã
 
0x0Cô
 
Â
 
,
 
<
 
L
 
Õ
 
l
 
õ
 
0x0Cô
 
[
 
Õ
 
õ
 
0x0DCR
 
â
 
-
 
=
 
M
 
Ú
 
m
 
`
 
0x0D~
 
0x0EÁ
 
Ê
 
.
 
>
 
N
 
Ü
 
n
 
ü
 
0x0EÁ
 
]
 
0x0Fá
 
É
 
/
 
?
 
O
 
§
 
o
 
à
 
0x0Fá
 
Ê
 
\
 
Ó
 
ó
 
â
 

 top 

Bengali and Assemese (4)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0981

0990
SP
 
0
 

09AC

09BE

09CE
p
 
0x00@
 
<
 

09EC

09F6
|
 
P
 
0x01
0982
!
 
1
 

09AD
ি
09BF
a
 
q
 
0x01£
 
=
 

09ED

09F7
A
 
Q
 
0x02
0983

099F
2
 

09AE

09C0
b
 
r
 
0x02$
 
>
 

09EE

09F8
B
 
R
 
0x03
0985

0993

09A0
3
 

09AF

09C1
c
 
s
 
0x03¥
 
¡
 

09EF

09F9
C
 
S
 
0x04
0986

0994

09A1
4
 

09B0

09C2
d
 
t
 
0x04¿
 
^
 

09DF

09FA
D
 
T
 
0x05
0987

0995

09A2
5
 

09C3
e
 
u
 
0x05"
 
¡
 

09E0
E
 
U
 

20AC
0x06
0988

0996

09A3
6
 

09B2

09C4
f
 
v
 
0x06¤
 
_
 

09E1
F
 
V
 
0x07
0989

0997

09A4
7
 
g
 
w
 
0x07%
 
#
 

09E2
G
 
W
 
0x08
098A

0998
)
 
8
 
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
098B

0999
(
 
9
 

09C7
i
 
y
 
0x09'
 

09E6
}
 
I
 
Y
 
0x0ALF
 

099A

09A5
:
 

09B6

09C8
j
 
z
 
0x0AFF
 

09E7

09E3
J
 
Z
 
0x0B
098C

09A6
;
 

09B7
k
 

09D7
0x0B*
 

09F2
K
 
0x0C
099B
,
 

09B8
l
 

09DC
0x0C+
 

09E8

09F3
[
 
L
 
0x0DCR
 

099C

09A7

09AA

09B9

09CB
m
 

09DD
0x0D
09E9

09F4
~
 
M
 
0x0E
099D
.
 

09AB

09BC

09CC
n
 

09F0
0x0E-
 

09EA

09F5
]
 
N
 
0x0F
098F

099E

09A8
?
 

09BD

09CD
o
 

09F1
0x0F/
 

09EB
\
 
O
 

 top 

Gujarati (5)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0A81

0A90
SP
 
0
 

0AAC

0ABE

0AD0
p
 
0x00@
 
<
 

0AEA
|
 
P
 
0x01
0A82

0A91
!
 
1
 

0AAD
િ
0ABF
a
 
q
 
0x01£
 
=
 

0AEB
A
 
Q
 
0x02
0A83

0A9F
2
 

0AAE

0AC0
b
 
r
 
0x02$
 
>
 

0AEC
B
 
R
 
0x03
0A85

0A93

0AA0
3
 

0AAF

0AC1
c
 
s
 
0x03¥
 
¡
 

0AED
C
 
S
 
0x04
0A86

0A94

0AA1
4
 

0AB0

0AC2
d
 
t
 
0x04¿
 
^
 

0AEE
D
 
T
 
0x05
0A87

0A95

0AA2
5
 

0AC3
e
 
u
 
0x05"
 
¡
 

0AEF
E
 
U
 

20AC
0x06
0A88

0A96

0AA3
6
 

0AB2

0AC4
f
 
v
 
0x06¤
 
_
 
F
 
V
 
0x07
0A89

0A97

0AA4
7
 

0AB3

0AC5
g
 
w
 
0x07%
 
#
 
G
 
W
 
0x08
0A8A

0A98
)
 
8
 
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0A8B

0A99
(
 
9
 

0AB5

0AC7
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0A9A

0AA5
:
 

0AB6

0AC8
j
 
z
 
0x0AFF
 

0965
J
 
Z
 
0x0B
0A8C

0AA6
;
 

0AB7

0AC9
k
 

0AE0
0x0B*
 
K
 
0x0C
0A8D

0A9B
,
 

0AB8
l
 

0AE1
0x0C+
 

0AE6
[
 
L
 
0x0DCR
 

0A9C

0AA7

0AAA

0AB9

0ACB
m
 

0AE2
0x0D
0AE7
~
 
M
 
0x0E
0A9D
.
 

0AAB

0ABC

0ACC
n
 

0AE3
0x0E-
 

0AE8
]
 
N
 
0x0F
0A8F

0A9E

0AA8
?
 

0ABD

0ACD
o
 

0AF1
0x0F/
 

0AE9
\
 
O
 

 top 

Hindi (6)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0901

0910
SP
 
0
 

092C

093E

0950
p
 
0x00@
 
<
 

096A

095B
|
 
P
 
0x01
0902

0911
!
 
1
 

092D
ि
093F
a
 
q
 
0x01£
 
=
 

096B

095C
A
 
Q
 
0x02
0903

0912

091F
2
 

092E

0940
b
 
r
 
0x02$
 
>
 

096C

095D
B
 
R
 
0x03
0905

0913

0920
3
 

092F

0941
c
 
s
 
0x03¥
 
¡
 

096D

095E
C
 
S
 
0x04
0906

0914

0921
4
 

0930

0942
d
 
t
 
0x04¿
 
^
 

096E

095F
D
 
T
 
0x05
0907

0915

0922
5
 

0931

0943
e
 
u
 
0x05"
 
¡
 

096F

0960
E
 
U
 

20AC
0x06
0908

0916

0923
6
 

0932

0944
f
 
v
 
0x06¤
 
_
 

0951

0961
F
 
V
 
0x07
0909

0917

0924
7
 

0933

0945
g
 
w
 
0x07%
 
#
 

0952

0962
G
 
W
 
0x08
090A

0918
)
 
8
 

0934

0946
h
 
x
 
0x08&
 
*
 
{
 

0963
H
 
X
 
0x09
090B

0919
(
 
9
 

0935

0947
i
 
y
 
0x09'
 

0964
}
 

0970
I
 
Y
 
0x0ALF
 

091A

0925
:
 

0936

0948
j
 
z
 
0x0AFF
 

0965

0953

0971
J
 
Z
 
0x0B
090C

0926
;
 

0937

0949
k
 

0972
0x0B*
 

0954
K
 
0x0C
090D

091B
,
 

0929

0938

094A
l
 

097B
0x0C+
 

0966

0958
[
 
L
 
0x0DCR
 

091C

0927

092A

0939

094B
m
 

097C
0x0D
0967

0959
~
 
M
 
0x0E
090E

091D
.
 

092B

093C

094C
n
 

097E
0x0E-
 

0968

095A
]
 
N
 
0x0F
090F

091E

0928
?
 

093D

094D
o
 
ॿ
097F
0x0F/
 

0969
\
 
O
 

 top 

Kannada (7)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0C90
SP
 
0
 

0CAC

0CBE

0CD5
p
 
0x00@
 
<
 

0CEA
|
 
P
 
0x01
0C82
!
 
1
 

0CAD
ಿ
0CBF
a
 
q
 
0x01£
 
=
 

0CEB
A
 
Q
 
0x02
0C83

0C92

0C9F
2
 

0CAE

0CC0
b
 
r
 
0x02$
 
>
 

0CEC
B
 
R
 
0x03
0C85

0C93

0CA0
3
 

0CAF

0CC1
c
 
s
 
0x03¥
 
¡
 

0CED
C
 
S
 
0x04
0C86

0C94

0CA1
4
 

0CB0

0CC2
d
 
t
 
0x04¿
 
^
 

0CEE
D
 
T
 
0x05
0C87

0C95

0CA2
5
 

0CB1

0CC3
e
 
u
 
0x05"
 
¡
 

0CEF
E
 
U
 

20AC
0x06
0C88

0C96

0CA3
6
 

0CB2

0CC4
f
 
v
 
0x06¤
 
_
 

0CDE
F
 
V
 
0x07
0C89

0C97

0CA4
7
 

0CB3
g
 
w
 
0x07%
 
#
 

0CF1
G
 
W
 
0x08
0C8A

0C98
)
 
8
 

0CC6
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0C8B

0C99
(
 
9
 

0CB5

0CC7
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0C9A

0CA5
:
 

0CB6

0CC8
j
 
z
 
0x0AFF
 

0965

0CF2
J
 
Z
 
0x0B
0C8C

0CA6
;
 

0CB7
k
 

0CD6
0x0B*
 
K
 
0x0C
0C9B
,
 

0CB8

0CCA
l
 

0CE0
0x0C+
 

0CE6
[
 
L
 
0x0DCR
 

0C9C

0CA7

0CAA

0CB9

0CCB
m
 

0CE1
0x0D
0CE7
~
 
M
 
0x0E
0C8E

0C9D
.
 

0CAB

0CBC

0CCC
n
 

0CE2
0x0E-
 

0CE8
]
 
N
 
0x0F
0C8F

0C9E

0CA8
?
 

0CBD

0CCD
o
 

0CE3
0x0F/
 

0CE9
\
 
O
 

 top 

Malayalam (8)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0D10
SP
 
0
 

0D2C

0D3E

0D57
p
 
0x00@
 
<
 

0D6A

0D7B
|
 
P
 
0x01
0D02
!
 
1
 

0D2D
ി
0D3F
a
 
q
 
0x01£
 
=
 

0D6B

0D7C
A
 
Q
 
0x02
0D03

0D12

0D1F
2
 

0D2E

0D40
b
 
r
 
0x02$
 
>
 

0D6C

0D7D
B
 
R
 
0x03
0D05

0D13

0D20
3
 

0D2F

0D41
c
 
s
 
0x03¥
 
¡
 

0D6D

0D7E
C
 
S
 
0x04
0D06

0D14

0D21
4
 

0D30

0D42
d
 
t
 
0x04¿
 
^
 

0D6E
ൿ
0D7F
D
 
T
 
0x05
0D07

0D15

0D22
5
 

0D31

0D43
e
 
u
 
0x05"
 
¡
 

0D6F
E
 
U
 

20AC
0x06
0D08

0D16

0D23
6
 

0D32

0D44
f
 
v
 
0x06¤
 
_
 

0D70
F
 
V
 
0x07
0D09

0D17

0D24
7
 

0D33
g
 
w
 
0x07%
 
#
 

0D71
G
 
W
 
0x08
0D0A

0D18
)
 
8
 

0D34

0D46
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0D0B

0D19
(
 
9
 

0D35

0D47
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0D1A

0D25
:
 

0D36

0D48
j
 
z
 
0x0AFF
 

0965

0D72
J
 
Z
 
0x0B
0D0C

0D26
;
 

0D37
k
 

0D60
0x0B*
 

0D73
K
 
0x0C
0D1B
,
 

0D38

0D4A
l
 

0D61
0x0C+
 

0D66

0D74
[
 
L
 
0x0DCR
 

0D1C

0D27

0D2A

0D39

0D4B
m
 

0D62
0x0D
0D67

0D75
~
 
M
 
0x0E
0D0E

0D1D
.
 

0D2B

0D4C
n
 

0D63
0x0E-
 

0D68

0D7A
]
 
N
 
0x0F
0D0F

0D1E

0D28
?
 

0D3D

0D4D
o
 

0D79
0x0F/
 

0D69
\
 
O
 

 top 

Oriya (9)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0B01

0B10
SP
 
0
 

0B2C

0B3E

0B56
p
 
0x00@
 
<
 

0B6A
|
 
P
 
0x01
0B02
!
 
1
 

0B2D
ି
0B3F
a
 
q
 
0x01£
 
=
 

0B6B
A
 
Q
 
0x02
0B03

0B1F
2
 

0B2E

0B40
b
 
r
 
0x02$
 
>
 

0B6C
B
 
R
 
0x03
0B05

0B13

0B20
3
 

0B2F

0B41
c
 
s
 
0x03¥
 
¡
 

0B6D
C
 
S
 
0x04
0B06

0B14

0B21
4
 

0B30

0B42
d
 
t
 
0x04¿
 
^
 

0B6E
D
 
T
 
0x05
0B07

0B15

0B22
5
 

0B43
e
 
u
 
0x05"
 
¡
 

0B6F
E
 
U
 

20AC
0x06
0B08

0B16

0B23
6
 

0B32

0B44
f
 
v
 
0x06¤
 
_
 

0B5C
F
 
V
 
0x07
0B09

0B17

0B24
7
 

0B33
g
 
w
 
0x07%
 
#
 

0B5D
G
 
W
 
0x08
0B0A

0B18
)
 
8
 
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0B0B

0B19
(
 
9
 

0B35

0B47
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0B1A

0B25
:
 

0B36

0B48
j
 
z
 
0x0AFF
 

0965

0B5F
J
 
Z
 
0x0B
0B0C

0B26
;
 

0B37
k
 

0B57
0x0B*
 

0B70
K
 
0x0C
0B1B
,
 

0B38
l
 

0B60
0x0C+
 

0B66

0B71
[
 
L
 
0x0DCR
 

0B1C

0B27

0B2A

0B39

0B4B
m
 

0B61
0x0D
0B67
~
 
M
 
0x0E
0B1D
.
 

0B2B

0B3C

0B4C
n
 

0B62
0x0E-
 

0B68
]
 
N
 
0x0F
0B0F

0B1E

0B28
?
 

0B3D

0B4D
o
 

0B63
0x0F/
 

0B69
\
 
O
 

 top 

Punjabi (10)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0A01

0A10
SP
 
0
 

0A2C

0A3E

0A51
p
 
0x00@
 
<
 

0A6A
|
 
P
 
0x01
0A02
!
 
1
 

0A2D
ਿ
0A3F
a
 
q
 
0x01£
 
=
 

0A6B
A
 
Q
 
0x02
0A03

0A1F
2
 

0A2E

0A40
b
 
r
 
0x02$
 
>
 

0A6C
B
 
R
 
0x03
0A05

0A13

0A20
3
 

0A2F

0A41
c
 
s
 
0x03¥
 
¡
 

0A6D
C
 
S
 
0x04
0A06

0A14

0A21
4
 

0A30

0A42
d
 
t
 
0x04¿
 
^
 

0A6E
D
 
T
 
0x05
0A07

0A15

0A22
5
 
e
 
u
 
0x05"
 
¡
 

0A6F
E
 
U
 

20AC
0x06
0A08

0A16

0A23
6
 

0A32
f
 
v
 
0x06¤
 
_
 

0A59
F
 
V
 
0x07
0A09

0A17

0A24
7
 

0A33
g
 
w
 
0x07%
 
#
 

0A5A
G
 
W
 
0x08
0A0A

0A18
)
 
8
 
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0A19
(
 
9
 

0A35

0A47
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0A1A

0A25
:
 

0A36

0A48
j
 
z
 
0x0AFF
 

0965

0A5B
J
 
Z
 
0x0B
0A26
;
 
k
 

0A70
0x0B*
 

0A5C
K
 
0x0C
0A1B
,
 

0A38
l
 

0A71
0x0C+
 

0A66

0A5E
[
 
L
 
0x0DCR
 

0A1C

0A27

0A2A

0A39

0A4B
m
 

0A72
0x0D
0A67

0A75
~
 
M
 
0x0E
0A1D
.
 

0A2B

0A3C

0A4C
n
 

0A73
0x0E-
 

0A68
]
 
N
 
0x0F
0A0F

0A1E

0A28
?
 

0A4D
o
 

0A74
0x0F/
 

0A69
\
 
O
 

 top 

Tamil (11)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0B90
SP
 
0
 

0BBE

0BD0
p
 
0x00@
 
<
 

0BEA
|
 
P
 
0x01
0B82
!
 
1
 
ி
0BBF
a
 
q
 
0x01£
 
=
 

0BEB
A
 
Q
 
0x02
0B83

0B92

0B9F
2
 

0BAE

0BC0
b
 
r
 
0x02$
 
>
 

0BEC
B
 
R
 
0x03
0B85

0B93
3
 

0BAF

0BC1
c
 
s
 
0x03¥
 
¡
 

0BED
C
 
S
 
0x04
0B86

0B94
4
 

0BB0

0BC2
d
 
t
 
0x04¿
 
^
 

0BEE
D
 
T
 
0x05
0B87

0B95
5
 

0BB1
e
 
u
 
0x05"
 
¡
 

0BEF
E
 
U
 

20AC
0x06
0B88

0BA3
6
 

0BB2
f
 
v
 
0x06¤
 
_
 

0BF3
F
 
V
 
0x07
0B89

0BA4
7
 

0BB3
g
 
w
 
0x07%
 
#
 

0BF4
G
 
W
 
0x08
0B8A
)
 
8
 

0BB4

0BC6
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0B99
(
 
9
 

0BB5

0BC7
i
 
y
 
0x09'
 

0964
}
 
I
 
Y
 
0x0ALF
 

0B9A
:
 

0BB6

0BC8
j
 
z
 
0x0AFF
 

0965

0BF5
J
 
Z
 
0x0B;
 

0BB7
k
 

0BD7
0x0B*
 

0BF6
K
 
0x0C,
 

0BA9

0BB8

0BCA
l
 

0BF0
0x0C+
 

0BE6

0BF7
[
 
L
 
0x0DCR
 

0B9C

0BAA

0BB9

0BCB
m
 

0BF1
0x0D
0BE7

0BF8
~
 
M
 
0x0E
0B8E
.
 

0BCC
n
 

0BF2
0x0E-
 

0BE8

0BFA
]
 
N
 
0x0F
0B8F

0B9E

0BA8
?
 

0BCD
o
 

0BF9
0x0F/
 

0BE9
\
 
O
 

 top 

Telugu (12)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00
0C01

0C10
SP
 
0
 

0C2C

0C3E

0C55
p
 
0x00@
 
<
 

0C6A

0C7D
|
 
P
 
0x01
0C02
!
 
1
 

0C2D
ి
0C3F
a
 
q
 
0x01£
 
=
 

0C6B

0C7E
A
 
Q
 
0x02
0C03

0C12

0C1F
2
 

0C2E

0C40
b
 
r
 
0x02$
 
>
 

0C6C
౿
0C7F
B
 
R
 
0x03
0C05

0C13

0C20
3
 

0C2F

0C41
c
 
s
 
0x03¥
 
¡
 

0C6D
C
 
S
 
0x04
0C06

0C14

0C21
4
 

0C30

0C42
d
 
t
 
0x04¿
 
^
 

0C6E
D
 
T
 
0x05
0C07

0C15

0C22
5
 

0C31

0C43
e
 
u
 
0x05"
 
¡
 

0C6F
E
 
U
 

20AC
0x06
0C08

0C16

0C23
6
 

0C32

0C44
f
 
v
 
0x06¤
 
_
 

0C58
F
 
V
 
0x07
0C09

0C17

0C24
7
 

0C33
g
 
w
 
0x07%
 
#
 

0C59
G
 
W
 
0x08
0C0A

0C18
)
 
8
 

0C46
h
 
x
 
0x08&
 
*
 
{
 
H
 
X
 
0x09
0C0B

0C19
(
 
9
 

0C35

0C47
i
 
y
 
0x09'
 
}
 
I
 
Y
 
0x0ALF
 

0C1A

0C25
:
 

0C36

0C48
j
 
z
 
0x0AFF
 

0C78
J
 
Z
 
0x0B
0C0C

0C26
;
 

0C37
k
 

0C56
0x0B*
 

0C79
K
 
0x0C
0C1B
,
 

0C38

0C4A
l
 

0C60
0x0C+
 

0C66

0C7A
[
 
L
 
0x0DCR
 

0C1C

0C27

0C2A

0C39

0C4B
m
 

0C61
0x0D
0C67

0C7B
~
 
M
 
0x0E
0C0E

0C1D
.
 

0C2B

0C4C
n
 

0C62
0x0E-
 

0C68

0C7C
]
 
N
 
0x0F
0C0F

0C1E

0C28
?
 

0C3D

0C4D
o
 

0C63
0x0F/
 

0C69
\
 
O
 

 top 

Urdu (13)  *)
Locking
Shift
0x000x100x200x300x400x500x600x70Single
Shift
0x000x100x200x300x400x500x600x70
0x00ا
0627
ث
062B
SP
 
0
 
ص
0635
ں
06BA
ٔ
0654
p
 
0x00@
 
<
 
۴
06F4
ؓ
0613
|
 
P
 
0x01آ
0622
ج
062C
!
 
1
 
ض
0636
ڻ
06BB
a
 
q
 
0x01£
 
=
 
۵
06F5
ؔ
0614
A
 
Q
 
0x02ب
0628
ځ
0681
ڏ
068F
2
 
ط
0637
ڼ
06BC
b
 
r
 
0x02$
 
>
 
۶
06F6
؛
061B
B
 
R
 
0x03ٻ
067B
ڄ
0684
ڍ
068D
3
 
ظ
0638
و
0648
c
 
s
 
0x03¥
 
¡
 
۷
06F7
؟
061F
C
 
S
 
0x04ڀ
0680
ڃ
0683
ذ
0630
4
 
ع
0639
ۄ
06C4
d
 
t
 
0x04¿
 
^
 
۸
06F8
ـ
0640
D
 
T
 
0x05پ
067E
څ
0685
ر
0631
5
 
ف
0641
ە
06D5
e
 
u
 
0x05"
 
¡
 
۹
06F9
ْ
0652
E
 
U
 

20AC
0x06ڦ
06A6
چ
0686
ڑ
0691
6
 
ق
0642
ہ
06C1
f
 
v
 
0x06¤
 
_
 
،
060C
٘
0658
F
 
V
 
0x07ت
062A
ڇ
0687
ړ
0693
7
 
ک
06A9
ھ
06BE
g
 
w
 
0x07%
 
#
 
؍
060D
٫
066B
G
 
W
 
0x08ۂ
06C2
ح
062D
)
 
8
 
ڪ
06AA
ء
0621
h
 
x
 
0x08&
 
*
 
{
 
٬
066C
H
 
X
 
0x09ٿ
067F
خ
062E
(
 
9
 
ګ
06AB
ی
06CC
i
 
y
 
0x09'
 
؀
0600
}
 
ٲ
0672
I
 
Y
 
0x0ALF
 
د
062F
ڙ
0699
:
 
گ
06AF
ې
06D0
j
 
z
 
0x0AFF
 
؁
0601
؎
060E
ٳ
0673
J
 
Z
 
0x0Bٹ
0679
ز
0632
;
 
ڳ
06B3
ے
06D2
k
 
ٕ
0655
0x0B*
 
؏
060F
ۍ
06CD
K
 
0x0Cٽ
067D
ڌ
068C
,
 
ښ
069A
ڱ
06B1
ٍ
064D
l
 
ّ
0651
0x0C+
 
۰
06F0
ؐ
0610
[
 
L
 
0x0DCR
 
ڈ
0688
ږ
0696
س
0633
ل
0644
ِ
0650
m
 
ٓ
0653
0x0D۱
06F1
ؑ
0611
~
 
M
 
0x0Eٺ
067A
ډ
0689
.
 
ش
0634
م
0645
ُ
064F
n
 
ٖ
0656
0x0E-
 
۲
06F2
ؒ
0612
]
 
N
 
0x0Fټ
067C
ڊ
068A
ژ
0698
?
 
ن
0646
ٗ
0657
o
 
ٰ
0670
0x0F/
 
۳
06F3
\
 
۔
06D4
O
 

 top 


smstools3/doc/localizing.html0000755000175000017500000002445713062516445015257 0ustar kekekeke 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. NOTE: those translations are from 2007 version of smsd, and not updated which means that all current headers are not presented in those files. Headers listed in this page are from the version 3.1.16 (released 2017).

Headers written using Unicode (UCS2) 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:
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.
incoming yes/no. Default: yes. Defines if incoming message files are written using localized headers.
no_chars Used like yes_chars.
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'.
yes_word Defines a word to be used when smsd writes "yes" word to the file, like "kyllä".

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.

Alphabet: Msg file input. Incoming message.
Autosplit: Msg file input.
Binary: Msg file input (sets alphabet to 1 or 0).
CALL MISSED Incoming call, written to the message body.
Cannot be omitted.
Call_type: Incoming message from phonebook.
Class: Msg file input.
DCS_hex: Msg file input.
Description: Written to all message files..
Fail_reason: Failed outgoing message (currently only spooling uses this).
Failed: Timestamp for failed message.
Flash: Msg file input.
From: Msg file input: informative. Incoming message: senders address.
Cannot be omitted.
From_SMSC: Incoming message: senders SMSC.
From_TOA: Incoming message: senders Type Of Address.
Hex: Msg file input.
IMEI: Incoming / Sent (or failed) message.
IMSI: Incoming / Sent (or failed) message, identification code if supported.
Include: Msg file input.
Incomplete: Incoming message.
Language: Msg file input, incoming message.
Language_ext: Msg file input, incoming message.
Length: Incoming message, text/data length.
Macro: Msg file input.
Message_id: Sent (successfully) message.
Message_reference: Msg file input.
missed Incoming call type. Note: this is a value.
Cannot be omitted.
Modem: Sent message, device name (=modemname).
Name: Incoming message: name from the modem response (???).
Number: SIM card's telephone number (if defined in the config file).
Original_filename: Stored when moving file from outgoing directory and unique filenames are used in the spooler.
Ping: 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).
Provider: Msg file input.
Received: Incoming message timestamp.
Cannot be omitted.
Reject_duplicates: Msg file input.
Replace: Msg file input. Incoming message: exists with code if replace code was defined.
Reply_path: Msg file input.
Report: Msg file input. Incoming message: report was asked yes/no.
Result: Voicecall. Result string from a modem.
Retries: Msg file input.
Sending_time: Outgoing time.
Sent: Outgoing timestamp. Incoming: senders date & time (from PDU).
SMSC: Msg file input.
Subject: Incoming message, modemname.
System_message: Msg file input.
Text_is_pdu: Msg file input.
To: Msg file input.
To_TOA: Msg file input.
UDH-Type: Incoming message, type(s) of content of UDH if present.
Validity: Msg file input.
Note that if word values are used, they should be written using english.
Voicecall: Msg file input.
Queue: Msg file input.


smstools3/doc/windows.html0000755000175000017500000001177413046362405014611 0ustar kekekeke 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. 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.


smstools3/doc/configure2.html0000755000175000017500000002000713046362405015147 0ustar kekekeke 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 


smstools3/doc/eventhandler.html0000755000175000017500000000605413046362405015571 0ustar kekekeke 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.

Smsd gives the following arguments to the alarmhandler:

  • the keyword ALARM
  • a date in the format yyyy-mm-dd
  • a time in the format hh:mm:ss
  • the alarm severity (1 digit number)
  • the modem name or SMSD
  • the alarm text

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.


smstools3/doc/index.html0000755000175000017500000001470113055061006014210 0ustar kekekeke 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).

  • SMS Server Tools is originally created by Mr. Stefan Frings.
  • Developing of version 2.x is freezed couple of years ago. Bugs are fixed if any critical one found. 2.x website.

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, improvements and enhancements are done (situation in 2009). Take a look at the version history for details.
  • Developing of version 3.x continues...

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 useful 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.

smstools3/doc/blacklist.html0000755000175000017500000001044413046362405015060 0ustar kekekeke 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

  • Disallow all short numbers
  • Blacklist regular international format and blacklist any alternative number formats. Start the alternative formats with an s.
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.


smstools3/doc/fileformat.html0000755000175000017500000007355313077160020015243 0ustar kekekeke 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:

Alphabet Tells the program what character set is used in this sms file. Possible values are;

binary The short message contains 8-bit binary data, no text.
GSM 7 bit character set, as described in the GSM specification.
ISO
Latin
Ansi
Normal 8 bit character set, also called Ansi or Latin-9. All three keywords do the same.
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.
UTF-8 8 bit multibyte character set. Available in version >= 3.1.16.

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

NOTE the difference of ISO and UTF-8 in version >= 3.1.16:

Handling of alphabets is enhanced and all conversions are now done using internal routines which work well with cyrillic languages too. For backward compatibility, the alphabet of message file still defaults to ISO-8859-15, and this can be changed to UTF-8 in the configuration, or a header Alphabet: UTF can should be used.

Alphabet: ISO

  • depending on the global setting outgoing_utf8 (which defaults to yes), UTF-8 is still accepted and converted.
  • optical replacement is done, depending on the modem setting cs_convert_optical (which defaults to yes).
  • characters are converted to the GSM alphabet, and missing characters are dropped.

Alphabet: UTF

  • like with ISO, but if any characters cannot be converted to the GSM alphabet, whole message is converted to the UCS2 alphabet.
  • if conversion to UCS2 is done, each message part will have less characters and therefore more parts will be required, but the text delivers as it was written.

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)

Class Number. Available from version >= 3.1.16.

Sets the Message Class, 0...3.

DCS_hex Value. Available from version >= 3.1.16.

Sets Data Coding Scheme in the PDU. Note that value must be represented as two hexadecimal digits.

Flash Boolean value. If yes, then the message appears directly on the phones display. Most phones support this feature, but not all.
From Senders name or phone number. This field has currently no function to the software.
Hex Boolean value. Available from version >= 3.0.

Together with Alphabet: binary setting the binary data can be presented in hexadecimal format.
After version >= 3.1.16 this can be used with text messages other than UCS2 too.

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.

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.

Language
Language_ext
Available from version >= 3.1.16.

These settings define National Language Shift Tables to be used. Text body must be written using UTF-8 character set. Value can be number, or variable length string which first macthes (case insensitive).
Choices are:

  • 0 = basic
  • 1 = Turkish
  • 2 = Spanish
  • 3 = Portuguese
  • 4 = Bengali and Assemese, or Bengali, or Assemese
  • 5 = Gujarati
  • 6 = Hindi
  • 7 = Kannada
  • 8 = Malayalam
  • 9 = Oriya
  • 10 = Punjabi
  • 11 = Tamil
  • 12 = Telugu
  • 13 = Urdu

Usually it is not necessary to set Language_ext value. When Language is set, Language_ext defaults to the same, and if only Language_ext is defined, Language defaults to basic character set. If nothing is set, default values are taken from the configuration.

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.

Message_reference Number. Available from version >= 3.1.16.

Sets TP-MR field in the PDU. Number can be 0...255.

Ping Boolean value. Available from version >= 3.1.16.

Selects the Short Message Type 0, which is also known as a silent SMS. As this kind of SMS is not stored by the receiving device, report is always requested, even if report was disabled in the configuration.

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

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.
Reject_duplicates Boolean value. Available from version >= 3.1.16.

Sets TP-RD bit in the PDU.

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.

Reply_path Boolean value. Available from version >= 3.1.16.

Sets TP-RP bit in the PDU.

Report Boolean value. Controls if a status report is requested for this message. Without this line, the setting from config file is used.
Retries Number. Available from version >= 3.1.16.

Defines how many times smsd will retry if sending fails. This overrides send_retries setting.

SMSC Phone number of the SMSC. From version >= 3.1 this setting is ignored if there is no smsc set in the config file.
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 Data 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.

Text_is_pdu Boolean value. Available from version >= 3.1.16.

If this feature is enabled by defining text_is_pdu_key in the configuration, and To: number matches that key, message body is handled as ready made PDU.

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.

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.
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.

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.

Available from >= 3.1.16.
IMEI IMEI of a modem which handled the message.
NOTICE Tells if some characters was not possible to convert to the target character set.

Available from >= 3.1.20.
Sending_time Tells how long it took to send the whole message. If the message has multiple parts and receive_before_send is set to yes, time to receive messages is included in this value.

Binary data

The data begins after the empty line and goes until end of file. No conversion is applied to the data.

Example:

To: 491721234567
Alphabet: binary
UDH: true

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

Another example, Wap Push with Hex presentation, available from version >= 3.0:

To: 358401234567
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 : si, 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

Example of Wap Push with Macros and Include file:

In this example message body is the same for all messages, and it is saved as different file. This file is included to the file which is sent by smstools. Macros are used. Outgoing file contains only headers which are mandatory, and no message body at all.

File: /var/spool/sms/outgoing/wap_push1
To: 358401234567
Macro: @URL@=smstools3.kekekasvi.com/board.php
Macro: @TEXT@=Visit the SMS Server Tools 3 forum.
Include: /var/spool/sms/include/wap_push_service1

File: /var/spool/sms/include/wap_push_service1
Alphabet: binary
Hex: yes

06 05 04 0B 84 23 F0 01 06 01 AE 02 05
6A : Charset=UTF-8 (MIBEnum 106)
00 45 C6
0C : Token for "href=http://"
INLINESTRING:@URL@
11
INLINESTRING:1
01
INLINESTRING:@TEXT@
01 01

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.

Alphabet Tells the character set of the message text.
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 Senders phone number.
From_SMSC The SMS service centre, that sent you this message.
From_TOA Available from >= 3.0.9. Type Of Address definition of senders phone number.
For example: "91 international, ISDN/telephone".
IMSI Available from >= 3.0.9. International Mobile Subscriber Identity from the SIM, if this request is supported.
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.
Received Time when the message was received by the program.
Replace Available from >= 3.0.9. Replace Short Message Type 1..7 number, if defined.
Report Available from >= 3.0.9. Tells if a status report is going to be returned to the SME.
Sent Time when the message was sent.
Subject The name of the modem that received this message.
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.

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

Discharge_timestamp This is the time, when the message was successfully delivered or when it was discarded by the SMSC.
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.
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:

  • Any number without "s" is an international number.
  • Numbers with "s" are "short" numbers and unknown Type Of Address is used.

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:

    System is located in Finland and it's used to send messages mainly to Finland, Sweden and United Kingdom. Both international and national number formats are used (for finnish numbers) and local short numbers should use national Type Of Address. Global configuration setting international_prefixes = 358,46,44 is enough for normal operation. With this setup, if SMS should be sent to Germany, it can be done with To_TOA: international header.

Another example:

    Still in Finland. Any mobile phone number should work without additional headers. Messages to the short numbers use "s" or will have To_TOA header if necessary. Global configuration setting national_prefixes = 04, 05 or shortly national_prefixes = 0 is enough as finnish mobile numbers start with 04 or 05. Messages to those numbers are sent using national Type Of Address, any other number will use international type.


smstools3/README0000755000175000017500000000027111177772605012344 0ustar kekekekePlease 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. smstools3/install.sh0000755000175000017500000000446710612645125013466 0ustar kekekeke#!/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 smstools3/examples/0000755000175000017500000000000011406643431013264 5ustar kekekekesmstools3/examples/operator_logo2.sms0000644000175000017500000000017710371202746016752 0ustar kekekekeTo: 491722056395 Binary: true 9A` !䲊@'$HJ(E"A(z!y smstools3/examples/smsd.conf.easy0000644000175000017500000000024710371202746016044 0ustar kekekeke# 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 smstools3/examples/smsd.conf.net0000644000175000017500000000032411353110131015650 0ustar kekekeke# 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 smstools3/examples/operator_logo1.sms0000644000175000017500000000025310371202746016744 0ustar kekekekeTo: 491722056395 Binary: true b NΤ ʠ**Τ @@smstools3/examples/received_sms.sms0000644000175000017500000000026510371202746016463 0ustar kekekekeFrom: 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.smstools3/examples/send_sms_unicode.sms0000644000175000017500000000005210371202746017326 0ustar kekekekeTo: 491721234567 Alphabet: UCS2 N-\1[ smstools3/examples/received_report.sms0000644000175000017500000000034610371202746017174 0ustar kekekekeFrom: 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 smstools3/examples/send_sms.sms0000644000175000017500000000012010577752441015626 0ustar kekekekeTo: 491721234567 Hello, this is a text that I can send with SMS Server Tools. smstools3/examples/.procmailrc0000755000175000017500000000020310371202746015416 0ustar kekekekeVERBOSE=off MAILDIR=/var/spool/mail DEFAULT=/var/spool/mail/sms LOGFILE=/var/log/procmail :0 * ^TOsms | /usr/local/bin/email2sms smstools3/examples/README0000644000175000017500000000022311406643377014152 0ustar kekekekeThis folder is not actively updated. More examples can be found in the program support website http://smstools3.kekekasvi.com/index.php?p=support smstools3/examples/smsd.conf.full0000644000175000017500000000606710624006664016055 0ustar kekekeke# 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 smstools3/examples/.qmailrc0000755000175000017500000000003410371202746014715 0ustar kekekeke|/usr/local/bin/email2sms smstools3/examples/smsd.conf.non-root0000644000175000017500000000062310531320773016653 0ustar kekekeke# 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 smstools3/examples/language-ISO-8859-15.fi0000644000175000017500000000212011156744656016665 0ustar kekekeke# 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: = Lhettj: #Flash: = Flash: Provider: = Palveluntarjoaja: Queue: = Jono: Binary: = Binri: Report: = Raportti: Autosplit: = Automaattipilkonta: Validity: = Voimassaolo: Voicecall: = nipuhelu: Replace: = Korvaa: Alphabet: = Merkist: Include: = Liit: Macro: = Makro: Hex: = Heksa: SMSC: = Viestikeskus: Sent: = Lhetetty: Modem: = Modeemi: From_TOA: = Osoitetyyppi: From_SMSC: = Viestikeskus: Name: = Nimi: Received: = Vastaanotettu: Subject: = Otsikko: UDH-Type: = UDH-Tyyppi: Length: = Pituus: Fail_reason: = Eponnistumisen_syy: Failed: = Eponnistunut: IMSI: = Liittymtunniste: Message_id: = Viestitunniste: Priority: = Etuoikeus: Original_filename: = Alkuperinen_tiedostonimi: Call_type: = Puhelutyyppi: missed = vastaamaton CALL MISSED = VASTAAMATON PUHELU Result: = Tulos: Number: = Numero: Incomplete: = Eptydellinen: smstools3/examples/language-UTF-8.fi0000644000175000017500000000214111156744727016202 0ustar kekekeke# 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: smstools3/uninstall.sh0000755000175000017500000000242011441415365014016 0ustar kekekeke#!/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 smstools3/src/0000700000175000017500000000000013102443031012210 5ustar kekekekesmstools3/src/charset.c0000755000175000017500000007243613067453777014072 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 "charset.h" #include "logging.h" #include "smsd_cfg.h" #include "pdu.h" #include "extras.h" #include "charshift.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[] = { 0x80, 0x20, // 3.1.16beta: NONBREAKABLE SPACE --> SPACE 0x09, 0x20, // 3.1.16beta: TAB --> SPACE 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 }; int special_char2gsm(char ch, char *newch) { int table_row = 0; char *table = iso_8859_15_chars; if (!DEVICE.cs_convert_optical) return 0; 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 *missing, char **notice) { int source_count = 0; int char_count = 0; int dest_count = 0; int found; char newch; char logtmp[51]; char tmpch; int bytes; unsigned char ch; char utf8bytes[32]; char utf8char[16]; int i; destination[dest_count] = 0; if (source == 0 || size <= 0) return 0; if (missing) *missing = 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 < size && dest_count < max) { *utf8bytes = 0; *utf8char = 0; ch = source[source_count]; if (outgoing_utf8) { // 3.1.20: Fix: Should still accept ISO character too. If not proper UTF-8 sequence is detected, use the char as it is. if ((bytes = utf8_to_iso_char(&source[source_count], &ch)) <= 0) { ch = source[source_count]; bytes = 1; } strcpy(utf8bytes, "("); for (i = 0; i < bytes; i++) { sprintf(strchr(utf8bytes, 0), "%02X", (unsigned char)source[source_count +i]); sprintf(strchr(utf8char, 0), "%c", (unsigned char)source[source_count +i]); } strcat(utf8bytes, ")"); source_count += bytes; } else source_count++; char_count++; found = 0; if (DEVICE.cs_convert_optical) { found = special_char2gsm(ch, &newch); if (found) { destination[dest_count++] = newch; if (log_charconv) { sprintf(logtmp, "%s%02X[%c]~>%02X", utf8bytes, ch, prch(ch), (unsigned char)newch); if (gsm2char(newch, &tmpch, 1)) sprintf(strchr(logtmp, 0), "[%c]", tmpch); logch("%s ", logtmp); } } } if (!found) { found = char2gsm(ch, &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]", utf8bytes, ch, prch(ch)); if (found > 1 || ch != 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) { if (missing) (*missing)++; tb_sprintf("NOTICE: Cannot convert %i. character %s%s to GSM.", char_count, utf8bytes, utf8char); writelogfile0(LOG_NOTICE, 0, tb + 8); if (notice) strcat_realloc(notice, tb, "\n"); #ifdef DEBUGMSG printf("%s\n", tb); #endif } } 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; char ucs2[2]; 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++) { c = (unsigned char)ascii[idx]; if (c >= 0x81 && c <= 0x8A) { ucs2[0] = 0x03; switch (c) { case 0x81: ucs2[1] = 0x94; break; case 0x82: ucs2[1] = 0xA6; break; case 0x83: ucs2[1] = 0x93; break; case 0x84: ucs2[1] = 0x9B; break; case 0x85: ucs2[1] = 0xA9; break; case 0x86: ucs2[1] = 0xA0; break; case 0x87: ucs2[1] = 0xA8; break; case 0x88: ucs2[1] = 0xA3; break; case 0x89: ucs2[1] = 0x98; break; case 0x8A: ucs2[1] = 0x9E; break; } } else { // Euro character is E282AC in UTF-8, 20AC in UCS-2, but A4 in ISO-8859-15: if (c == 0xA4) c = 0x20AC; ucs2[0] = (unsigned char)((c & 0xFF00) >> 8); ucs2[1] = (unsigned char)(c & 0xFF); } len = ucs2_to_utf8_char(ucs2, tmp); if (len == 0) { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("UTF-8 conversion error with %i. ch 0x%02X %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= 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; char ucs2[2]; 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++) { c = ascii[idx] & 0xFF; // Euro character is E282AC in UTF-8, 20AC in UCS-2, but A4 in ISO-8859-15: if (c == 0xA4) c = 0x20AC; ucs2[0] = (unsigned char)((c & 0xFF00) >> 8); ucs2[1] = (unsigned char)(c & 0xFF); len = ucs2_to_utf8_char(ucs2, tmp); if (len == 0) { if (log_charconv) logch(NULL); writelogfile0(LOG_NOTICE, 0, tb_sprintf("UTF-8 conversion error with %i. ch 0x%02X %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; } 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'; int save_outgoing_utf8 = outgoing_utf8; outgoing_utf8 = 1; len = iso_utf8_2gsm(text, strlen(text), buffer2, sizeof(buffer2), 0, 0); outgoing_utf8 = save_outgoing_utf8; #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; } 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; } int utf8bytes0(char *s, int allow_iso) { int result = 1; int i; unsigned char ch = s[0]; if (ch >= 0x80) { while (((ch <<= 1) & 0x80) != 0) result++; // 3.1.20: Allow ISO character if defined: if (result == 1 && allow_iso) return 1; if (result == 1 || result > 6) return -1; for (i = 1; i < result; i++) { if ((s[i] & 0xC0) != 0x80) { if (allow_iso) return 1; return -1; } } } return result; } int utf8bytes(char *s) { return utf8bytes0(s, 0); } int iso_utf8bytes(char *s) { return utf8bytes0(s, 1); } int iso_utf8chars(char *s) { int result = 0; char *p = s; int i; while (*p) { if ((i = iso_utf8bytes(p)) <= 0) return -1; result++; p += i; } return result; } int iso_utf8_to_ucs2_char(char *utf8, int *len, char *ucs2) { unsigned int c = 0; int i; i = iso_utf8bytes(utf8); if (len) *len = i; switch (i) { case 1: c = (unsigned char)utf8[0]; break; case 2: c = (utf8[0] & 0x1F) << 6 | (utf8[1] & 0x3F); break; case 3: c = (utf8[0] & 0x0F) << 12 | (utf8[1] & 0x3F) << 6 | (utf8[2] & 0x3F); break; default: return 0; } ucs2[0] = (unsigned char)((c & 0xFF00) >> 8); ucs2[1] = (unsigned char)(c & 0xFF); return 1; } // Note: Returns the number of UCS2 characters, not bytes. int iso_utf8_to_ucs2_buffer(char *utf8, char *ucs2, size_t ucs2_size) { char *p = utf8; char *end = utf8 +strlen(utf8); int bytes; size_t dest = 0; int result = 0; while (p < end) { if (dest >= ucs2_size -1) break; if (!iso_utf8_to_ucs2_char(p, &bytes, &ucs2[dest])) break; p += bytes; dest += 2; result++; } return result; } // Returns the number of utf8 bytes. int ucs2_to_utf8_char(char *ucs2, char *utf8) { int result; unsigned int c = (ucs2[0] << 8) | (unsigned char)ucs2[1]; if (c <= 0x7F) { utf8[0] = (unsigned char)c; result = 1; } else if (c <= 0x7FF) { utf8[1] = (unsigned char)(0x80 | (c & 0x3F)); c = (c >> 6); utf8[0] = (unsigned char)(0xC0 | c); result = 2; } else if (c <= 0xFFFF) { utf8[2] = (unsigned char)(0x80 | (c & 0x3F)); c = (c >> 6); utf8[1] = (unsigned char)(0x80 | (c & 0x3F)); c = (c >> 6); utf8[0] = (unsigned char)(0xE0 | c); result = 3; } else result = 0; utf8[result] = '\0'; return result; } // Returns number of utf8 characters, not bytes. int ucs2_to_utf8_buffer(char *ucs2, size_t ucs2_buffer_len, char *utf8, size_t utf8_size) { int result = 0; char *p = ucs2; char *end = ucs2 + ucs2_buffer_len; char utf8char[7]; size_t len = 0; int i; while (p < end) { if (!(i = ucs2_to_utf8_char(p, utf8char))) break; if (len + i >= utf8_size) break; strcpy(&utf8[len], utf8char); len += i; p += 2; result++; } return result; } // Returns number of bytes. size_t ucs2utf(char *buf, size_t len, size_t maxlen) { char *ucs2 = (char *)malloc(len); if (!ucs2) return 0; memcpy(ucs2, buf, len); ucs2_to_utf8_buffer(ucs2, len, buf, maxlen +1); free(ucs2); return strlen(buf); } // Returns number of bytes. size_t iso_utf8_2ucs(char *buf, size_t maxlen) { size_t ucs2_size = iso_utf8chars(buf) * 2; // Not NULL terminated. char *ucs2; size_t bytes; if (ucs2_size > maxlen + 1) ucs2_size = maxlen + 1; if (!(ucs2 = (char *)malloc(ucs2_size))) return 0; bytes = 2 * iso_utf8_to_ucs2_buffer(buf, ucs2, ucs2_size); memcpy(buf, ucs2, bytes); free(ucs2); return bytes; } // Returns number of utf8 bytes. -1 in case of error. int utf8_to_iso_char(char *utf8, unsigned char *iso) { unsigned char *s = (unsigned char *)utf8; *iso = '\0'; if (*s < 128) { *iso = *s; return 1; } if (s[0] == 0xCE) { // ΔΦΓΛΩΠΨΣΘΞ --> 0x81 ... 0x8A switch (s[1]) { case 0x94: *iso = 0x81; return 2; case 0xA6: *iso = 0x82; return 2; case 0x93: *iso = 0x83; return 2; case 0x9B: *iso = 0x84; return 2; case 0xA9: *iso = 0x85; return 2; case 0xA0: *iso = 0x86; return 2; case 0xA8: *iso = 0x87; return 2; case 0xA3: *iso = 0x88; return 2; case 0x98: *iso = 0x89; return 2; case 0x9E: *iso = 0x8A; return 2; } } if (s[0] == 226 && s[1] == 130 && s[2] == 172) { *iso = 164; return 3; } if (s[0] == 194 && s[1] >= 128 && s[1] <= 191) { *iso = s[1]; return 2; } if (s[0] == 195 && s[1] >= 128 && s[1] <= 191) { *iso = s[1] + 64; return 2; } if (s[0] == 197) { switch (s[1]) { case 160: *iso = 166; return 2; case 161: *iso = 168; return 2; case 189: *iso = 180; return 2; case 190: *iso = 184; return 2; case 146: *iso = 188; return 2; case 147: *iso = 189; return 2; case 184: *iso = 190; return 2; } } if (s[0] >= 192 && s[0] < 224 && s[1] >= 128 && s[1] < 192) return 2; if (s[0] >= 224 && s[0] < 240 && s[1] >= 128 && s[1] < 192 && s[2] >= 128 && s[2] < 192) return 3; if (s[0] >= 240 && s[0] < 248 && s[1] >= 128 && s[1] < 192 && s[2] >= 128 && s[2] < 192 && s[3] >= 128 && s[3] < 192) return 4; if (s[0] >= 248 && s[0] < 252 && s[1] >= 128 && s[1] < 192 && s[2] >= 128 && s[2] < 192 && s[3] >= 128 && s[3] < 192 && s[4] >= 128 && s[4] < 192) return 5; if (s[0] >= 252 && s[0] < 254 && s[1] >= 128 && s[1] < 192 && s[2] >= 128 && s[2] < 192 && s[3] >= 128 && s[3] < 192 && s[4] >= 128 && s[4] < 192 && s[5] >= 128 && s[5] < 192) return 6; return -1; } smstools3/src/Makefile0000755000175000017500000000366213102443051013676 0ustar kekekeke# 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 # Uncomment this to disable usage of inotify #CFLAGS += -D DISABLE_INOTIFY # Uncomment for FreeBSD if statistics are enabled #CFLAGS += -I/usr/local/include # Uncomment for FreeBSD if inotify is used #LFLAGS += -L/usr/local/lib -linotify # 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 this to disable usage of national language shift tables #CFLAGS += -D DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES # Uncomment this to support european national languages only #CFLAGS += -D NATIONAL_LANGUAGES_EUROPEAN_ONLY # Uncomment this to print national language shift tables (smsd does not do anything else) #CFLAGS += -D PRINT_NATIONAL_LANGUAGE_SHIFT_TABLES # 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 charshift.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 *~ smstools3/src/charset.h0000755000175000017500000000335013067015012014034 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 // 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 decode_ucs2(char *buffer, int len); int iso_utf8_2gsm(char* source, int size, char* destination, int max, int *missing, char **notice); int iso2utf8_file(FILE *fp, char *ascii, int userdatalength); int decode_7bit_packed(char *text, char *dest, size_t size_dest); int encode_7bit_packed(char *text, char *dest, size_t size_dest); int utf8bytes(char *s); int iso_utf8bytes(char *s); int iso_utf8chars(char *s); int iso_utf8_to_ucs2_char(char *utf8, int *len, char *ucs2); int iso_utf8_to_ucs2_buffer(char *utf8, char *ucs2, size_t ucs2_size); int ucs2_to_utf8_char(char *ucs2, char *utf8); int ucs2_to_utf8_buffer(char *ucs2, size_t ucs2_buffer_len, char *utf8, size_t utf8_size); size_t ucs2utf(char *buf, size_t len, size_t maxlen); size_t iso_utf8_2ucs(char *buf, size_t maxlen); int utf8_to_iso_char(char *utf8, unsigned char *iso); #endif smstools3/src/modeminit.h0000755000175000017500000000547413055300403014377 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 #include char *get_gsm_cme_error(int code); char *get_gsm_cms_error(int code); char *get_gsm_error(char *answer); int get_read_timeout(char *keyword); int set_read_timeout(char *error, int size_error, char *keyword, int value); void log_read_timeouts(int level); 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, char *timeout_count, char *expect); int put_command0(char *command, char *answer, int max, char *timeout_count, char *expect, int silent); int read_from_modem0(char *answer, int max, int timeout, regex_t *re, char *expect); int test_openmodem(); int talk_with_modem(); int wait_network_registration(int waitnetwork_errorsleeptime, int retry_count); int try_closemodem(int force); int try_openmodem(); #endif smstools3/src/blacklist.c0000755000175000017500000000314013046432777014365 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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; } smstools3/src/smsd.c0000755000175000017500000075612613102177030013363 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 #ifndef DISABLE_INOTIFY #include #endif #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" #include "charshift.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 mainprocess_child_pid = 0; // 3.1.17. int mainprocess_notifier_pid = 0; // 3.1.17. volatile sig_atomic_t break_suspend = 0; // To break suspend when SIGUSR2 is received. volatile sig_atomic_t got_sigchld = 0; // 3.1.16beta. To reap stopped modem processes. const char *HDR_Alphabet = "Alphabet:"; // Msg file input. Incoming message. char HDR_Alphabet2[SIZE_HEADER] = {}; const char *HDR_Autosplit = "Autosplit:"; // Msg file input. char HDR_Autosplit2[SIZE_HEADER] = {}; const char *HDR_Binary = "Binary:"; // Msg file input (sets alphabet to 1 or 0). char HDR_Binary2[SIZE_HEADER] = {}; const char *HDR_missed_text = "CALL MISSED"; // For incoming call, message body. char HDR_missed_text2[SIZE_HEADER] = {}; const char *HDR_CallType = "Call_type:"; // Incoming message from phonebook. char HDR_CallType2[SIZE_HEADER] = {}; const char *HDR_Class = "Class:"; // 3.1.16beta2. char HDR_Class2[SIZE_HEADER] = {}; const char *HDR_TpDcs = "DCS_hex:"; // 3.1.16beta2. char HDR_TpDcs2[SIZE_HEADER] = {}; const char *HDR_Description = "Description:"; // 3.1.16beta2. char HDR_Description2[SIZE_HEADER] = {}; 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_Flash = "Flash:"; // Msg file input. char HDR_Flash2[SIZE_HEADER] = {}; const char *HDR_From = "From:"; // Msg file input: informative, incoming message: senders address. char HDR_From2[SIZE_HEADER] = {}; const char *HDR_FromSMSC = "From_SMSC:"; // Incoming message: senders SMSC char HDR_FromSMSC2[SIZE_HEADER] = {}; const char *HDR_FromTOA = "From_TOA:"; // Incoming message: senders Type Of Address. char HDR_FromTOA2[SIZE_HEADER] = {}; const char *HDR_Hex = "Hex:"; // Msg file input. char HDR_Hex2[SIZE_HEADER] = {}; const char *HDR_IMEI = "IMEI:"; // 3.1.16beta: Incoming / Sent(or failed), IMEI number. char HDR_IMEI2[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_Include = "Include:"; // Msg file input. char HDR_Include2[SIZE_HEADER] = {}; const char *HDR_Incomplete = "Incomplete:"; // For purged message files. char HDR_Incomplete2[SIZE_HEADER] = {}; const char *HDR_Language = "Language:"; // 3.1.16beta2. char HDR_Language2[SIZE_HEADER] = {}; const char *HDR_Language_ext = "Language_ext:"; // 3.1.16beta2. char HDR_Language_ext2[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_Macro = "Macro:"; // Msg file input. char HDR_Macro2[SIZE_HEADER] = {}; 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_MessageReference = "Message_reference:"; // 3.1.16beta2: Msg file input. char HDR_MessageReference2[SIZE_HEADER] = {}; const char *HDR_missed = "missed"; // For incoming call type. char HDR_missed2[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_Name = "Name:"; // Incoming message: name from the modem response (???). char HDR_Name2[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_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_Ping = "Ping:"; // 3.1.16beta2. char HDR_Ping2[SIZE_HEADER] = {}; const char *HDR_Priority = "Priority:"; // Msg file input. char HDR_Priority2[SIZE_HEADER] = {}; const char *HDR_Provider = "Provider:"; // Msg file input. char HDR_Provider2[SIZE_HEADER] = {}; const char *HDR_Received = "Received:"; // Incoming message timestamp. char HDR_Received2[SIZE_HEADER] = {}; const char *HDR_RejectDuplicates = "Reject_duplicates:"; // 3.1.16beta2: Msg file input. char HDR_RejectDuplicates2[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_ReplyPath = "Reply_path:"; // 3.1.16beta2. char HDR_ReplyPath2[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_Result = "Result:"; // For voice call, result string from a modem char HDR_Result2[SIZE_HEADER] = {}; const char *HDR_Retries = "Retries:"; // 3.1.16beta: Msg file input. char HDR_Retries2[SIZE_HEADER] = {}; const char *HDR_SendingTime = "Sending_time:"; // 3.1.20. char HDR_SendingTime2[SIZE_HEADER] = {}; const char *HDR_Sent = "Sent:"; // Outgoing timestamp, incoming: senders date & time (from PDU). char HDR_Sent2[SIZE_HEADER] = {}; const char *HDR_SMSC = "SMSC:"; // Msg file input: smsc number. char HDR_SMSC2[SIZE_HEADER] = {}; const char *HDR_Subject = "Subject:"; // Incoming message, modemname. char HDR_Subject2[SIZE_HEADER] = {}; const char *HDR_SystemMessage = "System_message:"; // Msg file input. char HDR_SystemMessage2[SIZE_HEADER] = {}; const char *HDR_TextIsPdu = "Text_is_pdu:"; // 3.1.16beta2. char HDR_TextIsPdu2[SIZE_HEADER] = {}; 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_UDHType = "UDH-Type:"; // Incoming message, type(s) of content of UDH if present. char HDR_UDHType2[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_Queue = "Queue:"; // Msg file input. char HDR_Queue2[SIZE_HEADER] = {}; // Headers without translation: const char *HDR_SmsdDebug = "Smsd_debug:"; // For debugging purposes 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". 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"; char *EXEC_START = "start"; // Prototype needed: void send_admin_message(int *quick, int *errorcounter, char *text); void log_adjust_device_starting(int is_refresh); 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_IS_MODEM) { // 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 == PROCESS_ID_MAINPROCESS && 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; } void run_eventhandler(char *filename, char *copyfilename, char *event, char *arg3) { if (eventhandler[0] || DEVICE.eventhandler[0]) { char cmdline[PATH_MAX + PATH_MAX + 32]; char *handler = (DEVICE.eventhandler[0])? DEVICE.eventhandler : eventhandler; char *fn2eventhandler = filename; if (*copyfilename && eventhandler_use_copy) fn2eventhandler = copyfilename; snprintf(cmdline, sizeof(cmdline), "%s %s %s %s", handler, event, fn2eventhandler, (arg3)? arg3 : ""); exec_system(cmdline, EXEC_EVENTHANDLER); } } 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_Alphabet)) strcpy(HDR_Alphabet2, value); else if (!strcasecmp(name, HDR_Autosplit)) strcpy(HDR_Autosplit2, value); else if (!strcasecmp(name, HDR_Binary)) strcpy(HDR_Binary2, value); else if (!strcasecmp(name, HDR_missed_text)) strcpy(HDR_missed_text2, value); else if (!strcasecmp(name, HDR_CallType)) strcpy(HDR_CallType2, value); else if (!strcasecmp(name, HDR_Class)) strcpy(HDR_Class2, value); else if (!strcasecmp(name, HDR_TpDcs)) strcpy(HDR_TpDcs2, value); else if (!strcasecmp(name, HDR_Description)) strcpy(HDR_Description2, 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_Flash)) strcpy(HDR_Flash2, value); else if (!strcasecmp(name, HDR_From)) strcpy(HDR_From2, value); else if (!strcasecmp(name, HDR_FromSMSC)) strcpy(HDR_FromSMSC2, value); else if (!strcasecmp(name, HDR_FromTOA)) strcpy(HDR_FromTOA2, value); else if (!strcasecmp(name, HDR_Hex)) strcpy(HDR_Hex2, value); else if (!strcasecmp(name, HDR_IMEI)) strcpy(HDR_IMEI2, value); else if (!strcasecmp(name, HDR_Identity)) strcpy(HDR_Identity2, value); else if (!strcasecmp(name, HDR_Include)) strcpy(HDR_Include2, value); else if (!strcasecmp(name, HDR_Incomplete)) strcpy(HDR_Incomplete2, value); #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES else if (!strcasecmp(name, HDR_Language)) strcpy(HDR_Language2, value); else if (!strcasecmp(name, HDR_Language_ext)) strcpy(HDR_Language_ext2, value); #endif else if (!strcasecmp(name, HDR_Length)) strcpy(HDR_Length2, value); else if (!strcasecmp(name, HDR_Macro)) strcpy(HDR_Macro2, value); else if (!strcasecmp(name, HDR_MessageId)) strcpy(HDR_MessageId2, value); else if (!strcasecmp(name, HDR_MessageReference)) strcpy(HDR_MessageReference2, value); else if (!strcasecmp(name, HDR_missed)) strcpy(HDR_missed2, value); else if (!strcasecmp(name, HDR_Modem)) strcpy(HDR_Modem2, value); else if (!strcasecmp(name, HDR_Name)) strcpy(HDR_Name2, value); else if (!strcasecmp(name, HDR_Number)) strcpy(HDR_Number2, value); else if (!strcasecmp(name, HDR_OriginalFilename)) strcpy(HDR_OriginalFilename2, value); else if (!strcasecmp(name, HDR_Ping)) strcpy(HDR_Ping2, value); else if (!strcasecmp(name, HDR_Priority)) strcpy(HDR_Priority2, value); else if (!strcasecmp(name, HDR_Provider)) strcpy(HDR_Provider2, value); else if (!strcasecmp(name, HDR_Received)) strcpy(HDR_Received2, value); else if (!strcasecmp(name, HDR_RejectDuplicates)) strcpy(HDR_RejectDuplicates2, value); else if (!strcasecmp(name, HDR_Replace)) strcpy(HDR_Replace2, value); else if (!strcasecmp(name, HDR_ReplyPath)) strcpy(HDR_ReplyPath2, value); else if (!strcasecmp(name, HDR_Report)) strcpy(HDR_Report2, value); else if (!strcasecmp(name, HDR_Result)) strcpy(HDR_Result2, value); else if (!strcasecmp(name, HDR_Retries)) strcpy(HDR_Retries2, value); else if (!strcasecmp(name, HDR_SendingTime)) strcpy(HDR_SendingTime2, value); else if (!strcasecmp(name, HDR_Sent)) strcpy(HDR_Sent2, value); else if (!strcasecmp(name, HDR_SMSC)) strcpy(HDR_SMSC2, value); else if (!strcasecmp(name, HDR_Subject)) strcpy(HDR_Subject2, value); else if (!strcasecmp(name, HDR_SystemMessage)) strcpy(HDR_SystemMessage2, value); else if (!strcasecmp(name, HDR_TextIsPdu)) strcpy(HDR_TextIsPdu2, 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_UDHType)) strcpy(HDR_UDHType2, 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_Queue)) strcpy(HDR_Queue2, 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 // "-Relatrio:" = input translated, header is not printed // "Relatrio:" = 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\n%s\n%s\nPDU:\nNOTICE:", HDR_FailReason, HDR_Failed, HDR_Identity, HDR_IMEI, HDR_Modem, HDR_Number, HDR_Sent, HDR_SendingTime, 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_IMEI2))) { 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_SendingTime2))) { 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 alphabet) { /* $ ܧ 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, 0xA4, /* 3.1.16beta: ISO character caused warning: '', */ 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 (filename_preview <= 0) return 0; if (alphabet == ALPHABET_UCS2) text = strdup("ucs2"); else if (alphabet == ALPHABET_BINARY) text = strdup("binary"); else if (arg_text) text = strdup(arg_text); else return 0; strcpy(old_filename, filename); i = 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[8192]; 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); // 3.1.16beta: Use tmpdir: //sprintf(tmp_filename,"/tmp/smsd.XXXXXX"); sprintf(tmp_filename, "%s/smsd.XXXXXX", tmpdir); 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 */ int *retries, /* 3.1.16beta: -1 = default, >= 0 = accepted value */ // 3.1.16beta2: int *message_reference, int *reject_duplicates, int *reply_path, int *text_is_pdu, int *sms_class, int *tp_dcs, int *ping, int *language, int *language_ext) { FILE* File; char line[SIZE_UDH_DATA +256]; char *ptr; int hlen; if (recursion_level == 0) { to[0] = 0; from[0] = 0; *alphabet = default_alphabet; *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; *retries = -1; *message_reference = 0; *reject_duplicates = 0; *reply_path = -1; *text_is_pdu = 0; *sms_class = -1; *tp_dcs = -1; *ping = 0; *language = -2; *language_ext = -2; // 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"))) { // 3.1.16beta2: Check if a file begins with Byte Order Mark (EF BB BF), and remove it from the original file on disk. // Included files are not handled later, with them BOM is just skipped. char *bom = "\xEF\xBB\xBF"; struct stat statbuf; char *buffer; size_t size_buffer; size_t n; if (fgets(line, strlen(bom) + 1, File)) { if (!strcmp(line, bom)) { if (recursion_level == 0 && !stat(filename, &statbuf)) { size_buffer = statbuf.st_size - 3 + 1; if ((buffer = (char *) malloc(size_buffer))) { if ((n = fread(buffer, 1, size_buffer, File)) > 0) { fclose(File); if ((File = fopen(filename, "w"))) { fwrite(buffer, 1, n, File); fclose(File); if (!(File = fopen(filename, "r"))) { File = 0; writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot reopen (after removing BOM) sms file %s.", filename)); alarm_handler0(LOG_ERR, tb); } } else { File = 0; writelogfile0(LOG_ERR, 1, tb_sprintf("Cannot remove BOM from sms file %s.", filename)); alarm_handler0(LOG_ERR, tb); } } free(buffer); } } } else fseek(File, 0, SEEK_SET); } if (!File) return; // 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")) *alphabet = ALPHABET_GSM; else if (!strncasecmp(line, "iso", 3) || !strncasecmp(line, "lat", 3) || !strncasecmp(line, "ans", 3)) *alphabet = ALPHABET_ISO; else if (!strncasecmp(line, "bin", 3)) *alphabet = ALPHABET_BINARY; else if (!strncasecmp(line, "chi", 3) || !strncasecmp(line, "ucs", 3) || !strncasecmp(line, "uni", 3)) *alphabet = ALPHABET_UCS2; else if (!strncasecmp(line, "utf", 3)) *alphabet = ALPHABET_UTF8; else *alphabet = ALPHABET_UNKNOWN; } 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, retries, message_reference, reject_duplicates, reply_path, text_is_pdu, sms_class, tp_dcs, ping, language, language_ext); } 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_SystemMessage, HDR_SystemMessage2)) { // 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_SmsdDebug, 0)) { if (strlen(cutspaces(strcpyo(line, line +hlen))) < SIZE_SMSD_DEBUG) strcpy(smsd_debug, line); // smsd_debug is global. } else if (test_header(&hlen, line, HDR_Retries, HDR_Retries2)) *retries = atoi(cutspaces(strcpyo(line, line +hlen))); else if (test_header(&hlen, line, HDR_MessageReference, HDR_MessageReference2)) *message_reference = atoi(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_RejectDuplicates, HDR_RejectDuplicates2)) *reject_duplicates = yesno(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_ReplyPath, HDR_ReplyPath2)) *reply_path = yesno(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_TextIsPdu, HDR_TextIsPdu2)) *text_is_pdu = yesno(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_Class, HDR_Class2)) *sms_class = atoi(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_TpDcs, HDR_TpDcs2)) { *tp_dcs = strtol(cutspaces(strcpyo(line, line + hlen)), NULL, 16); if (errno == EINVAL) *tp_dcs = -1; } else if (test_header(&hlen, line, HDR_Ping, HDR_Ping2)) *ping = yesno(cutspaces(strcpyo(line, line + hlen))); else #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (test_header(&hlen, line, HDR_Language, HDR_Language2)) *language = parse_language_setting(cutspaces(strcpyo(line, line + hlen))); else if (test_header(&hlen, line, HDR_Language_ext, HDR_Language_ext2)) *language_ext = parse_language_setting(cutspaces(strcpyo(line, line + hlen))); else #endif { // 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, char **notice) { 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, notice); } 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.16beta2: If the last character is EOF, remove it (cannot convert to GSM): while (n > 0 && line[n - 1] == 0x1A) line[--n] = 0; // 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, 0, notice); } else { memmove(text + *textlen, line, n); *textlen += n; text[*textlen] = 0; } } } #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)); } // 3.1.16beta: Function to reap stopped modem processes: void listen_modem_processes() { if (got_sigchld) { int i; int status; char reason[128]; got_sigchld = 0; for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (device_pids[i]) { if (waitpid(device_pids[i], &status, WNOHANG) == device_pids[i]) { *reason = 0; if (WIFEXITED(status)) snprintf(reason, sizeof(reason), " Exited, status %d.", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) snprintf(reason, sizeof(reason), " Killed by signal %d.", WTERMSIG(status)); writelogfile0(LOG_CRIT, 0, tb_sprintf("Modem handler %i (%s) terminated while mainprocess is still running.%s", i, devices[i].name, reason)); alarm_handler0(LOG_CRIT, tb); // Note: After alarmhandler is called, mainprocess will receive sigchld, // and mainspooler will go here. This does not cause problems, because // only modem processes are listened. device_pids[i] = 0; } } } if (mainprocess_child_pid > 0) { if (waitpid(mainprocess_child_pid, &status, WNOHANG) == mainprocess_child_pid) { *reason = 0; if (WIFEXITED(status)) snprintf(reason, sizeof(reason), " Exited, status %d.", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) snprintf(reason, sizeof(reason), " Killed by signal %d.", WTERMSIG(status)); writelogfile0(LOG_CRIT, 0, tb_sprintf("Child (%s) terminated while mainprocess is still running.%s", mainprocess_child, reason)); alarm_handler0(LOG_CRIT, tb); mainprocess_child_pid = 0; } } if (mainprocess_notifier_pid > 0) { if (waitpid(mainprocess_notifier_pid, &status, WNOHANG) == mainprocess_notifier_pid) { *reason = 0; if (WIFEXITED(status)) snprintf(reason, sizeof(reason), " Exited, status %d.", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) snprintf(reason, sizeof(reason), " Killed by signal %d.", WTERMSIG(status)); writelogfile0(LOG_CRIT, 0, tb_sprintf("Notifier terminated while mainprocess is still running.%s", reason)); alarm_handler0(LOG_CRIT, tb); mainprocess_notifier_pid = 0; } } } } /* ======================================================================= Mainspooler (sorts SMS into queues) ======================================================================= */ int spool1sms() { int result = 0; char filename[PATH_MAX], newfilename[PATH_MAX], copyfilename[PATH_MAX], to[SIZE_TO], from[SIZE_FROM], smsc[SIZE_SMSC], queuename[SIZE_QUEUENAME], directory[PATH_MAX], udh_data[SIZE_UDH_DATA], text[MAXTEXT]; int textlen, with_udh, queue, alphabet, flash, report, split, validity, voicecall, hex, replace_msg, system_msg, to_type, retries, message_reference, reject_duplicates, reply_path, text_is_pdu, sms_class, tp_dcs, ping, language, language_ext; char *fail_text = 0; int success = 0; char cmdline[PATH_MAX+PATH_MAX+32]; int i; 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) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("SMS file %s rejected by checkhandler", filename)); alarm_handler0(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; // 3.1.20: When checkhandler spooled a message, checkhandler should also remove the file. If this is missed, remove the file here: unlink(filename); stop_if_file_exists("Cannot delete", filename, "", ""); } 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, &retries, &message_reference, &reject_duplicates, &reply_path, &text_is_pdu, &sms_class, &tp_dcs, &ping, &language, &language_ext); readSMStext(filename, 0, 0, text, &textlen, NULL, NULL); // Is the destination set? 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"; } // is To: in the blacklist? else if (inblacklist(to)) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Destination %s in file %s is blacklisted", to, filename)); alarm_handler0(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)) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Destination %s in file %s is not whitelisted", to, filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Destination is not whitelisted"; } // Is the alphabet setting valid? else if (alphabet > ALPHABET_UTF8) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid alphabet in file %s", filename)); alarm_handler0(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)) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Wrong provider queue %s in file %s", queuename, filename)); alarm_handler0(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)) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Destination number %s in file %s does not match any provider", to, filename)); alarm_handler0(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 { char title[1024]; char *to_unknown = "somewhere (has PDU)"; // because the key is not checked as it's device specific. // Everything is ok, move the file into the queue // 3.1beta7, 3.0.9: Return value handled: if (movefilewithdestlock(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. // 3.1.16beta2: Removed retrying with conflict, because retrying only rarely helps and the problem is that the same names were used. //if (movefilewithdestlock(filename, directory, keep_filename, store_original_filename, "", newfilename) == 0) // writelogfile0(LOG_CRIT, 0, tb_sprintf("Retry solved the spooler conflict: %s %s", filename, directory)); } // 3.1.16beta: Log "SMS To" and complete filename in case of error. //stop_if_file_exists("Cannot move", filename, "to", directory); //writelogfile(LOG_NOTICE, 0, "Moved file %s to %s", filename, (keep_filename)? directory : newfilename); snprintf(title, sizeof(title), "SMS To: %s. Cannot move", (text_is_pdu)? to_unknown : to); stop_if_file_exists(title, filename, "to", (keep_filename)? directory : newfilename); writelogfile(LOG_NOTICE, 0, "SMS To: %s. Moved file %s to %s", (text_is_pdu)? to_unknown : to, 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]; // 3.1.16beta: Zero sized files are not spooled. Changed st_size == 0 to < 8. if (stat(filename, &statbuf) == 0) if (statbuf.st_size < 8) 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++; // 3.1.17: Mainspooler FAILED message. If d_failed and d_failed_copy are both defined, first make // a copy and then move a file. Then run eventhandler for either one depending on eventhandler_use_copy. *newfilename = 0; *copyfilename = 0; if (d_failed[0] && !file_is_empty) { if (*d_failed_copy) copyfilewithdestlock(filename, d_failed_copy, keep_filename, store_original_filename, process_title, copyfilename); movefilewithdestlock(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); char *fn2eventhandler = (*newfilename)? newfilename : filename; if (*copyfilename && eventhandler_use_copy) fn2eventhandler = copyfilename; snprintf(cmdline, sizeof(cmdline), "%s %s %s", eventhandler, "FAILED", fn2eventhandler); 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); } } result = 1; } return result; } void log_check_pid() { int i; if ((i = check_pid(pidfile))) { writelogfile0(LOG_ERR, 1, tb_sprintf("FATAL ERROR: Looks like another smsd (%i) is running. I (%i) quit now.", i, (int)getpid())); alarm_handler0(LOG_ERR, tb); *pidfile = 0; abnormal_termination(1); } } int mspooler_run_rr(time_t *last_regular_run) { if (*regular_run && regular_run_interval > 0) { time_t now = time(0); if (now >= *last_regular_run + regular_run_interval) { int i; *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); } } return 1; } void mainspooler() { char cmdline[PATH_MAX+PATH_MAX+32]; char tmp[32]; int i; time_t now; time_t last_regular_run = 0; #ifndef NOSTATS time_t last_print_status = time(0); time_t last_status = time(0); #endif time_t last_spooling = 0; time_t last_check_pid = 0; *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(tmp, ",%i", (int)device_pids[i]); if (strlen(cmdline) + strlen(tmp) < sizeof(cmdline)) strcat(cmdline, tmp); } } if (mainprocess_child_pid > 0) { sprintf(tmp, ",%i", mainprocess_child_pid); if (strlen(cmdline) + strlen(tmp) < sizeof(cmdline)) strcat(cmdline, tmp); } if (mainprocess_notifier_pid > 0) { sprintf(tmp, ",%i", mainprocess_notifier_pid); if (strlen(cmdline) + strlen(tmp) < sizeof(cmdline)) strcat(cmdline, tmp); } writelogfile0(LOG_DEBUG, 0, cmdline); // 3.1.4: if (delaytime_mainprocess < 0)//== -1) delaytime_mainprocess = delaytime; flush_smart_logging(); while (terminate == 0) { // Check if another mainspooler is running. If is, this process will stop. if (check_pid_interval > 0 && (now = time(0)) >= last_check_pid + check_pid_interval) { last_check_pid = now; log_check_pid(); } // 3.1.16beta: Reap stopped modem processes: listen_modem_processes(); #ifndef NOSTATS // Print status every second or less frequently depending on the setting sleeptime_mainprocess: if ((now = time(0)) >= last_print_status +1) { last_print_status = now; print_status(); } // Write stats if required (this is using it's own timing): checkwritestats(); // Write status if required: if ((now = time(0)) >= last_status +status_interval) { last_status = now; write_status(); } #endif if (terminate == 1) break; mspooler_run_rr(&last_regular_run); if (terminate == 1) break; if (((now = time(0)) >= last_spooling + delaytime_mainprocess) || break_workless_delay) { // If nothing found, spooling continues after delaytime is spent: last_spooling = now; break_workless_delay = 0; if (spool1sms() == 1) { flush_smart_logging(); // Restart main loop without any delay. // Other tasks are executed if necessary, and next file is picked up as soon as possible. last_spooling = 0; continue; } } if (terminate == 1) break; flush_smart_logging(); spend_delay(sleeptime_mainprocess, &mspooler_run_rr, &last_regular_run, regular_run_interval); } } /* ======================================================================= Delete message on the SIM card ======================================================================= */ void deletesms(int sim) /* deletes the selected sms from the sim card */ { char command[100]; char answer[500]; int i; 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); // 3.1.16beta: Retry if did not get the OK answer. Log the error and run alarmhandler. i = 0; do { i++; put_command(command, answer, sizeof(answer), "cmgd", EXPECT_OK_ERROR); if (!strstr(answer, "OK")) if (i < 2) if (t_sleep(errorsleeptime)) break; } while (i < 2 && !strstr(answer, "OK")); if (!strstr(answer, "OK")) { if (!answer[0]) tb_sprintf("Modem did not answer when trying to delete message %i", sim); else tb_sprintf("Modem answered %s when trying to delete message %i", answer, sim); writelogfile0(LOG_ERR, 1, tb); alarm_handler0(LOG_ERR, tb); } } } 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), "cmgd", 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) { writelogfile(LOG_DEBUG, 0, "check_memory, 0 because modem_disabled"); *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; // +CMGD: (1,22,23,28),(0-4) \r OK // +CMGD: (),(0-4) \r OK // 3.1.16beta: retry if did not get the answer: i = 0; do { i++; put_command("AT+CMGD=?\r", answer, size_answer, "cmgd", "(\\+CMGD:.*OK)|(ERROR)"); if (!strstr(answer, "OK")) if (i < 2) if (t_sleep(errorsleeptime)) return 1; } while (i < 2 && !strstr(answer,"OK")); if (!strstr(answer, "OK")) { if (!answer[0]) tb_sprintf("Modem did not answer to AT+CMGD=?"); else tb_sprintf("Modem answered %s to AT+CMGD=?", answer); writelogfile0(LOG_ERR, 1, tb); alarm_handler0(LOG_ERR, tb); } if (terminate) return 1; if (strstr(answer, "()")) return 1; if ((p = strstr(answer, " ("))) { strcpyo(answer, p +2); if ((p = strstr(answer, "),"))) { *p = 0; // 3.1.16beta: Handle incorrect answer from older Telit modem: // GSM1: <- +CMGD: (1,2,3,4,5,),(0-4) OK // GSM1: Used memory is 6, list: 1,2,3,4,5, // With high traffic extra comma may be returned (even when the SIM is full). if (*answer && answer[strlen(answer) - 1] == ',') { answer[strlen(answer) - 1] = 0; // Log this issue because modem did not work properly and may have/cause other problems too. writelogfile0(LOG_NOTICE, 1, tb_sprintf("Removed extra comma from the answer for +CMGD=?")); alarm_handler0(LOG_NOTICE, tb); } // 3.1.16beta2: Some Huawei may answer using incorrect format: +CMGD: (1-4095),(0-4) OK if (strchr(answer, '-')) { writelogfile(LOG_ERR, 1, "Incorrect answer for AT+CMGD=?, feature not supported?"); *answer = 0; } if (*answer && 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, "cmgl", 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++; // 3.1.16beta: SMSC number is not mandatory, allow zero length but still check the octet: //if ((pdu1 = octet2bin_check(p)) <= 0) 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, "cpms", "(\\+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]; char copyfilename[PATH_MAX]; // 3.1.17. FILE *fp; char timestamp[81]; 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 (!strstr(DEVICE.imei, "ERROR") && *HDR_IMEI2 != '-') fprintf(fp, "%s %s\n", get_header_incoming(HDR_IMEI, HDR_IMEI2), DEVICE.imei); 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, ALPHABET_ISO); writelogfile(LOG_INFO, 0, "Wrote an incoming message file: %s", filename); // Phonecall // 3.1.17: If d_phonecalls was not used, copy message to incoming_copy if necessary: *copyfilename = 0; if (!(*d_phonecalls) && *d_incoming_copy) copyfilewithdestlock(filename, d_incoming_copy, 0/*keep_filename*/, 0/*store_original_filename*/, process_title, copyfilename); run_eventhandler(filename, copyfilename, "CALL", ""); } 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. */ /* 3.1.20: In case of ERROR return value is -3. */ /* 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), "cmgr", "(\\+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 -3; // 3.1.20. 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 = ALPHABET_ISO; 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] = ""; char udh_data2[SIZE_UDH_DATA] = {}; #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES int language = -2; int language_ext = -2; int language2 = -2; int language_ext2 = -2; #endif 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 // 3.1.16beta: Try to read name without getfield, because there can be comma inside the text: //getfield(line1,2,name, sizeof(name)); char *p1, *p2; if ((p1 = strchr(line1, ':')) && (p1 = strchr(p1, ',')) && *(++p1) == '"' && (p2 = strchr(++p1, '"'))) snprintf(name, sizeof(name), "%.*s", (int)(p2 -p1), p1); else 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); // 3.1.16beta2: Handle incoming message which is using language shift tables: #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (alphabet == ALPHABET_GSM && DEVICE.cs_convert == 1 && get_language_shift(udh_data, &language, &language_ext)) { userdatalength = gsm2utf8_shift(ascii, sizeof(ascii), userdatalength, language, language_ext); alphabet = ALPHABET_UTF8; } else #endif if (alphabet == ALPHABET_GSM && DEVICE.cs_convert==1) userdatalength=gsm2iso(ascii,userdatalength,ascii,sizeof(ascii)); else if (alphabet == ALPHABET_UCS2 && 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); } } if (!incoming_utf8) { userdatalength = decode_ucs2(ascii, userdatalength); alphabet = ALPHABET_ISO; } else { userdatalength = ucs2utf(ascii, userdatalength, sizeof(ascii)); alphabet = ALPHABET_UTF8; } } #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 == ALPHABET_GSM && DEVICE.cs_convert==1)||(alphabet == ALPHABET_ISO)||(alphabet == ALPHABET_UNKNOWN)) 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; #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES int using_gsm_alphabet = -1; int using_language_shift = 0; #endif // 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; #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES // Check if any part is using language shift: if (using_gsm_alphabet && !using_language_shift) { 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); using_gsm_alphabet = (alphabet == ALPHABET_GSM)? 1 : 0; if (using_gsm_alphabet && get_language_shift(udh_data, 0, 0)) using_language_shift = 1; } #endif } 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, udh_data2, 0, &is_statusreport, &is_unsupported_pdu, from_toa, &report, &replace, warning_headers, &flash, do_internal_combine_binary); #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES // Udh is taken from each part, because of shift tables. Udh from the first part is shown in the message file. if (i == 1) strcpy(udh_data2, udh_data); // If any part is using language shift, all parts are handled with shift function (as there are basic tables too). if (alphabet == ALPHABET_GSM && DEVICE.cs_convert == 1 && (get_language_shift(udh_data2, &language2, &language_ext2) || using_language_shift)) { udlen = gsm2utf8_shift(ascii +userdatalength, sizeof(ascii) -userdatalength, udlen, language2, language_ext2); alphabet = ALPHABET_UTF8; if (i == 1) { // Language of first part is shown in the message file, even when other parts have different language: language = language2; language_ext = language_ext2; } } else #endif if (alphabet == ALPHABET_GSM && DEVICE.cs_convert==1) udlen=gsm2iso(ascii +userdatalength,udlen,ascii +userdatalength,sizeof(ascii) -userdatalength); else if (alphabet == ALPHABET_UCS2 && do_decode_unicode_text == 1) { // 3.1.16beta2: Decode unicode text directly to UTF-8 when incoming_utf8 is set. if (!incoming_utf8) { udlen = decode_ucs2(ascii +userdatalength, udlen); alphabet = ALPHABET_ISO; } else { udlen = ucs2utf(ascii +userdatalength, udlen, sizeof(ascii) -userdatalength); alphabet = ALPHABET_UTF8; } } 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); // 3.1.16beta2: if (DEVICE.description[0]) if (*HDR_Description2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Description, HDR_Description2), DEVICE.description); if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_Identity, HDR_Identity2), DEVICE.identity); if (!strstr(DEVICE.imei, "ERROR") && *HDR_IMEI2 != '-') fprintf(fd, "%s %s\n", get_header_incoming(HDR_IMEI, HDR_IMEI2), DEVICE.imei); 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 == ALPHABET_GSM) { if (DEVICE.cs_convert) p = "ISO"; else p = "GSM"; } else if (alphabet == ALPHABET_ISO) p = "ISO"; else if (alphabet == ALPHABET_BINARY) p = "binary"; else if (alphabet == ALPHABET_UCS2) p = "UCS2"; else if (alphabet == ALPHABET_UTF8) p = "UTF-8"; else if (alphabet == ALPHABET_UNKNOWN) p = "unknown"; // 3.1.5: if (incoming_utf8 == 1 && alphabet <= ALPHABET_ISO) 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 == ALPHABET_UCS2)? 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 == ALPHABET_ISO) 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 == ALPHABET_ISO || alphabet == ALPHABET_UCS2)) || (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); } } } // -------------------------------------------- #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (language >= 0 || language_ext >= 0) { if (*HDR_Language2 != '-') fprintf(fd, "%s %s (%i)\n", get_header_incoming(HDR_Language, HDR_Language2), get_language_name(language), language); if (*HDR_Language_ext2 != '-') fprintf(fd, "%s %s (%i)\n", get_header_incoming(HDR_Language_ext, HDR_Language_ext2), get_language_name(language_ext), language_ext); } #endif fprintf(fd,"\n"); // UTF-8 conversion if necessary: if (incoming_utf8 == 1 && alphabet <= ALPHABET_ISO) { // 3.1beta7, 3.0.9: GSM alphabet is first converted to ISO if (alphabet == ALPHABET_GSM && DEVICE.cs_convert == 0) { userdatalength = gsm2iso(ascii, userdatalength, ascii, sizeof(ascii)); alphabet = ALPHABET_ISO; // 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; if (alphabet <= ALPHABET_ISO) { if (alphabet == ALPHABET_GSM && DEVICE.cs_convert == 0) { userdatalength = gsm2iso(ascii, userdatalength, ascii, sizeof(ascii)); alphabet = ALPHABET_ISO; } text = ascii; } apply_filename_preview(filename, text, alphabet); } 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]; 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 (DEVICE.modem_disabled) writelogfile(LOG_DEBUG, 0, "receivesms, quick: %i", *quick); 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), "cpms", 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; 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 == -1)? 1 : 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 { int i = (DEVICE.read_memory_start == -1)? 1 : DEVICE.read_memory_start; if (sim > i + 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; } // 3.1.20: If reading from last slot causes error, try if starting from 0 next time helps. if (found == -3 && DEVICE.read_memory_start != 0) { int i = (DEVICE.read_memory_start == -1 )? 1 : DEVICE.read_memory_start; if (sim == i + max_memory - 1) { writelogfile(LOG_ERR, 1, "Got an error when trying to read the last memory slot %i.", sim); writelogfile(LOG_ERR, 1, "Assuming next time that the numbering of slots starts from 0."); writelogfile(LOG_ERR, 1, "Consider using the configuration setting \"memory_start = 0\"."); DEVICE.read_memory_start = 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) { // 3.1.17: Make a copy of incoming message if necessary. char copyfilename[PATH_MAX] = {}; if (*d_incoming_copy || (statusreport && *d_report_copy)) { char *dest = d_incoming_copy; if (statusreport && *d_report_copy) dest = d_report_copy; copyfilewithdestlock(filename, dest, 0/*keep_filename*/, 0/*store_original_filename*/, process_title, copyfilename); } run_eventhandler(filename, copyfilename, (statusreport)? "REPORT" : "RECEIVED", 0); } 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, int retries, int message_reference, int reject_duplicates, int reply_path, int text_is_pdu, int sms_class, int tp_dcs, int ping, 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 retry_count; // 3.1.16beta: changed name from "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, retries=%i)\n", from, to, textlen, alphabet, with_udh, udh_data, quick, flash, smsc, report, validity, part, parts, replace_msg, system_msg, to_type, retries); #endif if (error_text) *error_text = 0; start_time = time(0); // Mark modem as sending STATISTICS->status = 's'; if (text_is_pdu) { read_pdu_text(pdu, sizeof(pdu), text); get_pdu_submit_to(to, SIZE_TO, pdu); } *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 (DEVICE.sending_disabled) writelogfile(LOG_DEBUG, 0, "send_part, quick: %i", quick); 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; } } } if (!text_is_pdu) make_pdu(to, text, textlen, alphabet, flash, report, with_udh, udh_data, DEVICE.mode, pdu, validity, replace_msg, system_msg, to_type, smsc, message_reference, reject_duplicates, reply_path, sms_class, tp_dcs, ping); 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")) // ) { char *p; writelogfile(LOG_NOTICE, 0, "Test run, NO actual sending:%s from %s to %s", partstr, from, to); // 3.1.16beta: remove \r if ((p = strdup(command))) { if (*p && p[strlen(p) - 1] == '\r') p[strlen(p) - 1] = 0; writelogfile(LOG_DEBUG, 0, "PDU to %s: %s %s", to, p, pdu); free(p); } // 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++; // 3.1.21: Fixed time_t argument to int. writelogfile(LOG_NOTICE, 0, "SMS sent, Message_id: %s, To: %s, sending time %i sec.", messageids, to, (int)(time(0) -start_time)); flush_smart_logging(); return 0; } else { retry_count = 0; //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), "cmgs", "(>)|(ERROR)"); // Send message if command was successful if (!strstr(answer,"ERROR")) { put_command(command2, answer ,sizeof(answer), "pdu", 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 { // 3.1.21: Fixed pos: format from %s to &i: writelogfile(LOG_ERR, 1, "Verify PDU: ERROR (pos: %i)", 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), "default", 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.16beta2: if (message_reference > 0) if (atoi(tmpid) != message_reference) writelogfile0(LOG_ERR, 0, tb_sprintf("Message reference was defined (%i), but the modem did not use it.", message_reference)); // 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 (retry_count > 0) snprintf(answer, sizeof(answer), " Retries: %i.", retry_count); // 3.1.21: Fixed time_t argument to int. writelogfile(LOG_NOTICE, 0, "SMS sent%s, Message_id: %s%s, To: %s, sending time %i sec.%s", partstr, tmpid, replacestr, to, (int)(time(0) -start_time), answer); // 3.1.1: update_message_counter(1, DEVICE.name); STATISTICS->usage_s+=time(0)-start_time; STATISTICS->succeeded_counter++; // 3.1.16beta2: if (DEVICE.sentsleeptime > 0) { writelogfile(LOG_DEBUG, 0, "Spending sleep time after sending (%i sec)", DEVICE.sentsleeptime); t_sleep(DEVICE.sentsleeptime); } flush_smart_logging(); return 0; } else { // 3.1.14: show retries and trying time when stopping: int result = 0; int retry; // 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); // 3.1.16beta: //if (++retries <= 2) retry_count++; if (retries >= 0) retry = retry_count <= retries; else retry = retry_count <= DEVICE.send_retries; if (retry) { 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 { // 3.1.16beta: new message and alarm: writelogfile0(LOG_ERR, 1, tb_sprintf("Sending canceled because modem initialization failed.")); alarm_handler0(LOG_ERR, tb); result = 1; } } else result = 2; // Cancel if too many retries if (result) { STATISTICS->usage_s += time(0) - start_time; STATISTICS->failed_counter++; // 3.1.21: Fixed time_t argument to int. writelogfile0(LOG_WARNING, 1, tb_sprintf("Sending SMS%s to %s failed, trying time %i sec. Retries: %i.", partstr, to, (int)(time(0) - start_time), retry_count - 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 copyfilename[PATH_MAX]; // 3.1.17. 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[1024]; //maxsms_pdu+1]; int part_text_length; char directory[PATH_MAX]; 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 retries; 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; // 3.1.16beta2: int message_reference; int reject_duplicates; int reply_path; int text_is_pdu; int sms_class; int tp_dcs; int ping; int language = -2; int language_ext = -2; int udh_shift_cost = 0; int use_get_part = 0; char *part_start; int do_convert; char *charconv_notice = 0; time_t start_time; // 3.1.20 #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, &retries, &message_reference, &reject_duplicates, &reply_path, &text_is_pdu, &sms_class, &tp_dcs, &ping, &language, &language_ext); // 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; } else if (text_is_pdu && strcmp(to, DEVICE.text_is_pdu_key)) // 3.1.16beta2. { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid key (To) for text_is_pdu in file %s", filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid key (To) for text_is_pdu"; success = -2; } else if (alphabet > ALPHABET_UTF8) { 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; } else if (message_reference > 255 || message_reference < 0) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid message reference in file %s", filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid message reference"; success = -2; } #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES else if (language == -1 || language_ext == -1) { writelogfile0(LOG_NOTICE, 0, tb_sprintf("Invalid language/language_ext in file %s", filename)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Invalid language/language_ext"; success = -2; } #endif *text = 0; textlen = 0; if (success == 0) { // 3.1.16beta2: Report can be disabled completely for other than ping messages. With ping messages report is set to yes and message is limited to single part. if (ping) { report = 1; split = 0; } else if (DEVICE.report == -2) report = 0; // Use config file setting if report is unset in file header if (report == -1) report = DEVICE.report; if (reply_path == -1) reply_path = DEVICE.reply_path; // Set a default for with_udh if it is not set in the message file. if (with_udh==-1) { if ((alphabet == ALPHABET_BINARY || 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; if (text_is_pdu) { alphabet = ALPHABET_BINARY; hex = 0; split = 0; had_udh = 0; with_udh = 0; *udh_data = 0; } if (*udh_data && split && alphabet != ALPHABET_BINARY) split = 0; #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 <= ALPHABET_ISO || alphabet >= ALPHABET_UCS2) { #ifdef DEBUGMSG printf("!! This is %stext message\n", (alphabet >= ALPHABET_UCS2)? "unicode " : ""); #endif // maxpartlen is set also later when charset is changed. maxpartlen = (alphabet == ALPHABET_UCS2)? maxsms_ucs2 : maxsms_pdu; // ucs2 = 140, pdu = 160 #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (alphabet != ALPHABET_UCS2) select_language_shift_tables(&language, &language_ext, DEVICE.language, DEVICE.language_ext); #endif // 3.1.16beta2: Allow hex presentation with text messages too: if (hex == 1 && alphabet != ALPHABET_UCS2) readSMShex(filename, 0, text, &textlen, macros, errortext); else { do_convert = DEVICE.cs_convert && alphabet == ALPHABET_ISO && language == -2 && language_ext == -2; readSMStext(filename, 0, do_convert, text, &textlen, macros, &charconv_notice); if (do_convert) alphabet = ALPHABET_GSM; } // 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); if (*errortext) fail_text = errortext; else fail_text = "No text"; parts=0; success = -2; } #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (success == 0 && language >= 0 && language_ext >= 0) { if (!utf2gsm_shift(text, sizeof(text), &textlen, &language, &language_ext, &charconv_notice)) { alphabet = ALPHABET_GSM; maxpartlen = maxsms_pdu; if (language > 0 || language_ext > 0) { udh_shift_cost = (language > 0 && language_ext > 0)? 8 : 5; with_udh = 1; // This UDH is for single part message, multipart UDH is created with UDH part numbering. sprintf(udh_data, "%02X", (udh_shift_cost == 8)? 6 : 3); if (language > 0) sprintf(strchr(udh_data, 0), " 25 01 %02X", language); if (language_ext > 0) sprintf(strchr(udh_data, 0), " 24 01 %02X", language_ext); } } else { writelogfile0(LOG_NOTICE, 0, tb_sprintf("The file %s had failure with language shift tables, language:%i, language_ext:%i.", filename, language, language_ext)); alarm_handler0(LOG_NOTICE, tb); fail_text = "Failure with language shift tables"; parts = 0; success = -2; } } #endif if (success == 0) { // 3.1.4: if (filename_preview > 0 && alphabet == ALPHABET_GSM && !system_msg && !udh_shift_cost) { 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); } } // ------ if (alphabet == ALPHABET_UTF8) { // ALPHABET_UTF8: Use GSM if all characters can be converted, othervise use UCS2. char *p = strdup(text); int missing = 0; textlen = iso_utf8_2gsm(p, textlen, text, sizeof(text), &missing, &charconv_notice); if (!missing) { alphabet = ALPHABET_GSM; maxpartlen = maxsms_pdu; } else { if (DEVICE.notice_ucs2 != 2) { free(charconv_notice); charconv_notice = 0; } tb_sprintf("NOTICE: %i characters outside the GSM alphabet, converting to UCS2", missing); writelogfile0(LOG_INFO, 0, tb + 8); if (DEVICE.notice_ucs2) strcat_realloc(&charconv_notice, tb, "\n"); strcpy(text, p); textlen = (int)iso_utf8_2ucs(text, sizeof(text)); alphabet = ALPHABET_UCS2; maxpartlen = maxsms_ucs2; } free(p); } // In how many parts do we need to split the text? if (split>0) { // 3.1.16beta2: With language shift text numbering is not supported: if (udh_shift_cost && split == 2) split = 1; // Use splitting without numbers, UDH numbering does not work with all operators. // if it fits into 1 SM, then we need 1 part if (textlen +udh_shift_cost <= maxpartlen) { parts = 1; reserved = udh_shift_cost; eachpartlen = maxpartlen -reserved; } else if (split==2) // number with text { reserved = 4; // 1/9_ if (alphabet == ALPHABET_UCS2) { reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; } else parts = calculate_required_parts(text, textlen, &reserved, split, &use_get_part); // 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 == ALPHABET_UCS2) { reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; } else parts = calculate_required_parts(text, textlen, &reserved, split, &use_get_part); // 3.1beta7: there can be more than 99 parts: if (parts > 99) { reserved = 8; // 111/255_ if (alphabet == ALPHABET_UCS2) { reserved *= 2; eachpartlen = maxpartlen -reserved; parts = (textlen +eachpartlen -1) /eachpartlen; } else parts = calculate_required_parts(text, textlen, &reserved, split, &use_get_part); // 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 { if (udh_shift_cost) { reserved = udh_shift_cost; eachpartlen = maxpartlen -reserved; parts = calculate_required_parts(text, textlen, &reserved, split, &use_get_part); } else { // reserve 7 chars for the UDH reserved=7; if (alphabet == ALPHABET_UCS2) // 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 reserved = udh_shift_cost; eachpartlen = maxpartlen -reserved; if (reserved) parts = calculate_required_parts(text, textlen, &reserved, split, &use_get_part); else parts = (textlen +eachpartlen -1) /eachpartlen; } } else { // split is 0, too long message is just cutted. reserved = udh_shift_cost; eachpartlen = maxpartlen -reserved; parts = 1; } if (parts>1) writelogfile(LOG_DEBUG, 0, "Splitting this message into %i parts of max %i characters%s.", parts, (alphabet == ALPHABET_UCS2)? eachpartlen /2 : eachpartlen, (alphabet == ALPHABET_UCS2)? " (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, 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 && !text_is_pdu) { 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; // 3.1.16beta2: if (parts > 1 && message_reference) { writelogfile0(LOG_ERR, 0, tb_sprintf("Message reference works properly with single part only, file %s", filename)); alarm_handler0(LOG_ERR, tb); } start_time = time(0); for (part=0; part1) // If more than 1 part and text numbering { sprintf(part_text, "%i/%i ", part +1, parts); if (alphabet == ALPHABET_UCS2) { for (i = reserved; i > 0; i--) { part_text[i *2 -1] = part_text[i -1]; part_text[i *2 -2] = 0; } } if (use_get_part) { part_text_length = get_part(&part_start, text, textlen, reserved, part); memcpy(part_text, part_start, part_text_length); } else { 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 { if (use_get_part) { part_text_length = get_part(&part_start, text, textlen, reserved, part); memcpy(part_text, part_start, part_text_length); } else { // 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; } if (udh_shift_cost) { char shift[18] = {}; if (language > 0) sprintf(shift, "25 01 %02X", language); if (language_ext > 0) sprintf(strchr(shift, 0), "%s24 01 %02X", (*shift)? " " : "", language_ext); sprintf(udh_data, "%02X 00 03 %02X %02X %02X %s", (unsigned int)(strlen(shift) +1) / 3 + 5, concatenated_id, parts, part +1, shift); } else sprintf(udh_data,"05 00 03 %02X %02X %02X",concatenated_id,parts,part+1); with_udh=1; } else // No part numbers { if (text_is_pdu) { snprintf(part_text, sizeof(part_text), "%s", text); part_text_length = strlen(part_text); } else if (use_get_part) { part_text_length = get_part(&part_start, text, textlen, reserved, part); memcpy(part_text, part_start, part_text_length); } else { 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. // 3.1.17: receive_before_send is now a modem setting too: //if ((receive_before_send) && (DEVICE.incoming)) if (DEVICE.incoming) { if (DEVICE.receive_before_send == 1 || (DEVICE.receive_before_send == -1 && receive_before_send == 1)) { 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); // 3.1.16beta: Fix: Did not make a voicecall to short number starting with 's'. if (*to == 's') sprintf(command, "ATD%s;\r", to + 1); else 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); // 3.1.16beta: If wait_delay is set to zero, use 150: if (wait_delay <= 0) wait_delay = 150; put_command(command, answer, sizeof(answer), "atd", 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) { // TODO: With a new Telit chip, last CLCC gets 0 (active) even when there is no call active. Polling does not help. strcpy(answer, "OK"); strcpy(voicecall_result, answer); 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; } } // 3.1.16beta: Use the delay which was set by user. //if (time(0) > wait_time + 150) if (time(0) > wait_time + wait_delay) { 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), (DEVICE.voicecall_cpas)? "cpas" : "clcc", expect); } } else { if (!wait_delay) { // 3.1.7: // put_command(command, answer, sizeof(answer), 24, expect); i = put_command(command, answer, sizeof(answer), "atd", 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); // 3.1.16beta: If no answer, it's Timeout: if (!(*answer)) strcpy(answer, "Timeout"); 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; char tone_timeout[32]; 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'; // ---------------------------------------------------------------------- // 3.1.15: snprintf(tone_timeout, sizeof(tone_timeout), "%i", (int)(tones * 0.39 + 1) * 5); for (i = 0; (i < count) && tones; i++) { t_sleep(3); //put_command(command, answer, sizeof(answer), tones *0.39 +1, expect); put_command(command, answer, sizeof(answer), tone_timeout, 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), "ath", 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 == ALPHABET_BINARY && 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, retries, message_reference, reject_duplicates, reply_path, text_is_pdu, sms_class, tp_dcs, ping, 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]; // 3.1.16beta: Zero sized files are not spooled. Changed st_size == 0 to < 8. if (stat(filename, &statbuf) == 0) if (statbuf.st_size < 8) 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); // 3.1.16beta2: if (DEVICE.description[0]) if (*HDR_Description2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Description, HDR_Description2), DEVICE.description); if (!strstr(DEVICE.identity, "ERROR") && *HDR_Identity2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Identity, HDR_Identity2), DEVICE.identity); if (!strstr(DEVICE.imei, "ERROR") && *HDR_IMEI2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_IMEI, HDR_IMEI2), DEVICE.imei); 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); if (charconv_notice) change_headers(filename, 0, 0, "%s", charconv_notice); // Move file into failed queue or delete // 3.1.17: Sending failed, create a copy of a message if necessary. *newfilename = 0; *copyfilename = 0; if (d_failed[0] && !file_is_empty) { if (*d_failed_copy) copyfilewithdestlock(filename, d_failed_copy, keep_filename, 0, process_title, copyfilename); movefilewithdestlock(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. apply_filename_preview(newfilename, filename_preview_buffer, alphabet); // 3.1.16beta: //writelogfile(LOG_INFO, 0, "Moved file %s to %s", filename, newfilename); writelogfile(LOG_INFO, 0, "SMS To: %s. Moved file %s to %s", to, filename, newfilename); } run_eventhandler((*newfilename)? newfilename : filename, copyfilename, "FAILED", messageids); 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; time_t sending_time; // 3.1.20 sending_time = time(0) - start_time; 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); // 3.1.16beta2: if (DEVICE.description[0]) if (*HDR_Description2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_Description, HDR_Description2), DEVICE.description); 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 (*HDR_SendingTime2 != '-') sprintf(strchr(add_headers, 0), "%s %i\n", get_header(HDR_SendingTime, HDR_SendingTime2), (int)sending_time); 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 (!strstr(DEVICE.imei, "ERROR") && *HDR_IMEI2 != '-') sprintf(strchr(add_headers, 0), "%s %s\n", get_header(HDR_IMEI, HDR_IMEI2), DEVICE.imei); 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 == ALPHABET_BINARY || alphabet == ALPHABET_UCS2))) p = outgoing_pdu_store; else p = NULL; change_headers(filename, remove_headers, p, "%s", add_headers); if (charconv_notice) change_headers(filename, 0, 0, "%s", charconv_notice); // Move file into sent queue or delete after eventhandler is executed. // 3.1.17: Sent successfully, create a copy of a message if necessary. *newfilename = 0; *copyfilename = 0; if (d_sent[0]) { if (*d_sent_copy) copyfilewithdestlock(filename, d_sent_copy, keep_filename, 0, process_title, copyfilename); movefilewithdestlock(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. apply_filename_preview(newfilename, filename_preview_buffer, alphabet); // 3.1.16beta: //writelogfile(LOG_INFO, 0, "Moved file %s to %s", filename, newfilename); writelogfile(LOG_INFO, 0, "SMS To: %s. Moved file %s to %s", to, filename, newfilename); } run_eventhandler((*newfilename)? newfilename : filename, copyfilename, "SENT", messageids); 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 free(outgoing_pdu_store); outgoing_pdu_store = NULL; // 3.1.4: free(filename_preview_buffer); free(charconv_notice); 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; int modem_was_open; // 3.1.16beta. Fix. If modem was not open, we must open and close it. int save_store_sent_pdu = store_sent_pdu; // 3.1.16beta: Do not store PDU when sending admin msg. int retries = -1; // 3.1.16beta. (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; } } modem_was_open = modem_handle >= 0; if (!modem_was_open) if (!try_openmodem()) 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), 0, 0); store_sent_pdu = 0; // 3.1.16beta: If send_retries is set to zero for this device, use value 2 for admin message. if (DEVICE.send_retries < 1) retries = 2; send_part("Smsd3" /*from*/, to, buffer, textlen, ALPHABET_ISO, 0 /*with_udh*/, "" /*udh_data*/, *quick, 0 /*flash*/, messageids, "" /*smsc*/, DEVICE.report, -1 /*validity*/, 0, 1, 0, 0, -1 /*to_type*/, retries, 0 /*message_reference*/, 0 /*reject_duplicates*/, -1 /*reply_path*/, 0 /*text_is_pdu*/, -1 /*sms_class*/, -1 /*tp_dcs*/, 0 /*ping*/, 0 /*error_text*/); store_sent_pdu = save_store_sent_pdu; if (!modem_was_open) try_closemodem(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[1024]; // 3.1.16beta: 500 -> 1024 char buffer[1024]; // 3.1.16beta: 600 -> 1024 int fd; int log_retry = 3; int i; FILE *fp; int is_ussd = 0; // 3.1.7 if (!command || !(*command)) return 1; if (DEVICE.modem_disabled) { writelogfile(LOG_DEBUG, 0, "cmd_to_modem: %s", command); return 1; } if (!try_openmodem()) return 0; if (cmd_number == 1 && DEVICE.needs_wakeup_at) { put_command("AT\r", 0, 0, "default", 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++; // 3.1.21: In some cases modem gives the answer in two or more parts. // First part is not enough, must wait to end or near to end if dsc is not known: //put_command(cmd, answer, sizeof(answer), "cusd", "(\\+CUSD:)|(ERROR)"); put_command(cmd, answer, sizeof(answer), "cusd", "(\\+CUSD:.*\",)|(ERROR)"); } else // 3.1.12: if (*cmd == '[' && strchr(cmd, ']')) { char *expect; if ((expect = strdup(cmd + 1))) { // 3.1.21: Use cusd timeout if this seems to be USSD command: char *timeout = "cmd"; char *command = strchr(cmd, ']') + 1; if (strstr(command, "+CUSD")) timeout = "cusd"; *(strchr(expect, ']')) = 0; //put_command(strchr(cmd, ']') + 1, answer, sizeof(answer), "cmd", expect); put_command(command, answer, sizeof(answer), timeout, expect); free(expect); // 3.1.21: Set is_ussd if this answer seems to be ussd answer: // Sample regular_run_cmd in this case (without the dcs in the end): // [(\+CUSD:.*",)|(ERROR)]AT+CUSD=1,"AA180C3602" if (strstr(answer, "+CUSD:")) is_ussd++; } } else // ------- put_command(cmd, answer, sizeof(answer), "cmd", 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 > 0) { char *p1, *p2; int c, idx; if ((p1 = strstr(answer, "+CUSD:"))) { if ((p1 = strchr(p1, '"'))) { p1++; if ((p2 = strchr(p1, '"'))) { snprintf(buffer, sizeof(buffer), "%.*s", (int) (p2 - p1), p1); if ((p = strdup(buffer))) { switch (DEVICE.ussd_convert) { case 1: // Unicode format is converted to UTF-8 case 4: // Hexadecimal dump is converted to ASCII memset(buffer, 0, sizeof(buffer)); for (idx = 0; p[idx] && p[idx + 1]; idx += 2) { sscanf(&p[idx], "%2X", &c); buffer[idx / 2] = (char) c; } if (DEVICE.ussd_convert == 1) ucs2utf(buffer, idx / 2, sizeof(buffer)); break; case 2: // GSM 7bit packed format is converted to ISO or UTF-8 decode_7bit_packed(p, buffer, sizeof(buffer)); break; } 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), "default", 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"); // 3.1.16beta: Use tmpdir: //sprintf(tmp_filename, "%s/smsd.XXXXXX", "/tmp"); sprintf(tmp_filename, "%s/smsd.XXXXXX", tmpdir); 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); // USSD 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); if (!DEVICE.dev_rr_keep_open) 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 (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); if (!DEVICE.dev_rr_keep_open) 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); } } // 3.1.16beta: dev_rr_cmd is executed after dev_rr_post_run 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 (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() { int result = 1; // 3.1.17: return value changed, check modem_disabled here and log it: if (DEVICE.modem_disabled) { if (DEVICE.startstring[0]) writelogfile(LOG_DEBUG, 0, "send_startstring"); return result; } 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), "start", 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)) result = 0; } } return result; } // 3.1.7: int send_stopstring() { int result = 1; // 3.1.17: return value changed, check modem_disabled here and log it: if (DEVICE.modem_disabled) { if (DEVICE.stopstring[0]) writelogfile(LOG_DEBUG, 0, "send_stopstring"); return result; } 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), "stop", 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 result; } // 3.1.7: Check if a modem is suspended. // 3.1.18: Return value: 0 = should stop, 1 = ok, 2 = ok after suspend int check_suspend(int initialized) { int result = 1; int suspended = 0; FILE *fp; char name[33]; char line[PATH_MAX]; int found; int modem_was_open; int dt; time_t t; struct stat statbuf; static time_t suspend_last_modified = 0; char *p; if (break_suspend || !(*suspend_filename)) return result; // 3.1.18: Do not open and read the file if it's not modified since the last read: if (stat(suspend_filename, &statbuf) == 0) { if (statbuf.st_mtime == suspend_last_modified) return result; suspend_last_modified = statbuf.st_mtime; } else { suspend_last_modified = 0; return result; } modem_was_open = modem_handle >= 0; snprintf(name, sizeof(name), "%s:", DEVICE.name); for (;;) { found = 0; if ((fp = fopen(suspend_filename, "r"))) { while (fgets(line, sizeof(line), fp)) { cutspaces(cut_ctrl(line)); if (!strncmp(line, name, strlen(name)) || !strncmp(line, "ALL:", 4)) found = 1; // 3.1.19beta: Allow wildcard definition for suspend. if (!found && (p = strstr(line, "*:"))) if (!strncmp(line, name, p - line)) found = 1; if (found) { 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; result = 2; suspend_last_modified = 0; // Must read the file next time. // 3.1.18: If delaytime_random_start was used, apply it now when suspend ends: if (!break_suspend && DEVICE.delaytime_random_start == 0) DEVICE.delaytime_random_start = 1; // 3.1.18: refresh identity, if so desired: if (DEVICE.read_identity_after_suspend == 1) { DEVICE.imei[0] = 0; DEVICE.identity[0] = 0; } // 3.1.18: read configuration, it so desired: if (DEVICE.read_configuration_after_suspend == 1) { int errors; if (!terminate) writelogfile(LOG_NOTICE, 0, "Refreshing configuration."); break_suspend = 0; while (!terminate && (errors = refresh_configuration())) { STATISTICS->status = 't'; writelogfile(LOG_CRIT, 0, "There was %i errors while trying to refresh the configuration.", errors); writelogfile(LOG_CRIT, 0, "Fix errors and send me a signal USR2 to retry. My PID is %i.", (int)getpid()); while (!terminate && !break_suspend) sleep(60); // this breaks as soon as any signal is received break_suspend = 0; } break_suspend = 1; STATISTICS->status = 'i'; if (terminate) break; writelogfile(LOG_NOTICE, 0, "Configuration refreshed."); log_adjust_device_starting(1); } if (modem_was_open) if (try_openmodem()) if (initialized) if (!send_startstring()) return 0; } if (!suspended) break; dt = (delaytime > 10)? 10 : (delaytime < 1)? 1 : delaytime; t = time(0); while (time(0) - t < dt) { if (terminate == 1) { // Do not send stop string when terminating: // 3.1.17: Send stop string anyway as it may be important: //DEVICE.stopstring[0] = 0; return 0; } // 3.1.17: React more quickly to the received signal: if (break_suspend) break; sleep(1); } } return result; } void log_adjust_device_starting(int is_refresh) { char *p; int i; i = LOG_NOTICE; p = ""; 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; } if (is_refresh) { if (*p) // 3.1.21: Fixed format for process_id from %s to %i. writelogfile(i, 0, "Modem handler %i:%s", process_id, p); } else { if (DEVICE.logfile[0] && strcmp(DEVICE.logfile, logfile)) { flush_smart_logging(); writelogfile(i, 2, "Modem handler %i has started. PID: %i.%s", process_id, (int)getpid(), p); flush_smart_logging(); } else writelogfile(i, 0, "Modem handler %i has started. PID: %i.%s", process_id, (int)getpid(), p); } // 3.1.16beta2: if (DEVICE.description[0]) writelogfile(LOG_NOTICE, 0, "Description: %s", DEVICE.description); // 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 = -2; //0; 3.1.17 set to disabled when incoming = no } // 3.1.16beta2: If primary_memory and secondary_memory are set to same, disable dual memory handler: if (DEVICE.primary_memory[0] && DEVICE.secondary_memory[0] && !strcmp(DEVICE.primary_memory, DEVICE.secondary_memory)) { writelogfile0(LOG_WARNING, 0, tb_sprintf("Disabling dual memory handler because primary_memory and secondary_memory are both set to same \"%s\".", DEVICE.primary_memory)); alarm_handler0(LOG_WARNING, tb); DEVICE.primary_memory[0] = DEVICE.secondary_memory[0] = 0; } 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; } writelogfile0(LOG_NOTICE, 0, buffer); } // 3.1.16beta: Report queues at startup: if (DEVICE.queues[0][0]) { char buffer[PATH_MAX]; sprintf(buffer, "Serving queues: "); for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (DEVICE.queues[i][0] == 0) break; if (i > 0) strcat(buffer, ", "); strcat(buffer, DEVICE.queues[i]); } writelogfile0(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 && !DEVICE.report_read_timeouts) 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); if (DEVICE.send_retries != 2 && DEVICE.outgoing) writelogfile(LOG_NOTICE, 0, "Using send_retries %i.", DEVICE.send_retries); if (DEVICE.delaytime != -1) writelogfile(LOG_NOTICE, 0, "Using delaytime %i sec.", DEVICE.delaytime); if (DEVICE.report_read_timeouts) log_read_timeouts(LOG_NOTICE); if (DEVICE.report_read_timeouts || DEVICE.poll_faster != POLL_FASTER_DEFAULT) writelogfile(LOG_NOTICE, 0, "Using poll_faster factor %i.", DEVICE.poll_faster); if (DEVICE.report_read_timeouts || DEVICE.read_delay > 0) writelogfile(LOG_NOTICE, 0, "Using read_delay %i milliseconds.", DEVICE.read_delay); } int start_device() { // 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))) { // 3.1.21: Fixed size_t argument to int. writelogfile0(LOG_CRIT, 1, tb_sprintf("Did not get memory for check_memory_buffer (%i). Stopping.", (int)check_memory_buffer_size)); alarm_handler0(LOG_CRIT, tb); return 0; } } if (!send_startstring()) return 0; // 3.1.1: If a modem is used for sending only, it's first initialized. if (DEVICE.outgoing && !DEVICE.incoming) { if (initialize_modem_sending("")) { writelogfile0(LOG_CRIT, 1, tb_sprintf("Failed to initialize modem %s. Stopping.", DEVICE.name)); alarm_handler0(LOG_CRIT, tb); return 0; } else writelogfile(LOG_NOTICE, 0, "Waiting for messages to send..."); } return 1; } void dspooler_clear_msgc(time_t *last_msgc_clear, int *message_count) { if (DEVICE.message_count_clear > 0) { time_t now; if ((now = time(0)) >= *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; } } } void dspooler_send_admin_message(int *quick, int *errorcounter) { if (!strncmp(shared_buffer, DEVICE.name, strlen(DEVICE.name)) && shared_buffer[strlen(DEVICE.name)] == ' ') { char msg[SIZE_SHARED_BUFFER]; char *p; 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); } } } void dspooler_alert_message_limit(int *quick, int *errorcounter, int message_count) { 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); } } int dspooler_check_max_continuous_sending(time_t started_sending, int continuous_sent) { int max_cs = max_continuous_sending; if (DEVICE.max_continuous_sending != -1) max_cs = DEVICE.max_continuous_sending; if (max_cs < 0) max_cs = 0; if (max_cs > 0) { if (time(0) >= started_sending + max_cs) { writelogfile0(LOG_DEBUG, 0, "Max continuous sending time reached, will do other tasks and then continue."); if (continuous_sent) { time_t seconds; seconds = time(0) - started_sending; // 3.1.21: Fixed time_t argument to int. writelogfile(LOG_INFO, 0, "Sent %d messages in %d sec. Average time for one message: %.1f sec.", continuous_sent, (int)seconds, (double)seconds / continuous_sent); } return 1; } } return 0; } void dspooler_handle_routed_pdu() { if (routed_pdu_store) { char *term; char filename[PATH_MAX]; int stored_concatenated; int statusreport; char *p; 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) { // 3.1.17: Save a copy of routed message/status report if necessary. char copyfilename[PATH_MAX] = {}; if (*d_incoming_copy || (statusreport && *d_report_copy)) { char *dest = d_incoming_copy; if (statusreport && *d_report_copy) dest = d_report_copy; copyfilewithdestlock(filename, dest, 0/*keep_filename*/, 0/*store_original_filename*/, process_title, copyfilename); } run_eventhandler(filename, copyfilename, (statusreport)? "REPORT" : "RECEIVED", 0); } p = term +1; } free(routed_pdu_store); routed_pdu_store = NULL; } } int dspooler_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 if (DEVICE.phonecalls == 1) { writelogfile(LOG_INFO, 0, "Reading phonecall entries"); sprintf(command,"AT+CPBS=\"%s\"\r", "MC"); put_command(command, answer, sizeof(answer), "default", 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), "cpbr", 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), "cpbr", 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 (%i)", 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), "default", 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), "cpbw", 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; #undef PB_MAX_ERRORS #undef PB_INDEX_DEFAULT } void dspooler_purge_internal_combine(time_t *last_ic_purge) { if (DEVICE.internal_combine == 1 || (DEVICE.internal_combine == -1 && internal_combine == 1)) { if ((ic_purge_hours *60 +ic_purge_minutes) > 0) { time_t now = time(0); if (now >= *last_ic_purge + ic_purge_interval) { *last_ic_purge = now; do_ic_purge(); } } } } int dspooler_run_rr(time_t *last_rr) { if (DEVICE.dev_rr_interval > 0) { time_t now = time(0); if (now >= *last_rr + DEVICE.dev_rr_interval) { *last_rr = now; if (!run_rr()) return 0; } } return 1; } void devicespooler() { int workless; int quick = 0; int errorcounter; int i; time_t last_msgc_clear = time(0); time_t last_rr = 0; time_t last_ic_purge = 0; time_t started_sending; int continuous_sent; // 3.1.14. // Load initial modemname.counter value: update_message_counter(0, DEVICE.name); *smsd_debug = 0; put_command_sent = 0; errorcounter = 0; concatenated_id = getrand(255); log_adjust_device_starting(0); if (!check_suspend(0)) return; // Open serial port or return if not successful if (!try_openmodem()) return; if (!start_device()) return; #ifdef DEBUGMSG printf("!! Entering endless send/receive loop\n"); #endif flush_smart_logging(); while (terminate == 0) /* endless loop */ { workless = 1; break_workless_delay = 0; continuous_sent = 0; while (!terminate && DEVICE.outgoing) { switch (check_suspend(1)) { case 0: return; case 2: quick = 0; } dspooler_clear_msgc(&last_msgc_clear, &message_count); if (DEVICE.message_limit > 0 && message_count >= DEVICE.message_limit) break; dspooler_send_admin_message(&quick, &errorcounter); if (!try_openmodem()) return; if (continuous_sent == 0) started_sending = time(0); i = send1sms(&quick, &errorcounter); if (i > 0) { if (!message_count) last_msgc_clear = time(0); message_count++; continuous_sent++; dspooler_alert_message_limit(&quick, &errorcounter, message_count); // 3.1.18: When suspend is breaked, only one SMS is sent. Signal USR2 can also // be used to break sending when max continuous sending is not yet reached. if (break_suspend || dspooler_check_max_continuous_sending(started_sending, continuous_sent)) { quick = 0; workless = 0; break; } } else if (i != -2) // If there was a failed messsage, do not break. break; workless = 0; if (break_suspend) break; if (DEVICE.incoming == 2) // repeat only if receiving has low priority break; if (terminate == 1) return; flush_smart_logging(); // 3.1.17: Check and run rr even when sending continuously: if (!dspooler_run_rr(&last_rr)) return; } // outgoing loop if (terminate == 1) return; // Receive SM if (DEVICE.incoming) { switch (check_suspend(1)) { case 0: return; case 2: quick = 0; } 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(); dspooler_handle_routed_pdu(); if (terminate == 1) return; } dspooler_readphonecalls(); if (!dspooler_run_rr(&last_rr)) return; dspooler_purge_internal_combine(&last_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; switch (check_suspend(1)) { case 0: return; case 2: quick = 0; workless = 0; } if (workless == 1) // wait a little bit if there was no SM to send or receive to save CPU usage { // 3.1.18: Use delaytime of a modem, if it's defined: int d_time = delaytime; if (DEVICE.delaytime != -1) d_time = DEVICE.delaytime; // 3.1.18: Randomize first delay, if necessary: if (DEVICE.delaytime_random_start == 1) // -1 == not set { d_time = getrand(d_time); DEVICE.delaytime_random_start = 0; } try_closemodem(0); // Disable quick mode if modem was workless quick = 0; if (!trouble_logging_started) STATISTICS->status = 'i'; if (!spend_delay(d_time, &dspooler_run_rr, &last_rr, DEVICE.dev_rr_interval)) return; } 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 == PROCESS_ID_CHILD || process_id == PROCESS_ID_NOTIFIER) { signal(SIGTERM, SIG_IGN); signal(SIGINT, SIG_IGN); signal(SIGHUP, SIG_IGN); signal(SIGUSR1, SIG_IGN); } else if (process_id == PROCESS_ID_MAINPROCESS) { 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_IS_MODEM) { 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) { // 3.1.16beta2: log time of start and uptime. char timestamp[81]; char tmp[81]; // Note: Cannot use logtime_format, because it may have timems or timeus defined. strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&process_start_time)); make_uptime_string(tmp, sizeof(tmp), time(0) - process_start_time); if (process_id == PROCESS_ID_MAINPROCESS) { if (all) sendsignal2devices(SIGTERM); // Child may have terminated before, check it's pid: if (mainprocess_child_pid) kill(mainprocess_child_pid, SIGTERM); if (mainprocess_notifier_pid) kill(mainprocess_notifier_pid, SIGKILL); // 3.1.18: Must use kill at least in some environments. // If child is bash script and it's using pipes, it has one or more childs // which do not terminate when the script terminates. // Kill them by sending SIGTERM to the process group. if (*mainprocess_child) { signal(SIGTERM, SIG_IGN); // ignore SIGTERM, already going to terminate killpg(getpid(), SIGTERM); } // 3.1.16beta: Be the last who stops: waitpid(0, 0, 0); remove_pid(pidfile); if (*infofile) unlink(infofile); writelogfile(LOG_CRIT, 1, "Smsd mainprocess terminated abnormally. PID: %i, was started %s, up %s.", (int) getpid(), timestamp, tmp); flush_smart_logging(); closelogfile(); #ifndef NOSTATS MM_destroy(); #endif exit(EXIT_FAILURE); } else if (PROCESS_IS_MODEM) { if (all) kill((int)getppid(), SIGTERM); writelogfile(LOG_CRIT, 1, "Modem handler %i terminated abnormally. PID: %i, was started %s, up %s.", process_id, (int) getpid(), timestamp, tmp); flush_smart_logging(); closelogfile(); exit(EXIT_FAILURE); } } void signal_handler(int signum) { signal(SIGCONT, SIG_IGN); signal(SIGUSR2, SIG_IGN); signal(SIGCHLD, SIG_IGN); if (signum == SIGCHLD) { // 3.1.16beta: If a modem process stops, mainprocess will listen it in the main loop. if (process_id == PROCESS_ID_MAINPROCESS) got_sigchld = 1; #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_CRIT, 0, "SIGCHLD received, process_id: %i", process_id); #endif } else if (signum == SIGCONT) { if (process_id == PROCESS_ID_MAINPROCESS) { // 3.1.2: Signal handlers are now silent. #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_CRIT, 0, "Smsd mainprocess received SIGCONT. PID: %i.", (int)getpid()); #endif // 3.1.17: When mainprocess receives SIGCONT, stop waiting but do not send the signal to modem handlers: //sendsignal2devices(SIGCONT); break_workless_delay = 1; } else if (PROCESS_IS_MODEM) { #ifdef DEBUG_SIGNALS_NOT_FOR_PRODUCTION writelogfile(LOG_INFO, 0, "Modem handler %i received SIGCONT. PID: %i.", process_id, (int)getpid()); #endif break_workless_delay = 1; } } else if (signum == SIGUSR2) break_suspend = 1; signal(SIGCONT, signal_handler); signal(SIGUSR2, signal_handler); signal(SIGCHLD, signal_handler); } void apply_process_name(int argc, char **argv, char *process_title) { if (use_linux_ps_trick) { memset(argv[0] + sizeof("smsd: ") - 1, 0, sizeof("MAINPROCESS") - 1); strcpy(argv[0] + sizeof("smsd: ") - 1, process_title); } else { int idx; for (idx = 0; idx < argc; idx++) { if (strncmp(argv[idx], "MAINPROCESS", 11) == 0) { size_t l = strlen(process_title); if (l > strlen(argv[idx])) l = strlen(argv[idx]); strncpy(argv[idx], process_title, l); while (argv[idx][l]) argv[idx][l++] = '_'; break; } else if (!strncmp(argv[idx], "-nMAINPROCESS", 13)) { size_t l = strlen(process_title); if (l > strlen(argv[idx]) - 2) l = strlen(argv[idx] - 2); strncpy(argv[idx] + 2, process_title, l); while (argv[idx][l + 2]) argv[idx][l++ + 2] = '_'; break; } } } } /* ======================================================================= Main ======================================================================= */ int main(int argc,char** argv) { int i; struct passwd *pwd; struct group *grp; int result = 1; pid_t pid; char timestamp[81]; char tmp[81]; int start_failed = 0; process_id = PROCESS_ID_MAINPROCESS; strcpy(process_title, "smsd"); // 3.1.16beta, 3.1.17: Set tmpdir, use TMPDIR or TEMPDIR if defined: char *p = getenv("TMPDIR"); if (p && *p && strlen(p) < sizeof(tmpdir)) snprintf(tmpdir, sizeof(tmpdir), "%s", p); else { p = getenv("TEMPDIR"); if (p && *p && strlen(p) < sizeof(tmpdir)) snprintf(tmpdir, sizeof(tmpdir), "%s", p); else strcpy(tmpdir, "/tmp"); } break_workless_delay = 0; terminate = 0; 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); signal(SIGCHLD,signal_handler); // 3.1.16beta: To reap stopped modem processes. // 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]; 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); writelogfile0(LOG_CRIT, 0, p); exit(EXIT_FAILURE); } pwd = getpwuid(getuid()); grp = getgrgid(getgid()); // 3.1.16beta2: Show also numeric uid:gid, and only them if names are not available. if (pwd && grp) writelogfile(LOG_CRIT, 0, "Running as %s:%s (%u:%u).", pwd->pw_name, grp->gr_name, (int)getuid(), (int)getgid()); else writelogfile(LOG_CRIT, 0, "Running as %u:%u.", (int)getuid(), (int)getgid()); 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); } } 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"); } // 3.1.18: Run mainprocess_start if defined: if (*mainprocess_start) { char cmdline[PATH_MAX + PATH_MAX + 32]; int i; snprintf(cmdline, sizeof(cmdline), "%s %s", mainprocess_start, mainprocess_start_args); if ((i = my_system(cmdline, EXEC_START)) != 0) { writelogfile(LOG_CRIT, 0, "Start script %s returned %i.", mainprocess_start, i); start_failed = 1; } } // 3.1.17: Start child if defined: if (*mainprocess_child) { mainprocess_child_pid = fork(); if (mainprocess_child_pid < 0) { writelogfile(LOG_CRIT, 0, "Cannot start child %s", mainprocess_child); start_failed = 1; } if (mainprocess_child_pid == 0) { char command[PATH_MAX + PATH_MAX]; process_id = PROCESS_ID_CHILD; strcpy(process_title, "CHILD"); apply_process_name(argc, argv, process_title); snprintf(command, sizeof(command), "%s %s", mainprocess_child, mainprocess_child_args); system(command); exit(127); } else writelogfile(LOG_NOTICE, 0, "Child %s has started. PID: %d", mainprocess_child, mainprocess_child_pid); } #ifndef DISABLE_INOTIFY // 3.1.17: Start notifier if enabled and not talking with any modem: if (!start_failed && mainprocess_notifier == 1 && *communicate == 0) { pid = fork(); if (pid > 0) { mainprocess_notifier_pid = pid; writelogfile(LOG_NOTICE, 1, "Notifier has started. PID: %i.", pid); } if (pid == 0) { #define EVENT_SIZE (sizeof(struct inotify_event)) int fd, wd, length, i; char buffer[128 * (EVENT_SIZE + 16)]; process_id = PROCESS_ID_NOTIFIER; strcpy(process_title, "NOTIFIER"); apply_process_name(argc, argv, process_title); fd = inotify_init(); if (fd < 0) { writelogfile(LOG_CRIT, 0, "Unable to initialize notifier: %i %s", errno, strerror(errno)); start_failed = 1; } else { wd = inotify_add_watch(fd, d_spool, IN_CLOSE_WRITE | IN_MOVED_TO); if (wd == -1) { writelogfile(LOG_CRIT, 0, "Unable to add watch to notifier: %i %s", errno, strerror(errno)); start_failed = 1; } else { while (terminate == 0) { length = read(fd, buffer, sizeof(buffer)); if (length > 0) { i = 0; while (i < length) { struct inotify_event *event = (struct inotify_event *)&buffer[i]; if ((event->mask & IN_ISDIR) == 0) { if (event->len) { int skip = 0; if (event->name[0] == '.' || !strncmp(event->name, "LOCKED", 6)) skip = 1; if (strlen(event->name) >= 5 && !strcmp(event->name + strlen(event->name) - 5, ".LOCK")) skip = 1; if (!skip) kill((int)getppid(), SIGCONT); } } i += EVENT_SIZE + event->len; } } // Not logging errors. When stopping, we get 4 Interrupted system call //else // writelogfile(LOG_CRIT, 0, "Error with notifier: %i %s", errno, strerror(errno)); } inotify_rm_watch(fd, wd); } close(fd); } exit(127); #undef EVENT_SIZE } } #endif // If notifier is used and it did not start, smsd should stop too. if (start_failed) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated."); exit(EXIT_FAILURE); } // 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) { process_id = i; strcpy(process_title, DEVICE.name); apply_process_name(argc, argv, process_title); time(&process_start_time); 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); if (DEVICE.needs_wakeup_at) { put_command("AT\r", 0, 0, "default", 0); usleep_until(time_usec() + 100000); read_from_modem(answer, sizeof(answer), 2); } do { retries++; *answer = 0; put_command("AT\r", answer, sizeof(answer), "default", 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), "default", 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, "default", EXPECT_OK_ERROR); while (*answer && !isdigitc(*answer)) strcpyo(answer, answer +1); if (strstr(answer, "ERROR")) { put_command("AT+CGSN\r", answer, SIZE_IDENTITY, "default", 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'; free(check_memory_buffer); check_memory_buffer = 0; } // Note: Cannot use logtime_format, because it may have timems or timeus defined. strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&process_start_time)); make_uptime_string(tmp, sizeof(tmp), time(0) - process_start_time); writelogfile(LOG_CRIT, 0, "Modem handler %i terminated. PID: %i, was started %s, up %s.", process_id, (int) getpid(), timestamp, tmp); flush_smart_logging(); if (DEVICE.logfile[0]) closelogfile(); exit(127); // modem process terminates here. } } } // Start main program mainspooler(); // 3.1.16beta: If there are no any modem processes left, skip logging. //writelogfile(LOG_CRIT, 0, "Smsd mainprocess is awaiting the termination of all modem handlers. PID: %i.", (int)getpid()); for (i = 0; i < NUMBER_OF_MODEMS; i++) { if (device_pids[i] > 0) { writelogfile(LOG_CRIT, 0, "Smsd mainprocess is awaiting the termination of all modem handlers. PID: %i.", (int)getpid()); break; } } // Child may have terminated before, check it's pid: if (mainprocess_child_pid > 0) { kill(mainprocess_child_pid, SIGTERM); mainprocess_child_pid = 0; } // If child is bash script and it's using pipes, it has one or more childs // which do not terminate when the script terminates. // Kill them by sending SIGTERM to the process group. if (*mainprocess_child) { signal(SIGTERM, SIG_IGN); // ignore SIGTERM, already going to terminate killpg(getpid(), SIGTERM); } if (mainprocess_notifier_pid > 0) { kill(mainprocess_notifier_pid, SIGKILL); // 3.1.18: Must use kill at least in some environments. mainprocess_notifier_pid = 0; } waitpid(0, 0, 0); savestats(); #ifndef NOSTATS MM_destroy(); #endif remove_pid(pidfile); if (*infofile) unlink(infofile); // Note: Cannot use logtime_format, because it may have timems or timeus defined. strftime(timestamp, sizeof(timestamp), datetime_format, localtime(&process_start_time)); make_uptime_string(tmp, sizeof(tmp), time(0) - process_start_time); writelogfile(LOG_CRIT, 0, "Smsd mainprocess terminated. PID: %i, was started %s, up %s.", (int) getpid(), timestamp, tmp); flush_smart_logging(); closelogfile(); return 0; } smstools3/src/pdu.h0000755000175000017500000000704613046433000013177 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 with_udh, char *udh_data, char *mode, char *pdu, int validity, int replace_msg, int system_msg, int number_type, char *smsc, // 3.1.16beta2: int message_reference, int reject_duplicates, int reply_path, int sms_class, int tp_dcs, int ping ); // 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); int read_pdu_text(char *pdu, size_t pdu_size, char *text); int get_pdu_submit_to(char *to, size_t to_size, char *pdu); #endif smstools3/src/extras.h0000755000175000017500000001020013053650757013721 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 #include FILE *fopen_mkstemp(char *fname); /* 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); int copyfile(char *filename, char *directory); int copymovefile(int copy, 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 keep_fname, int store_original_fname, char *prefix, char *newfilename); int copyfilewithdestlock(char *filename, char *directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename); int copymovefilewithdestlock(int copy, 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); int make_uptime_string(char *dest, size_t dest_size, time_t upt); int is_ok_answer(char *answer); int is_error_answer(char *answer); int is_ok_0_answer(char *answer); int is_error_4_answer(char *answer); int is_ok_error_answer(char *answer); int is_ok_error_0_4_answer(char *answer); int get_file_details(char *filename, char *dest, size_t dest_size); int calculate_required_parts(char *text, int textlen, int *reserved, int split, int *use_get_part); int get_part(char **part_start, char *text, int textlen, int reserved, int part); int spend_delay(int delaytime, int (*cb)(time_t *), time_t *cb_last, int cb_interval); #endif smstools3/src/alarm.h0000755000175000017500000000203213100200671013466 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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); #ifdef __GNUC__ void alarm_handler(int severity, char* format, ...) __attribute__ ((format(printf, 2, 3))); #else void alarm_handler(int severity, char* format, ...); #endif #endif smstools3/src/logging.c0000755000175000017500000002405713054334213014036 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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; unsigned long long trouble_logging_start_time = 0; // 3.1.16beta. For total time of continuous trouble. int logging_start_printed = 0; // 3.1.16beta. 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) // 3.1.16beta. && 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; trouble_logging_start_time = 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) // 3.1.16beta. && Level < 7) { // 3.1.16beta: Mark the start of communication: if (!logging_start_printed) { logging_start_printed = 1; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2),"%s,%i, %s: Start:\n", timestamp, severity, process_title); strcat_realloc(&trouble_logging_buffer, text2, 0); } if (trouble) { if (!trouble_logging_started) { trouble_logging_started = 1; // 3.1.16beta: if (!trouble_logging_start_time) { if (put_command_sent) trouble_logging_start_time = put_command_sent; else trouble_logging_start_time = time_usec(); } // Global process_id is the same as int device in many functions calls. if (PROCESS_IS_MODEM) STATISTICS->status = 't'; // 3.1.16beta: Mark the start of trouble (not when a process is starting, value 2): if (trouble == 1) { make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2),"%s,%i, %s: Trouble:\n", timestamp, severity, process_title); strcat_realloc(&trouble_logging_buffer, text2, 0); } } } // 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"); strcat_realloc(&trouble_logging_buffer, text2, 0); } } int make_duration_string(char *dest, size_t size_dest, unsigned long long time_start, unsigned long long time_now) { int duration; *dest = 0; duration = (int)(time_now - time_start) / 100000; if (duration >= 10) { if (duration < 600) snprintf(dest, size_dest, "%.1f sec.", (double)duration / 10); else { duration = (int)duration / 10; snprintf(dest, size_dest, "%i min %i sec.", (int)duration / 60, (int)(duration - duration / 60 * 60)); } return 1; } return 0; } void flush_smart_logging() { static unsigned long long last_flush_time = 0; // 3.1.16beta. char text2[SIZE_LOG_LINE]; char timestamp[40]; char duration[64]; if (trouble_logging_started && trouble_logging_buffer) { write(Filehandle_trouble, trouble_logging_buffer, strlen(trouble_logging_buffer)); last_flush_was_clear = 0; last_flush_time = time_usec(); if (trouble_logging_start_time) { make_duration_string(duration, sizeof(duration), trouble_logging_start_time, last_flush_time); make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); snprintf(text2, sizeof(text2), "%s,%i, %s: (flush) %s\n", timestamp, LOG_NOTICE, process_title, duration); write(Filehandle_trouble, text2, strlen(text2)); } } 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 trouble_time[128]; make_datetime_string(timestamp, sizeof(timestamp), 0, 0, logtime_format); // 3.1.16beta: //snprintf(text2, sizeof(text2), "%s,%i, %s: %s\n", timestamp, LOG_NOTICE, process_title, "Everything ok now."); *trouble_time = 0; if (trouble_logging_start_time && last_flush_time) if (make_duration_string(duration, sizeof(duration), trouble_logging_start_time, last_flush_time)) snprintf(trouble_time, sizeof(trouble_time), " Duration of trouble was %s", duration); snprintf(text2, sizeof(text2), "%s,%i, %s: %s%s\n", timestamp, LOG_NOTICE, process_title, "Everything ok now.", trouble_time); write(Filehandle_trouble, text2, strlen(text2)); } last_flush_was_clear = 1; logging_start_printed = 0; trouble_logging_start_time = 0; put_command_sent = 0; last_flush_time = 0; } trouble_logging_started = 0; if (trouble_logging_buffer) { free(trouble_logging_buffer); trouble_logging_buffer = 0; } } // ****************************************************************************** // Collect character conversion log, flush it if called with format==NULL. // Also prints to the stdout, if debugging. void logch(char* format, ...) { va_list argp; char text[2048]; int flush = 0; if (format) { va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); if (strlen(logch_buffer) +strlen(text) < sizeof(logch_buffer)) { sprintf(strchr(logch_buffer, 0), "%s", text); // Line wrap after space character: // Outgoing conversion: if (strlen(text) >= 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 '.'; } smstools3/src/smsd_cfg.c0000755000175000017500000034330713102206664014201 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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" #include "modeminit.h" #include "charshift.h" char *msg_dir = "%s directory %s cannot be opened."; char *msg_file = "%s directory %s is not writable."; char *msg_not_executable = "is not executable for smsd."; char *msg_is_directory = "is a directory, script expected."; 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; } #ifdef __GNUC__ void startuperror(char* format, ...) __attribute__ ((format(printf, 1, 2))); #endif 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_device(int i) { int j; devices[i].name[0] = 0; devices[i].number[0] = 0; devices[i].device[0] = 0; devices[i].device_open_retries = 1; devices[i].device_open_errorsleeptime = 30; devices[i].device_open_alarm_after = 0; devices[i].identity[0] = 0; devices[i].imei[0] = 0; devices[i].conf_identity[0] = 0; for (j = 0; j < NUMBER_OF_MODEMS; j++) devices[i].queues[j][0] = 0; devices[i].incoming = 0; devices[i].outgoing = 1; devices[i].report = 0; devices[i].phonecalls = 0; devices[i].phonecalls_purge[0] = 0; devices[i].phonecalls_error_max = 3; devices[i].pin[0] = 0; devices[i].pinsleeptime = 0; strcpy(devices[i].mode, "new"); devices[i].smsc[0] = 0; devices[i].baudrate = 115200; devices[i].send_delay = 0; devices[i].send_handshake_select = 1; devices[i].cs_convert = 1; devices[i].cs_convert_optical = 1; devices[i].initstring[0] = 0; devices[i].initstring2[0] = 0; devices[i].eventhandler[0] = 0; devices[i].eventhandler_ussd[0] = 0; devices[i].ussd_convert = 0; devices[i].rtscts = 1; devices[i].read_memory_start = -1; // 3.1.20: -1 = not set, default is still 1. devices[i].primary_memory[0] = 0; devices[i].secondary_memory[0] = 0; devices[i].secondary_memory_max = -1; devices[i].pdu_from_file[0] = 0; devices[i].sending_disabled = 0; devices[i].modem_disabled = 0; devices[i].decode_unicode_text = -1; devices[i].internal_combine = -1; devices[i].internal_combine_binary = -1; devices[i].pre_init = 1; devices[i].check_network = 1; devices[i].admin_to[0] = 0; devices[i].message_limit = 0; devices[i].message_count_clear = 0; devices[i].keep_open = 1; // 0; devices[i].dev_rr[0] = 0; devices[i].dev_rr_post_run[0] = 0; devices[i].dev_rr_interval = 5 * 60; devices[i].dev_rr_cmdfile[0] = 0; devices[i].dev_rr_cmd[0] = 0; devices[i].dev_rr_logfile[0] = 0; devices[i].dev_rr_loglevel = LOG_NOTICE; devices[i].dev_rr_statfile[0] = 0; devices[i].dev_rr_keep_open = 0; // 0 to be compatible with previous versions. devices[i].logfile[0] = 0; devices[i].loglevel = -1; devices[i].messageids = 2; devices[i].voicecall_vts_list = 0; devices[i].voicecall_ignore_modem_response = 0; devices[i].voicecall_hangup_ath = -1; devices[i].voicecall_vts_quotation_marks = 0; devices[i].voicecall_cpas = 0; devices[i].voicecall_clcc = 0; devices[i].check_memory_method = CM_CPMS; strcpy(devices[i].cmgl_value, "4"); devices[i].priviledged_numbers[0] = 0; devices[i].read_timeout = 5; devices[i].ms_purge_hours = 6; devices[i].ms_purge_minutes = 0; devices[i].ms_purge_read = 1; devices[i].detect_message_routing = 1; devices[i].detect_unexpected_input = 1; devices[i].unexpected_input_is_trouble = 1; devices[i].adminmessage_limit = 0; devices[i].adminmessage_count_clear = 0; devices[i].status_signal_quality = -1; devices[i].status_include_counters = -1; devices[i].communication_delay = 0; devices[i].hangup_incoming_call = -1; devices[i].max_continuous_sending = -1; devices[i].socket_connection_retries = 11; devices[i].socket_connection_errorsleeptime = 5; devices[i].socket_connection_alarm_after = 0; devices[i].report_device_details = (strstr(smsd_version, "beta")) ? 1 : 0; devices[i].using_routed_status_report = 0; devices[i].routed_status_report_cnma = 1; devices[i].needs_wakeup_at = 0; devices[i].keep_messages = 0; devices[i].startstring[0] = 0; devices[i].startsleeptime = 3; devices[i].stopstring[0] = 0; devices[i].trust_spool = 1; devices[i].smsc_pdu = 0; devices[i].telnet_login[0] = 0; snprintf(devices[i].telnet_login_prompt, sizeof(devices[i].telnet_login_prompt), "%s", TELNET_LOGIN_PROMPT_DEFAULT); snprintf(devices[i].telnet_login_prompt_ignore, sizeof(devices[i].telnet_login_prompt_ignore), "%s", TELNET_LOGIN_PROMPT_IGNORE_DEFAULT); devices[i].telnet_password[0] = 0; snprintf(devices[i].telnet_password_prompt, sizeof(devices[i].telnet_password_prompt), "%s", TELNET_PASSWORD_PROMPT_DEFAULT); devices[i].telnet_cmd[0] = 0; devices[i].telnet_cmd_prompt[0] = 0; devices[i].telnet_crlf = 1; devices[i].wakeup_init[0] = 0; devices[i].signal_quality_ber_ignore = 0; devices[i].verify_pdu = 0; devices[i].loglevel_lac_ci = 6; devices[i].log_not_registered_after = 0; devices[i].send_retries = 2; devices[i].report_read_timeouts = 0; devices[i].select_pdu_mode = 1; devices[i].ignore_unexpected_input[0] = 0; devices[i].national_toa_unknown = 0; devices[i].reply_path = 0; devices[i].description[0] = 0; devices[i].text_is_pdu_key[0] = 0; devices[i].sentsleeptime = 0; devices[i].poll_faster = POLL_FASTER_DEFAULT; devices[i].read_delay = 0; devices[i].language = -2; devices[i].language_ext = -2; devices[i].notice_ucs2 = 2; devices[i].receive_before_send = -1; devices[i].delaytime = -1; devices[i].delaytime_random_start = -1; devices[i].read_identity_after_suspend = 1; devices[i].read_configuration_after_suspend = 0; devices[i].check_sim = 0; // 3.1.21. devices[i].check_sim_cmd[0] = 0; // 3.1.21. devices[i].check_sim_keep_open = 0; // 3.1.21. devices[i].check_sim_reset[0] = 0; // 3.1.21. devices[i].check_sim_retries = 10; // 3.1.21. devices[i].check_sim_wait = 30; // 3.1.21. } 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_incoming_copy = 0; // 3.1.16beta2. *d_report = 0; *d_report_copy = 0; // 3.1.17. *d_phonecalls = 0; *d_saved = 0; strcpy(d_checked,"/var/spool/sms/checked"); d_failed[0]=0; d_failed_copy[0]=0; // 3.1.17. d_sent[0]=0; d_sent_copy[0]=0; // 3.1.17. 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; status_include_uptime = 0; // 3.1.16beta. 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; log_response_time = 0; // 3.1.16beta. log_read_timing = 0; // 3.1.16beta2. default_alphabet = ALPHABET_DEFAULT; *mainprocess_child = 0; // 3.1.17. *mainprocess_child_args = 0; // 3.1.17. mainprocess_notifier = 0; // 3.1.17. eventhandler_use_copy = 0; // 3.1.17. sleeptime_mainprocess = 1; // 3.1.17. check_pid_interval = 10; // 3.1.17. *mainprocess_start = 0; // 3.1.18. *mainprocess_start_args = 0; // 3.1.18. 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; } // 3.1.16beta2: More settings may now use "modemname". char *apply_modemname(char *device_name, char *value) { char *p; char tmp[4096]; 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); } } return value; } int readcfg_device(int device, char *device_name) { #define NEWDEVICE devices[device] FILE *File; File = fopen(configfile, "r"); if (File) { // 3.1.12: modem section is no more mandatory. char *default_section = "default"; int read_section; int device_found = 1; char name[64]; int result; char value[4096]; int j; char tmp[4096]; char *p; char group_section[64 + 1] = { }; // 3.1.19beta. int group_section_found = 0; if (!isdigit(*device_name)) { strcpy(group_section, device_name); p = group_section; while (*p && !isdigit(*p)) p++; strcpy(p, "*"); group_section_found = gotosection(File, group_section); } if (!gotosection(File, default_section) && !gotosection(File, device_name) && !group_section_found) { if (*group_section) startuperror("Problem in configuration: [%s], [%s] and [%s] are all missing.\n", device_name, default_section, group_section); else startuperror("Problem in configuration: [%s] and [%s] are missing.\n", device_name, default_section); } else { for (read_section = 2; read_section >= 0; read_section--) { if (read_section == 2) { if (!gotosection(File, default_section)) continue; strcpy2(NEWDEVICE.name, default_section); } else if (read_section == 1) { if (!group_section_found || !gotosection(File, group_section)) continue; strcpy2(NEWDEVICE.name, group_section); } 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; } // .name is set by the program if (!strcasecmp(name, "number")) { strcpy2(NEWDEVICE.number, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "device")) { ask_value(NEWDEVICE.name, name, value); // 3.1.12: special devicename: apply_modemname(device_name, value); strcpy2(NEWDEVICE.device, value); continue; } if (!strcasecmp(name, "device_open_retries")) { NEWDEVICE.device_open_retries = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "device_open_errorsleeptime")) { NEWDEVICE.device_open_errorsleeptime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "device_open_alarm_after")) { NEWDEVICE.device_open_alarm_after = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } // .identity is set by the program, +CIMI, IMSI // .imei is set by the program, +CGSN, Serial Number if (!strcasecmp(name, "identity")) // .conf_identity, undocumented. { strcpy2(NEWDEVICE.conf_identity, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "queues")) { ask_value(NEWDEVICE.name, name, value); // 3.1.16beta: Fix: Forget previous values (from [default] section): for (j = 0; j < NUMBER_OF_MODEMS; j++) NEWDEVICE.queues[j][0] = 0; // 3.1.5: special queuename: apply_modemname(device_name, value); // 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; } continue; } if (!strcasecmp(name, "incoming")) { ask_value(NEWDEVICE.name, name, value); if (!strcasecmp(value, "high")) 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); } } continue; } if (!strcasecmp(name, "outgoing")) { if ((NEWDEVICE.outgoing = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "report")) { ask_value(NEWDEVICE.name, name, value); // 3.1.16beta2: Can disable reports completely: if (strncasecmp(value, "disabled", strlen(value)) == 0) NEWDEVICE.report = -2; else if ((NEWDEVICE.report = yesno_check(value)) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "phonecalls")) { ask_value(NEWDEVICE.name, name, value); if (!strcasecmp(value, "clip")) NEWDEVICE.phonecalls = 2; else { NEWDEVICE.phonecalls = atoi(value); if (NEWDEVICE.phonecalls == 0) { if ((NEWDEVICE.phonecalls = yesno_check(value)) == -1) startuperror(yesno_error, name, value); } } continue; } if (!strcasecmp(name, "phonecalls_purge")) { strcpy2(NEWDEVICE.phonecalls_purge, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "phonecalls_error_max")) { NEWDEVICE.phonecalls_error_max = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "pin")) { strcpy2(NEWDEVICE.pin, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "pinsleeptime")) { NEWDEVICE.pinsleeptime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "mode")) { ask_value(NEWDEVICE.name, name, value); if (!strcasecmp(value, "ascii")) startuperror("Ascii mode is not supported anymore.\n"); if (strcasecmp(value, "old") && strcasecmp(value, "new")) startuperror("Invalid mode=%s.\n", value); else strcpy2(NEWDEVICE.mode, value); continue; } if (!strcasecmp(name, "smsc")) { strcpy2(NEWDEVICE.smsc, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "baudrate")) { NEWDEVICE.baudrate = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "send_delay")) { NEWDEVICE.send_delay = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "send_handshake_select")) { if ((NEWDEVICE.send_handshake_select = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "cs_convert")) { if ((NEWDEVICE.cs_convert = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "cs_convert_optical")) { if ((NEWDEVICE.cs_convert_optical = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "init") || !strcasecmp(name, "init1")) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.initstring, sizeof(NEWDEVICE.initstring) - 2, value, name); strcat(NEWDEVICE.initstring, "\r"); continue; } if (!strcasecmp(name, "init2")) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.initstring2, sizeof(NEWDEVICE.initstring2) - 2, value, name); strcat(NEWDEVICE.initstring2, "\r"); continue; } if (!strcasecmp(name, "eventhandler")) { strcpy2(NEWDEVICE.eventhandler, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "eventhandler_ussd")) { strcpy2(NEWDEVICE.eventhandler_ussd, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "ussd_convert")) { NEWDEVICE.ussd_convert = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "rtscts")) { if ((NEWDEVICE.rtscts = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "memory_start")) { NEWDEVICE.read_memory_start = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "primary_memory")) { ask_value(NEWDEVICE.name, name, value); while ((p = strchr(value, '\"'))) strcpyo(p, p + 1); strcpy2(NEWDEVICE.primary_memory, value); continue; } if (!strcasecmp(name, "secondary_memory")) { ask_value(NEWDEVICE.name, name, value); while ((p = strchr(value, '\"'))) strcpyo(p, p + 1); strcpy2(NEWDEVICE.secondary_memory, value); continue; } if (!strcasecmp(name, "secondary_memory_max")) { NEWDEVICE.secondary_memory_max = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "pdu_from_file")) { strcpy2(NEWDEVICE.pdu_from_file, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "sending_disabled")) { if ((NEWDEVICE.sending_disabled = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "modem_disabled")) { if ((NEWDEVICE.modem_disabled = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "decode_unicode_text")) { if ((NEWDEVICE.decode_unicode_text = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "internal_combine")) { if ((NEWDEVICE.internal_combine = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "internal_combine_binary")) { if ((NEWDEVICE.internal_combine_binary = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "pre_init")) { if ((NEWDEVICE.pre_init = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "check_network")) { // 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); continue; } if (!strcasecmp(name, "admin_to")) { strcpy2(NEWDEVICE.admin_to, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "message_limit")) { NEWDEVICE.message_limit = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "message_count_clear")) { NEWDEVICE.message_count_clear = atoi(ask_value(NEWDEVICE.name, name, value)) * 60; continue; } if (!strcasecmp(name, "keep_open")) { if ((NEWDEVICE.keep_open = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "regular_run")) { strcpy2(NEWDEVICE.dev_rr, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "regular_run_post_run")) { strcpy2(NEWDEVICE.dev_rr_post_run, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "regular_run_interval")) { 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); continue; } if (!strcasecmp(name, "regular_run_cmdfile")) { strcpy2(NEWDEVICE.dev_rr_cmdfile, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "regular_run_cmd")) { 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); } continue; } if (!strcasecmp(name, "regular_run_logfile")) { strcpy2(NEWDEVICE.dev_rr_logfile, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "regular_run_loglevel")) { ask_value(NEWDEVICE.name, name, value); NEWDEVICE.dev_rr_loglevel = set_level(NEWDEVICE.name, name, value); continue; } if (!strcasecmp(name, "regular_run_statfile")) { strcpy2(NEWDEVICE.dev_rr_statfile, apply_modemname(device_name, ask_value(NEWDEVICE.name, name, value))); continue; } if (!strcasecmp(name, "regular_run_keep_open")) { if ((NEWDEVICE.dev_rr_keep_open = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "logfile")) { ask_value(NEWDEVICE.name, name, value); // 3.1.12: special filename: apply_modemname(device_name, value); strcpy2(NEWDEVICE.logfile, value); continue; } if (!strcasecmp(name, "loglevel")) { NEWDEVICE.loglevel = set_level(NEWDEVICE.name, name, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "messageids")) { NEWDEVICE.messageids = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "voicecall_vts_list")) { if ((NEWDEVICE.voicecall_vts_list = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "voicecall_ignore_modem_response")) { if ((NEWDEVICE.voicecall_ignore_modem_response = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "voicecall_hangup_ath")) { if ((NEWDEVICE.voicecall_hangup_ath = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "voicecall_vts_quotation_marks")) { if ((NEWDEVICE.voicecall_vts_quotation_marks = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "voicecall_cpas")) { if ((NEWDEVICE.voicecall_cpas = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "voicecall_clcc")) { if ((NEWDEVICE.voicecall_clcc = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "check_memory_method")) { NEWDEVICE.check_memory_method = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "cmgl_value")) { strcpy2(NEWDEVICE.cmgl_value, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "priviledged_numbers")) { ask_value(NEWDEVICE.name, name, value); // 3.1.16beta: Fix: Forget previous values (from [default] section): NEWDEVICE.priviledged_numbers[0] = 0; 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; } continue; } if (!strcasecmp(name, "read_timeout")) { NEWDEVICE.read_timeout = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "ms_purge_hours")) { NEWDEVICE.ms_purge_hours = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "ms_purge_minutes")) { NEWDEVICE.ms_purge_minutes = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "ms_purge_read")) { if ((NEWDEVICE.ms_purge_read = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "detect_message_routing")) { if ((NEWDEVICE.detect_message_routing = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "detect_unexpected_input")) { if ((NEWDEVICE.detect_unexpected_input = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "unexpected_input_is_trouble")) { if ((NEWDEVICE.unexpected_input_is_trouble = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "adminmessage_limit")) { NEWDEVICE.adminmessage_limit = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "adminmessage_count_clear")) { NEWDEVICE.adminmessage_count_clear = atoi(ask_value(NEWDEVICE.name, name, value)) * 60; continue; } if (!strcasecmp(name, "status_signal_quality")) { if ((NEWDEVICE.status_signal_quality = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "status_include_counters")) { if ((NEWDEVICE.status_include_counters = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "communication_delay")) { NEWDEVICE.communication_delay = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "hangup_incoming_call")) { if ((NEWDEVICE.hangup_incoming_call = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "max_continuous_sending")) { NEWDEVICE.max_continuous_sending = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "socket_connection_retries")) { NEWDEVICE.socket_connection_retries = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "socket_connection_errorsleeptime")) { NEWDEVICE.socket_connection_errorsleeptime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "socket_connection_alarm_after")) { NEWDEVICE.socket_connection_alarm_after = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "report_device_details")) { if ((NEWDEVICE.report_device_details = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "using_routed_status_report")) { if ((NEWDEVICE.using_routed_status_report = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "routed_status_report_cnma")) { if ((NEWDEVICE.routed_status_report_cnma = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "needs_wakeup_at")) { if ((NEWDEVICE.needs_wakeup_at = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "keep_messages")) { if ((NEWDEVICE.keep_messages = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "start")) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.startstring, sizeof(NEWDEVICE.startstring) - 2, value, name); strcat(NEWDEVICE.startstring, "\r"); continue; } if (!strcasecmp(name, "startsleeptime")) { NEWDEVICE.startsleeptime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "stop")) { ask_value(NEWDEVICE.name, name, value); copyvalue(NEWDEVICE.stopstring, sizeof(NEWDEVICE.stopstring) - 2, value, name); strcat(NEWDEVICE.stopstring, "\r"); continue; } if (!strcasecmp(name, "trust_spool")) { if ((NEWDEVICE.trust_spool = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "smsc_pdu")) { if ((NEWDEVICE.smsc_pdu = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "telnet_login")) { strcpy2(NEWDEVICE.telnet_login, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_login_prompt")) { strcpy2(NEWDEVICE.telnet_login_prompt, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_login_prompt_ignore")) { strcpy2(NEWDEVICE.telnet_login_prompt_ignore, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_password")) { strcpy2(NEWDEVICE.telnet_password, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_password_prompt")) { strcpy2(NEWDEVICE.telnet_password_prompt, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_cmd")) { strcpy2(NEWDEVICE.telnet_cmd, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_cmd_prompt")) { strcpy2(NEWDEVICE.telnet_cmd_prompt, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "telnet_crlf")) { if ((NEWDEVICE.telnet_crlf = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "wakeup_init")) { strcpy2(NEWDEVICE.wakeup_init, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "signal_quality_ber_ignore")) { if ((NEWDEVICE.signal_quality_ber_ignore = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "verify_pdu")) { if ((NEWDEVICE.verify_pdu = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "loglevel_lac_ci")) { ask_value(NEWDEVICE.name, name, value); NEWDEVICE.loglevel_lac_ci = set_level(NEWDEVICE.name, name, value); continue; } if (!strcasecmp(name, "log_not_registered_after")) { NEWDEVICE.log_not_registered_after = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "send_retries")) { NEWDEVICE.send_retries = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "report_read_timeouts")) { if ((NEWDEVICE.report_read_timeouts = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "select_pdu_mode")) { if ((NEWDEVICE.select_pdu_mode = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "ignore_unexpected_input")) { ask_value(NEWDEVICE.name, name, value); // "word " --> word // ""word "" --> "word " if (*value == '"') strcpyo(value, value + 1); if (*value && value[strlen(value) - 1] == '"') value[strlen(value) - 1] = 0; // If not empty, buffer is terminated with double-zero. if (*value) { p = NEWDEVICE.ignore_unexpected_input; while (*p) p = strchr(p, 0) + 1; if ((ssize_t) strlen(value) <= SIZE_IGNORE_UNEXPECTED_INPUT - 2 - (p - NEWDEVICE.ignore_unexpected_input)) { strcpy(p, value); *(p + strlen(value) + 1) = 0; } else startuperror("Not enough space for %s ignore_unexpected_input value: %s\n", NEWDEVICE.name, value); } continue; } if (!strcasecmp(name, "national_toa_unknown")) { if ((NEWDEVICE.national_toa_unknown = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "reply_path")) { if ((NEWDEVICE.reply_path = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "description")) { strcpy2(NEWDEVICE.description, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "text_is_pdu_key")) { strcpy2(NEWDEVICE.text_is_pdu_key, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "sentsleeptime")) { NEWDEVICE.sentsleeptime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "poll_faster")) { NEWDEVICE.poll_faster = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "read_delay")) { NEWDEVICE.read_delay = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES if (!strcasecmp(name, "language")) { ask_value(NEWDEVICE.name, name, value); if ((NEWDEVICE.language = parse_language_setting(value)) == -1) startuperror("Invalid language for %s: %s\n", NEWDEVICE.name, value); continue; } if (!strcasecmp(name, "language_ext")) { ask_value(NEWDEVICE.name, name, value); if ((NEWDEVICE.language_ext = parse_language_setting(value)) == -1) startuperror("Invalid language_ext for %s: %s\n", NEWDEVICE.name, value); continue; } #endif if (!strcasecmp(name, "notice_ucs2")) { NEWDEVICE.notice_ucs2 = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "receive_before_send")) { if ((NEWDEVICE.receive_before_send = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "delaytime")) { NEWDEVICE.delaytime = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "delaytime_random_start")) { if ((NEWDEVICE.delaytime_random_start = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "read_identity_after_suspend")) { if ((NEWDEVICE.read_identity_after_suspend = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "read_configuration_after_suspend")) { if ((NEWDEVICE.read_configuration_after_suspend = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strncmp(name, "read_timeout_", 13)) { char result[256]; ask_value(NEWDEVICE.name, name, value); if (!set_read_timeout(result, sizeof(result), name + 13, atoi(value))) startuperror("Modem %s: %s\n", NEWDEVICE.name, result); continue; } if (!strcasecmp(name, "check_sim")) { ask_value(NEWDEVICE.name, name, value); if (strncasecmp(value, "once", strlen(value)) == 0) NEWDEVICE.check_sim = 2; else if ((NEWDEVICE.check_sim = yesno_check(value)) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "check_sim_cmd")) { strcpy2(NEWDEVICE.check_sim_cmd, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "check_sim_keep_open")) { if ((NEWDEVICE.check_sim_keep_open = yesno_check(ask_value(NEWDEVICE.name, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "check_sim_reset")) { strcpy2(NEWDEVICE.check_sim_reset, ask_value(NEWDEVICE.name, name, value)); continue; } if (!strcasecmp(name, "check_sim_retries")) { ask_value(NEWDEVICE.name, name, value); if (strncasecmp(value, "forever", strlen(value)) == 0) NEWDEVICE.check_sim_retries = -1; else NEWDEVICE.check_sim_retries = atoi(value); continue; } if (!strcasecmp(name, "check_sim_wait")) { NEWDEVICE.check_sim_wait = atoi(ask_value(NEWDEVICE.name, name, value)); continue; } // 3.1.19beta: Show modem/section: if (!strcmp(NEWDEVICE.name, device_name)) startuperror("Unknown setting for modem %s: %s\n", NEWDEVICE.name, name); else startuperror("Unknown setting for modem %s in section %s: %s\n", device_name, NEWDEVICE.name, name); } } } // 3.1.19: Fix: 3.1.18 forgot to close file: fclose(File); } else return 0; return 1; #undef NEWDEVICE } int readcfg() { FILE *File; char devices_list[4096]; char name[64]; char value[4096]; char tmp[4096]; char device_name[32]; int result; int i, j, q, max; char *p; int 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")) { strcpy2(devices_list, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "spool")) { strcpy2(d_spool, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "outgoing")) { strcpy2(d_spool, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "stats")) { strcpy2(d_stats, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "suspend")) { strcpy2(suspend_filename, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "failed")) { strcpy2(d_failed, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "failed_copy")) { strcpy2(d_failed_copy, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "incoming")) { strcpy2(d_incoming, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "incoming_copy")) { strcpy2(d_incoming_copy, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "report")) { strcpy2(d_report, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "report_copy")) { strcpy2(d_report_copy, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "phonecalls")) { strcpy2(d_phonecalls, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "saved")) { strcpy2(d_saved, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "checked")) { strcpy2(d_checked, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "sent")) { strcpy2(d_sent, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "sent_copy")) { strcpy2(d_sent_copy, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "mypath")) { // Removed in > 3.0.1 because this is not used. Setting is accepted because of backward compatibility. continue; } if (!strcasecmp(name, "delaytime")) { delaytime = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "delaytime_mainprocess")) { delaytime_mainprocess = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "sleeptime_mainprocess")) { sleeptime_mainprocess = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "check_pid_interval")) { check_pid_interval = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "blocktime")) { blocktime = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "blockafter")) { blockafter = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "stats_interval")) { stats_interval = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "status_interval")) { status_interval = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "stats_no_zeroes")) { if ((stats_no_zeroes = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "errorsleeptime")) { errorsleeptime = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "eventhandler")) { strcpy2(eventhandler, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "checkhandler")) { strcpy2(checkhandler, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "alarmhandler")) { strcpy2(alarmhandler, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "blacklist")) { strcpy2(blacklist, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "whitelist")) { strcpy2(whitelist, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "logfile")) { strcpy2(logfile, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "loglevel")) { loglevel = set_level("global", name, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "log_unmodified")) { if ((log_unmodified = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "alarmlevel")) { alarmlevel = set_level("global", name, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "autosplit")) { autosplit = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "receive_before_send")) { if ((receive_before_send = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "store_received_pdu")) { store_received_pdu = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "store_sent_pdu")) { store_sent_pdu = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "validity")) { if ((validity_period = parse_validity(ask_value(0, name, value), -1)) == -1) startuperror("Invalid validity period: %s\n", value); continue; } if (!strcasecmp(name, "decode_unicode_text")) { if ((decode_unicode_text = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "internal_combine")) { if ((internal_combine = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "internal_combine_binary")) { if ((internal_combine_binary = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "keep_filename")) { if ((keep_filename = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "store_original_filename")) { if ((store_original_filename = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "date_filename")) { date_filename = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "regular_run")) { strcpy2(regular_run, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "regular_run_interval")) { if ((regular_run_interval = atoi(ask_value(0, name, value))) <= 0) startuperror("Invalid global regular_run_interval: %s\n", value); continue; } if (!strcasecmp(name, "admin_to")) { strcpy2(admin_to, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "filename_preview")) { filename_preview = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "incoming_utf8")) { if ((incoming_utf8 = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "outgoing_utf8")) { if ((outgoing_utf8 = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "log_charconv")) { if ((log_charconv = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "log_read_from_modem")) { if ((log_read_from_modem = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "log_single_lines")) { if ((log_single_lines = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "executable_check")) { if ((executable_check = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "keep_messages")) { if ((keep_messages = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "user")) { strcpy2(username, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "group")) { strcpy2(groupname, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "infofile")) { strcpy2(infofile, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "pidfile")) { strcpy2(pidfile, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "terminal")) { if ((terminal = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "os_cygwin")) { if ((os_cygwin = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "language_file")) { strcpy2(language_file, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "datetime")) { strcpy2(datetime_format, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "datetime_format")) { strcpy2(datetime_format, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "logtime_format")) { strcpy2(logtime_format, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "date_filename_format")) { strcpy2(date_filename_format, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "international_prefixes") || !strcasecmp(name, "national_prefixes")) { 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")) p = international_prefixes; else p = national_prefixes; sprintf(p, "%s%c", value, 0); while (*p) { if (*p == ',') *p = 0; p++; } continue; } if (!strcasecmp(name, "priviledged_numbers")) { 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; } continue; } if (!strcasecmp(name, "enable_smsd_debug")) { if ((enable_smsd_debug = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "ignore_exec_output")) { if ((ignore_exec_output = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } 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); continue; } if (!strcasecmp(name, "ic_purge_hours")) { ic_purge_hours = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "ic_purge_minutes")) { ic_purge_minutes = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "ic_purge_read")) { if ((ic_purge_read = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "ic_purge_interval")) { ic_purge_interval = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "shell")) { strcpy2(shell, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "adminmessage_device")) { strcpy2(adminmessage_device, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "smart_logging")) { if ((smart_logging = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "status_signal_quality")) { if ((status_signal_quality = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "status_include_counters")) { if ((status_include_counters = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "status_include_uptime")) { if ((status_include_uptime = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "trust_outgoing")) { if ((trust_outgoing = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "ignore_outgoing_priority")) { if ((ignore_outgoing_priority = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "spool_directory_order")) { if ((spool_directory_order = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "trim_text")) { if ((trim_text = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "hangup_incoming_call")) { if ((hangup_incoming_call = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "max_continuous_sending")) { max_continuous_sending = atoi(ask_value(0, name, value)); continue; } if (!strcasecmp(name, "voicecall_hangup_ath")) { if ((voicecall_hangup_ath = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "use_linux_ps_trick")) { if ((use_linux_ps_trick = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "logtime_us")) { if ((logtime_us = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "logtime_ms")) { if ((logtime_ms = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "shell_test")) { if ((shell_test = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "log_response_time")) { if ((log_response_time = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "log_read_timing")) { if ((log_read_timing = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "alphabet")) // 3.1.16beta2. Default alphabet for outgoing SMS body. { ask_value(0, name, value); if (!strncasecmp(value, "iso", 3) || !strncasecmp(value, "lat", 3) || !strncasecmp(value, "ans", 3)) default_alphabet = ALPHABET_ISO; else if (!strncasecmp(value, "utf", 3)) default_alphabet = ALPHABET_UTF8; else startuperror("Invalid value for alphabet: %s\n", value); continue; } if (!strcasecmp(name, "child")) { strcpy2(mainprocess_child, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "child_args")) { strcpy2(mainprocess_child_args, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "eventhandler_use_copy")) { if ((eventhandler_use_copy = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } if (!strcasecmp(name, "start")) { strcpy2(mainprocess_start, ask_value(0, name, value)); continue; } if (!strcasecmp(name, "start_args")) { strcpy2(mainprocess_start_args, ask_value(0, name, value)); continue; } #ifndef DISABLE_INOTIFY if (!strcasecmp(name, "notifier")) { if ((mainprocess_notifier = yesno_check(ask_value(0, name, value))) == -1) startuperror(yesno_error, name, value); continue; } #endif 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"); } } // 3.1.20: Read communicate section if communicating: if (*communicate && gotosection(File, "communicate")) { int key; while (my_getline(File, name, sizeof(name), value, sizeof(value)) == 1) { if (*name == 'a' && (key = atoi(name + 1)) >= 0 && key <= COMMUNICATE_A_KEY_COUNT) snprintf(communicate_a_keys[key], sizeof(communicate_a_keys[0]), "%s", value); } } /* 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); } fclose(File); // 3.1.19beta: Allow multiple wildcard definitions in devices setting: strcpy(tmp, devices_list); *devices_list = 0; j = 1; while (getsubparam(tmp, j++, device_name, sizeof(device_name))) { if ((p = strchr(device_name, '*')) && strchr(device_name, '-')) { *p = 0; i = atoi(p + 1); max = atoi(strstr(p + 1, "-") + 1); while (i <= max) { sprintf(value, "%s%i", device_name, i); if (strlen(devices_list) + strlen(device_name) + 1 >= sizeof(devices_list)) break; sprintf(strchr(devices_list, 0), "%s%s", (*devices_list) ? "," : "", value); i++; } } else if (strlen(devices_list) + strlen(device_name) + 1 < sizeof(devices_list)) sprintf(strchr(devices_list, 0), "%s%s", (*devices_list) ? "," : "", device_name); } // 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: %s\n", device_name); else if (!strcmp(device_name, "default")) startuperror("Device name cannot be \"default\"."); else if (!strcmp(device_name, "modemname")) startuperror("Device name cannot be \"modemname\"."); else if (!strcmp(device_name, "ALL")) startuperror("Device name cannot be \"ALL\"."); else if (!strcmp(device_name, "communicate")) startuperror("Device name cannot be \"communicate\"."); else { // 3.1.19beta: Check for duplicate names: int error = 0; for (i = 0; i < newdevice; i++) { if (!strcmp(devices[i].name, device_name)) { startuperror("Device name %s is used more than once.", device_name); error++; break; } } if (!error) readcfg_device(newdevice, device_name); } } else break; } } 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 { // 3.1.16beta: Show the path and the error: //fprintf(stderr,"Cannot open config file for read.\n"); fprintf(stderr, "Cannot open config file %s for read: %s\n", configfile, strerror(errno)); 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)++; } void startup_check_device(int *result, int x) { FILE *fp; char *p; int i, y; char tmp[PATH_MAX]; 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 && devices[x].outgoing) // 3.1.16beta: Queue is not required if outgoing is disabled. 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 ((i = is_executable(devices[x].eventhandler))) wrlogfile(result, "%s eventhandler %s %s", devices[x].name, devices[x].eventhandler, (i == 2)? msg_is_directory : msg_not_executable); } } 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 ((i = is_executable(devices[x].eventhandler_ussd))) wrlogfile(result, "%s eventhandler_ussd %s %s", devices[x].name, devices[x].eventhandler_ussd, (i == 2)? msg_is_directory : msg_not_executable); } } 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 ((i = is_executable(devices[x].dev_rr))) wrlogfile(result, "%s regular_run file %s %s", devices[x].name, devices[x].dev_rr, (i == 2)? msg_is_directory : msg_not_executable); } } 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 ((i = is_executable(devices[x].dev_rr_post_run))) wrlogfile(result, "%s regular_run_post_run file %s %s", devices[x].name, devices[x].dev_rr_post_run, (i == 2)? msg_is_directory : msg_not_executable); } } 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 (devices[x].notice_ucs2 < 0 || devices[x].notice_ucs2 > 2) wrlogfile(result, "Device %s has invalid value for notice_ucs2 (%i).", devices[x].name, devices[x].notice_ucs2); 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. 3.1.16beta: Network device can have. if (!DEVICE_X_IS_SOCKET) { 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); } } // 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); } void flush_startup_err_str(int *result) { char *p, *p2; 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; } } int startup_check(int result) { // result has initial value and total number of problems is returned. 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; flush_startup_err_str(&result); // 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; } if ((i = check_directory(d_incoming_copy)) > 1) wrlogfile(&result, (i == 2) ? msg_dir : msg_file, "Incoming copy", d_incoming_copy); else if (i == 0) remove_lockfiles(d_incoming_copy); if ((i = check_directory(d_report_copy)) > 1) wrlogfile(&result, (i == 2) ? msg_dir : msg_file, "Report copy", d_report_copy); else if (i == 0) remove_lockfiles(d_report_copy); if ((i = check_directory(d_failed_copy)) > 1) wrlogfile(&result, (i == 2) ? msg_dir : msg_file, "Failed copy", d_failed_copy); else if (i == 0) remove_lockfiles(d_failed_copy); if ((i = check_directory(d_sent_copy)) > 1) wrlogfile(&result, (i == 2) ? msg_dir : msg_file, "Sent copy", d_sent_copy); else if (i == 0) remove_lockfiles(d_sent_copy); 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 NUMS 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 ((i = is_executable(eventhandler))) wrlogfile(&result, "Eventhandler %s %s", eventhandler, (i == 2)? msg_is_directory : msg_not_executable); } } if (*checkhandler && executable_check) { if (!(fp = fopen(checkhandler, "r"))) wrlogfile(&result, "Checkhandler %s cannot be read: %s", checkhandler, strerror(errno)); else { fclose(fp); if ((i = is_executable(checkhandler))) wrlogfile(&result, "Checkhandler %s %s", checkhandler, (i == 2)? msg_is_directory : msg_not_executable); } } if (*alarmhandler && executable_check) { if (!(fp = fopen(alarmhandler, "r"))) wrlogfile(&result, "Alarmhandler %s cannot be read: %s", alarmhandler, strerror(errno)); else { fclose(fp); if ((i = is_executable(alarmhandler))) wrlogfile(&result, "Alarmhandler %s %s", alarmhandler, (i == 2)? msg_is_directory : msg_not_executable); } } 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 ((i = is_executable(regular_run))) wrlogfile(&result, "Regular run %s %s", regular_run, (i == 2)? msg_is_directory : msg_not_executable); } } if (*mainprocess_child && executable_check) { if (!(fp = fopen(mainprocess_child, "r"))) wrlogfile(&result, "Child %s cannot be read: %s", mainprocess_child, strerror(errno)); else { fclose(fp); if ((i = is_executable(mainprocess_child))) wrlogfile(&result, "Child %s %s", mainprocess_child, (i == 2)? msg_is_directory : msg_not_executable); } } if (*mainprocess_start && executable_check) { if (!(fp = fopen(mainprocess_start, "r"))) wrlogfile(&result, "Start script %s cannot be read: %s", mainprocess_start, strerror(errno)); else { fclose(fp); if ((i = is_executable(mainprocess_start))) wrlogfile(&result, "Start script %s %s", mainprocess_start, (i == 2)? msg_is_directory : msg_not_executable); } } for (x = 0; x < NUMBER_OF_MODEMS; x++) if (devices[x].name[0]) startup_check_device(&result, x); // 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++) // 3.1.21: Fix to prevent warning on compilation (FreeBSD): "devices[x].name" was always true, // but device which is not set, also does not have .logfile set. //if (devices[x].name && devices[x].logfile[0]) if (devices[x].name[0] && 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 ((i = is_executable(shell))) wrlogfile(&result, "Shell %s does not exist or %s", shell, (i == 2)? msg_is_directory : msg_not_executable); 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; // 3.1.16beta: Use tmpdir: //sprintf(tmp_data, "%s/smsd_data.XXXXXX", "/tmp"); sprintf(tmp_data, "%s/smsd_data.XXXXXX", tmpdir); 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: %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: %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:"); 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; } int refresh_configuration() { // returns number of errors int result = 0; char device_name[32]; strcpy(device_name, DEVICE.name); initcfg_device(process_id); strcpy(DEVICE.name, device_name); if (readcfg_device(process_id, device_name)) { flush_startup_err_str(&result); startup_check_device(&result, process_id); if (!result && !test_openmodem()) { wrlogfile(NULL, "Device setting wrong: %s", DEVICE.device); result++; } } else { wrlogfile(NULL, "Cannot read configuration: %s", configfile); result++; } return result; } smstools3/src/extras.c0000755000175000017500000013402713100201551013703 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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" FILE *fopen_mkstemp(char *fname) { mode_t mode; int fd; FILE *fp = 0; if ((fd = mkstemp(fname)) == -1) { writelogfile0(LOG_CRIT, 1, tb_sprintf("mkstemp failure: %s - %i, %s", fname, errno, strerror(errno))); alarm_handler0(LOG_CRIT, tb); } else { mode = umask(0); umask(mode); if (fchmod(fd, 0666 & ~mode)) { writelogfile0(LOG_CRIT, 1, tb_sprintf("fchmod failure: %s - %i, %s", fname, errno, strerror(errno))); alarm_handler0(LOG_CRIT, tb); } if (!(fp = fdopen(fd, "w"))) { writelogfile0(LOG_CRIT, 1, tb_sprintf("fdopen failure: %s - %i, %s", fname, errno, strerror(errno))); alarm_handler0(LOG_CRIT, tb); close(fd); unlink(fname); // This should not be required, but at least on one Cygwin environment fopen() right after // unlink() failed (permission denied) randomly, but rarely, and first retry helped: writelogfile(LOG_CRIT, 1, "Waiting 1 sec before retrying with %s", fname); usleep_until(time_usec() + 1000000); if (!(fp = fopen(fname, "w"))) { writelogfile(LOG_CRIT, 1, "2. fopen write failure: %s - %i: %s", fname, errno, strerror(errno)); writelogfile(LOG_CRIT, 1, "Waiting 5 sec before retrying with %s", fname); usleep_until(time_usec() + 5000000); if (!(fp = fopen(fname, "w"))) { writelogfile(LOG_CRIT, 1, "3. fopen write failure: %s - %i: %s", fname, errno, strerror(errno)); writelogfile0(LOG_CRIT, 1, tb_sprintf("Not retrying anymore with %s", fname)); alarm_handler0(LOG_CRIT, tb); } else { writelogfile0(LOG_CRIT, 1, tb_sprintf("2. retry helped with %s", fname)); alarm_handler0(LOG_CRIT, tb); } } else { writelogfile0(LOG_CRIT, 1, tb_sprintf("1. retry helped with %s", fname)); alarm_handler0(LOG_CRIT, tb); } } } return fp; } 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); } // 3.1.17: Using the same function to copy file. int movefile(char *filename, char *directory) { return copymovefile(0, filename, directory); } int copyfile(char *filename, char *directory) { return copymovefile(1, filename, directory); } int copymovefile(int copy, 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); if (!copy) 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. // 3.1.17: Using the same function to copy file. int movefilewithdestlock(char *filename, char *directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename) { return copymovefilewithdestlock(0, filename, directory, keep_fname, store_original_fname, prefix, newfilename); } int copyfilewithdestlock(char *filename, char *directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename) { return copymovefilewithdestlock(1, filename, directory, keep_fname, store_original_fname, prefix, newfilename); } int copymovefilewithdestlock(int copy, char *filename, char *directory, int keep_fname, int store_original_fname, char *prefix, char *newfilename) { if (newfilename) *newfilename = 0; if (keep_fname) { char filename2lock[PATH_MAX]; char* cp; //create filename2lock in destination cp=strrchr(filename,'/'); if (cp) sprintf(filename2lock,"%s%s",directory,cp); else sprintf(filename2lock,"%s/%s",directory,filename); //create lock and move file if (!lockfile(filename2lock)) // 3.1.16beta2: log details in the case of conflict: // return 1; { char details[128]; if (get_file_details(filename2lock, details, sizeof(details))) { writelogfile0(LOG_CRIT, 1, tb_sprintf("File already exists and it was locked: %s %s", filename2lock, details)); alarm_handler0(LOG_CRIT, tb); } return 1; } if (!copymovefile(copy, filename, directory)) { unlockfile(filename2lock); return 2; } if (!unlockfile(filename2lock)) return 3; if (newfilename) strcpy(newfilename, filename2lock); 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) { // 3.1.16beta: Log and retry in case of failures. This is from "VVV-3" special version. if (!(fpnew = fopen(newname, "w"))) { writelogfile(LOG_CRIT, 1, "1. fopen write failure: %s - %i: %s", newname, errno, strerror(errno)); writelogfile(LOG_CRIT, 1, "Waiting 1 sec before retrying with %s", newname); usleep_until(time_usec() + 1000000); if (!(fpnew = fopen(newname, "w"))) { writelogfile(LOG_CRIT, 1, "2. fopen write failure: %s - %i: %s", newname, errno, strerror(errno)); writelogfile(LOG_CRIT, 1, "Waiting 5 sec before retrying with %s", newname); usleep_until(time_usec() + 5000000); if (!(fpnew = fopen(newname, "w"))) { writelogfile(LOG_CRIT, 1, "3. fopen write failure: %s - %i: %s", newname, errno, strerror(errno)); writelogfile(LOG_CRIT, 1, "Not retrying anymore with %s", newname); result = 2; } else writelogfile(LOG_CRIT, 1, "2. retry helped with %s", newname); } else writelogfile(LOG_CRIT, 1, "1. retry helped with %s", newname); } if (!result) { 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 { if (!copy) if (!result) // 3.1.16beta2: Do not delete in case of errors. 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) { // 3.1.17: Use access() if not under cygwin. If using inotifywait with -e close_write, // testing with append causes extra events. They do not cause errors, but still use access. if (!os_cygwin) { if (access(filename, W_OK) == 0) result = 1; } else { 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[256]; // 3.1.16beta2: was [NAME_MAX + 1]; but some systems do not define NAME_MAX. 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.17: should use ent->d_name, tmpname contains full path: i = 1; if (strlen(ent->d_name) >= 5 && !strcmp(ent->d_name + strlen(ent->d_name) - 5, ".LOCK")) i = 0; else if (!strncmp(ent->d_name, "LOCKED", 6)) i = 0; if (!i) { locked_count++; continue; } // 3.1.16beta: Skip empty files: if (statbuf.st_size < 8) continue; // 3.1.17: Ignore hidden files starting with a dot: if (ent->d_name[0] == '.') 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]; // 3.1.16beta: Retry once after small delay: int access_ok = check_access(tmpname); if (!access_ok) { usleep_until(time_usec() + 250000); access_ok = check_access(tmpname); } if (!access_ok) //!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 // 3.1.16beta: Use tmpdir: //snprintf(tmp1, sizeof(tmp1), ">%s/smsd_%s_1.XXXXXX", "/tmp", info); snprintf(tmp1, sizeof(tmp1), ">%s/smsd_%s_1.XXXXXX", tmpdir, info); while ((p = strchr(tmp1, '('))) *p = '.'; while ((p = strchr(tmp1, ')'))) *p = '.'; while ((p = strchr(tmp1, ' '))) *p = '-'; // 3.1.16beta: Use tmpdir: //snprintf(tmp2, sizeof(tmp2), "2>%s/smsd_%s_2.XXXXXX", "/tmp", info); snprintf(tmp2, sizeof(tmp2), "2>%s/smsd_%s_2.XXXXXX", tmpdir, 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; // 3.1.21: Fixed time_t argument to int. writelogfile0(LOG_ERR, 0, tb_sprintf("Done: %s, execution time %i sec., errno: %i, %s", info, (int)(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; } // 3.1.21: Fixed time_t argument to int. writelogfile0(level, trouble, tb_sprintf("Done: %s, execution time %i sec., status: %i (%i)", info, (int)(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 executable = 0; struct stat statbuf; mode_t mode; int n, i; gid_t *g; // 3.1.20: Should also check if filename is a directory. It can be executable but it's not a script. // Return value now: 0 = ok, 1 = not exists, 2 = is a directory, 3 = not executable if (stat(filename, &statbuf) < 0) return 1; if (S_ISDIR(statbuf.st_mode)) return 2; 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) & (!executable); i++) if (g[i] == statbuf.st_gid) executable = 1; } free(g); } } if (executable) { if ((mode & 050) != 050) executable = 0; } else if ((mode & 05) == 05) executable = 1; } else if ((mode & 0500) == 0500) executable = 1; } else if ((mode & 0100) || (mode & 010) || (mode & 01)) executable = 1; return (executable)? 0 : 3; } 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; // 3.1.16beta, 3.1.17: Sleep more and less often to reduce CPU load (100 --> 10000 max). for (;;) { gettimeofday(&tv, &tz); now = (unsigned long long)tv.tv_sec *1000000 +tv.tv_usec; if (terminate == 1) return 1; if (now >= target_time) break; if (now +10000 < target_time) usleep(10000); else usleep(target_time -now); } 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 } // 3.1.16beta: int make_uptime_string(char *dest, size_t dest_size, time_t upt) { int result = 0; time_t day; char tmp[16]; day = 60 * 60 * 24; if (upt < day) { if (upt >= 60 * 60) { strftime(tmp, sizeof(tmp), "%H:%M", gmtime(&upt)); snprintf(dest, dest_size, "%s", (*tmp == '0')? tmp + 1 : tmp); } else snprintf(dest, dest_size, "%i min", (int)upt / 60); } else { int days; days = (int)upt / day; snprintf(dest, dest_size, "%i day%s, ", days, (days > 1)? "s" : ""); upt -= days * day; if (upt >= 60 * 60) { strftime(tmp, sizeof(tmp), "%H:%M", gmtime(&upt)); snprintf(dest + strlen(dest), dest_size - strlen(dest), "%s", (*tmp == '0')? tmp + 1 : tmp); } else snprintf(dest + strlen(dest), dest_size - strlen(dest), "%i min", (int)upt / 60); } return result; } // 3.1.16beta: int is_ok_answer(char *answer) { if (strstr(answer, "OK")) return 1; return 0; } int is_error_answer(char *answer) { if (strstr(answer, "ERROR")) return 1; return 0; } int is_ok_0_answer(char *answer) { if (is_ok_answer(answer) || strstr(answer, "0")) return 1; return 0; } int is_error_4_answer(char *answer) { if (is_error_answer(answer) || strstr(answer, "4")) return 1; return 0; } int is_ok_error_answer(char *answer) { if (is_ok_answer(answer) || is_error_answer(answer)) return 1; return 0; } int is_ok_error_0_4_answer(char *answer) { if (is_ok_0_answer(answer) || is_error_4_answer(answer)) return 1; return 0; } int get_file_details(char *filename, char *dest, size_t dest_size) { struct stat statbuf; if (dest) *dest = 0; if (stat(filename, &statbuf) == 0) { if (dest) { time_t t = statbuf.st_mtime; struct tm *timeinfo; char timestamp[81]; timeinfo = localtime(&t); strftime(timestamp, sizeof(timestamp), datetime_format, timeinfo); snprintf(dest, dest_size, "%s 0%o/%c%c%c%c%c%c%c%c%c%c %u %u:%u %u", timestamp, 0777 & statbuf.st_mode, (S_ISDIR(statbuf.st_mode))? 'd' : '-', (statbuf.st_mode & S_IRUSR)? 'r' : '-', (statbuf.st_mode & S_IWUSR)? 'w' : '-', (statbuf.st_mode & S_IXUSR)? 'x' : '-', (statbuf.st_mode & S_IRGRP)? 'r' : '-', (statbuf.st_mode & S_IWGRP)? 'w' : '-', (statbuf.st_mode & S_IXGRP)? 'x' : '-', (statbuf.st_mode & S_IROTH)? 'r' : '-', (statbuf.st_mode & S_IWOTH)? 'w' : '-', (statbuf.st_mode & S_IXOTH)? 'x' : '-', (int)statbuf.st_ino, statbuf.st_uid, statbuf.st_gid, (int)statbuf.st_size); } return 1; } return 0; } // 3.1.16beta2: int calculate_required_parts(char *text, int textlen, int *reserved, int split, int *use_get_part) { int result = 0; int chars = 0; // text is in GSM alphabet. // If more than one part is required, should not split right after esc character. if (use_get_part) *use_get_part = 0; if (textlen <= maxsms_pdu - *reserved) return 1; if (split == 3) { if (*reserved == 0) *reserved = 7; else if (*reserved == 8) *reserved = 14; else *reserved = 11; } while (chars < textlen) { chars += maxsms_pdu - *reserved; if (chars <= textlen && text[chars -1] == 0x1B) chars--; result++; } if (result > 1 && use_get_part) *use_get_part = 1; return result; } int get_part(char **part_start, char *text, int textlen, int reserved, int part) { int length; char *start = text; while (textlen > 0) { length = (textlen > maxsms_pdu -reserved)? maxsms_pdu -reserved : textlen; if (length > 1 && start[length -1] == 0x1B) length--; textlen -= length; if (!part) { if (part_start) *part_start = start; return length; } part--; start += length; } return 0; } int spend_delay(int delaytime, int (*cb)(time_t *), time_t *cb_last, int cb_interval) { if (!terminate && !break_workless_delay && delaytime > 0) { time_t targettime = time(0) + delaytime; time_t target_cb; time_t time_to_sleep; if (*cb_last > 0 && cb_interval > 0 && *cb_last + cb_interval - time(0) > 0) { while (!terminate && !break_workless_delay && (target_cb = *cb_last + cb_interval) <= targettime) { while (!terminate && !break_workless_delay && (time_to_sleep = target_cb - time(0)) > 0) sleep(time_to_sleep); if (!terminate) if (!cb(cb_last)) return 0; } } while (!terminate && !break_workless_delay && (time_to_sleep = targettime - time(0)) > 0) sleep(time_to_sleep); if (!terminate) if (!cb(cb_last)) return 0; } return 1; } smstools3/src/blacklist.h0000755000175000017500000000121513046432777014373 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 smstools3/src/pdu.c0000755000175000017500000017422613067461363013217 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 (reject_duplicates) flags += 4; if (reply_path) flags += 128; if (alphabet == ALPHABET_BINARY) coding = 4; // 8bit binary else if (alphabet == ALPHABET_UCS2) 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) else { // 3.1.16beta2. Invalid values for class are just ignored. // Note: system_msg will override "Alphabet", "Flash" and "Class" settings. switch (sms_class) { case 0: // Immediate display (alert) coding += 0x10; break; case 1: // ME specific coding += 0x11; break; case 2: // SIM specific coding += 0x12; break; case 3: // TE specific coding += 0x13; break; } } // 3.1.16beta2: tp_dcs overrides all but system_msg: if (tp_dcs >= 0 && tp_dcs <= 0xFF) coding = tp_dcs; /* Create the PDU string of the message */ if (alphabet == ALPHABET_BINARY || alphabet == ALPHABET_UCS2 || 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; else if (ping) proto = 0x40; // 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"); if (message_reference < 0 || message_reference > 255) message_reference = 0; sprintf(strchr(pdu, 0), "%02X%02X%02X%02X%s%02X%02X%02X%02X", flags, message_reference, 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; case 0x24: p = "Single Shift Character Set information"; break; case 0x25: p = "Locking Shift Character Set information"; break; } if (!p) { if (i >= 0x1B && i <= 0x1F) p = "Reserved for future EMS features"; else if (i >= 0x26 && 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)) { char buffer[128]; // 3.1.20: If custom datetime_format is defined, use it for Discharge_timestamp: temp[8] = '\0'; make_datetime_string(buffer, sizeof(buffer), temp, temp +9, 0); sprintf(strchr(result, 0), "Discharge_timestamp: %s", buffer); 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 { 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); ucs2utf(ascii, message_length, sizeof(ascii)); i = iso_utf8chars(ascii); 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); } int read_pdu_text(char *pdu, size_t pdu_size, char *text) { int result = 1; size_t src = 0; size_t dst = 0; if (!strncasecmp(pdu, "PDU:", 4)) src = 4; while (text[src]) { if (isXdigit(text[src])) { if (dst >= pdu_size -1) { result = 0; break; } pdu[dst++] = text[src]; } src++; } pdu[dst] = '\0'; return result; } int get_pdu_submit_to(char *to, size_t to_size, char *pdu) { int result = 0; char *p = pdu; char *end = pdu + strlen(pdu); int len; char phone[SIZE_TO]; int i; if ((len = octet2bin_check(p)) >= 0) { for (p += 2; len > 0; len--) p += 2; if (p < end && (i = octet2bin_check(p)) > 0 && (i & 0x03) == 0x01) { if ((p += 4) < end && (len = octet2bin_check(p)) >= 2 && (p += 4) < end) { if (len % 2) len++; if (len < (int)sizeof(phone)) { for (i = 0; i < len; i++) phone[i] = p[i]; phone[i] = '\0'; swapchars(phone); if (phone[--i] == 'F') phone[i] = '\0'; if (strlen(phone) < to_size) { strcpy(to, phone); result = 1; } } } } } return result; } smstools3/src/cfgfile.c0000755000175000017500000000661313046432777014024 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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; } smstools3/src/stats.c0000755000175000017500000003007113055057127013546 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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]; size_t size; // 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.18: Fix: Each allocation needs 16 bytes more space. If number of modems was set // to 124 or more, there was not enough shared memory available. //if (MM_create(NUMBER_OF_MODEMS *sizeof(_stats) +SIZE_SHARED_BUFFER, filename) == 0) size = NUMBER_OF_MODEMS * (sizeof(_stats) + 16) + SIZE_SHARED_BUFFER + 16; if (MM_create(size, 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() { #ifndef NOSTATS 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); } } #endif } void checkwritestats() { #ifndef NOSTATS 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); } } } #endif } // 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); if (status_include_uptime) if (longest_modemname < 5) // "Start" longest_modemname = 5; } 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"); } // 3.1.16beta: if (status_include_uptime) { char upstr[64]; strftime(buffer, sizeof(buffer), datetime_format, localtime(&process_start_time)); make_uptime_string(upstr, sizeof(upstr), t - process_start_time); fprintf(fp, "\nStart:%s\t%s,\tup %s\n", (longest_modemname >= 7)? "\t" : "", buffer, upstr); } fclose(fp); sprintf(fname, "%s/status", d_stats); rename(fname_tmp, fname); } } } #endif } smstools3/src/modeminit.c0000755000175000017500000023321613102367460014400 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 // 3.1.16beta: typedef struct { char *keyword; int value; char *comment; } _read_timeout; _read_timeout read_timeouts[] = { {"askpin", 2, "Asking if PIN is required."}, {"atd", 24, "Initiating a voice call."}, {"ath", 1, "Ending a voice call using ATH or AT+CHUP."}, {"clcc", 24, "Listing current calls during a voice call."}, {"cmd", 1, "Sending regular_run command to modem."}, {"cmgd", 1, "Deleting a message, checking messages using +CMGD."}, {"cmgf", 1, "Selecting a PDU mode."}, {"cmgl", 12, "Listing messages."}, {"cmgr", 1, "Reading message."}, {"cmgs", 2, "Initiating a sending."}, {"cpas", 24, "Checking activity during a voice call."}, {"cpbr", 1, "Reading phonebook entry, check limits."}, {"cpbw", 1, "Writing (deleting) a phonebook entry."}, {"cpms", 1, "Selecting preferred memory, checking size."}, {"creg", 2, "Reading registration report."}, {"csca", 1, "Changing SMSC."}, {"csq", 2, "Reading signal quality."}, {"cusd", 3, "Sending USSD string."}, {"default", 1, "Default for basic commands: AT, ATE0, etc."}, {"enterpin", 6, "Entering a PIN."}, {"init", 2, "Sending init or init2 string."}, {"pdu", 12, "Sending a PDU."}, {"preinit", 2, "Sending pre_init string."}, {"start", 2, "Sending start string."}, {"stop", 2, "Sending stop string."} }; // ------- 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"}, {48, "PH-SIM PUK required"}, {100, "Unknown"}, {103, "Illegal MS"}, {106, "Illegal ME"}, {107, "GPRS services not allowed"}, {111, "PLMN not allowed"}, {112, "Location area not allowed"}, {113, "Roaming not allowed in this location area"}, {126, "Operation temporary not allowed"}, {132, "Service operation not supported"}, {133, "Requested service option not subscribed"}, {134, "Service option temporary out of order"}, {148, "Unspecified GPRS error"}, {149, "PDP authentication failure"}, {150, "Invalid mobile class"}, {256, "Operation temporarily not allowed"}, {257, "Call barred"}, {258, "Phone is busy"}, {259, "User abort"}, {260, "Invalid dial string"}, {261, "SS not executed"}, {262, "SIM Blocked"}, {263, "Invalid block"}, {515, "Device busy"}, {772, "SIM powered down"} }; _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 ""; } int get_read_timeout(char *keyword) { int i; int m = sizeof read_timeouts / sizeof *read_timeouts; int result = 1; if (!keyword) return 0; for (i = 0; i < m; i++) if (!strcmp(keyword, read_timeouts[i].keyword)) result = read_timeouts[i].value; // Accept direct value: if ((i = atoi(keyword)) > 0) result = i; if (log_read_timing) writelogfile(LOG_DEBUG, 0, "read_timeout for %s is %i sec", keyword, DEVICE.read_timeout * result); return result; } int set_read_timeout(char *error, int size_error, char *keyword, int value) { int result = 0; int i; int m = sizeof read_timeouts / sizeof *read_timeouts; *error = 0; for (i = 0; i < m; i++) if (!strcmp(keyword, read_timeouts[i].keyword)) break; if (i < m) { if (value < 1) snprintf(error, size_error, "Cannot set read_timeout_%s to less than 1.", keyword); else { read_timeouts[i].value = value; result = 1; } } else snprintf(error, size_error, "Unknown setting read_timeout_%s.", keyword); return result; } void log_read_timeouts(int level) { int i; int m = sizeof read_timeouts / sizeof *read_timeouts; writelogfile(level, 0, "Using read_timeout %i seconds.", DEVICE.read_timeout); for (i = 0; i < m; i++) writelogfile(level, 0, "Using read_timeout_%s %i * read_timeout = %i seconds. %s", read_timeouts[i].keyword, read_timeouts[i].value, read_timeouts[i].value * DEVICE.read_timeout, read_timeouts[i].comment); } 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; } // 3.1.16beta: Do not log "device busy": //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 idx +2 && strlen(commands) < sizeof(commands) - 6) sprintf(strchr(commands, 0), "%s%i (0x%02X)", (*commands)? ", " : "", (int)answer[i], answer[i]); } } if (*response) if ((size_t)write(modem_handle, response, strlen(response)) != strlen(response)) writelogfile(LOG_ERR, 1, "%s: Failed to send response.", title); for (i = idx; i +count < *len; i++) answer[i] = answer[i +count]; *len -= count; } else idx++; } answer[*len] = 0; // 3.1.16beta: Now remove 0x00's from the answer: if (strlen(answer) < (size_t)*len) { int i, j, l; for (i = 0, j = 0, l = *len; i < *len; i++) { if (answer[i] == '\0') { l--; continue; } if (i > j) answer[j] = answer[i]; j++; } answer[l] = 0; *len = l; } // 3.1.16beta: If login/password or cmd is sent, remove the prompt. char *p; int l = 0; char *info = 0; *response = 0; if (DEVICE.telnet_login_prompt[0] && (p = strstr(answer, DEVICE.telnet_login_prompt)) && DEVICE.telnet_login[0]) { if (DEVICE.telnet_login_prompt_ignore[0] == 0 || !strstr(answer, DEVICE.telnet_login_prompt_ignore)) { snprintf(response, sizeof(response), "%s%s", DEVICE.telnet_login, eol); l = strlen(DEVICE.telnet_login_prompt); info = "login"; } } else if (DEVICE.telnet_password_prompt[0] && (p = strstr(answer, DEVICE.telnet_password_prompt)) && DEVICE.telnet_password[0]) { snprintf(response, sizeof(response), "%s%s", DEVICE.telnet_password, eol); l = strlen(DEVICE.telnet_password_prompt); info = "password"; } else if (DEVICE.telnet_cmd_prompt[0] && (p = strstr(answer, DEVICE.telnet_cmd_prompt)) && DEVICE.telnet_cmd[0]) { snprintf(response, sizeof(response), "%s%s", DEVICE.telnet_cmd, eol); l = strlen(DEVICE.telnet_cmd_prompt); info = DEVICE.telnet_cmd; } if (l) { for (i = (int)(p -answer) +l; i <= *len; i++) answer[i -l] = answer[i]; *len -= l; } if (*response) { if ((size_t)write(modem_handle, response, strlen(response)) != strlen(response)) writelogfile(LOG_ERR, 1, "%s: Failed to send response: %s", title, info); else writelogfile(LOG_DEBUG, 0, "%s: Sent %s", title, info); } #undef IAC #undef DONT #undef DO #undef WONT #undef WILL #undef SB #undef SE } int read_from_modem(char *answer, int max, int timeout) { return read_from_modem0(answer, max, timeout, 0, 0); } int read_from_modem0(char *answer, int max, int timeout, regex_t *re, char *expect) { int count=0; int got=0; int timeoutcounter=0; int success=0; int toread=0; // Cygwin does not support TIOC functions, so we cannot use it. // ioctl(modem,FIONREAD,&available); // how many bytes are available to read? // 3.1.16beta: Cannot use strlen(answer) in the loop when 0x00's are not removed here. int answer_length = strlen(answer); // 3.1.16beta2: If regular expression is defined, poll faster and stop as soon as regex matches. char *check_answer = NULL; int i; int sleep_time = 100000; int poll_faster = DEVICE.poll_faster; if (re && poll_faster > 0) { check_answer = (char *)malloc(max); timeout *= poll_faster; sleep_time /= poll_faster; } if (log_read_timing) writelogfile(LOG_DEBUG, 0, "read_from_modem, %i, '%s', %s", timeout, answer, (expect)? expect : ""); do { if (check_answer && success) { *check_answer = '\0'; for (i = 0; i < answer_length; i++) check_answer[i] = (answer[i] == '\0')? '\n' : answer[i]; check_answer[i] = '\0'; if (log_read_timing) writelogfile(LOG_DEBUG, 0, "answer now: %s", check_answer); if (regexec(re, check_answer, (size_t) 0, NULL, 0) == 0) { if (log_read_timing) writelogfile(LOG_DEBUG, 0, "answer is what expected, go ahead"); break; } } // How many bytes do I want to read maximum? Not more than buffer size -1 for termination character. count = answer_length; //count=strlen(answer); toread=max-count-1; if (toread<=0) break; // read data got = read(modem_handle, answer +count, toread); if (log_read_timing) writelogfile(LOG_DEBUG, 0, "read, got %i", got); // if nothing received ... if (got<=0) { // wait a litte bit and then repeat this loop got=0; // 3.1.16beta: Do not sleep if the loop will not continue: timeoutcounter++; if (timeoutcounter < timeout) { if (log_read_timing) writelogfile(LOG_DEBUG, 0, "sleeping %i us (%i)", sleep_time, timeoutcounter); usleep_until(time_usec() + sleep_time /*100000*/); } } else { if (log_read_from_modem) { char tmp[SIZE_LOG_LINE]; int i; snprintf(tmp, sizeof(tmp), "read_from_modem: count=%i, got=%i:", count, got); for (i = count; i < count + got; i++) { if (strlen(tmp) >= 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] : '.'); } writelogfile0(LOG_CRIT, 0, tmp); } // restart timout counter timeoutcounter=0; // append a string termination character answer[count+got]=0; answer_length = count +got; success=1; // 3.1.12: With Multitech network modem (telnet) there can be 0x00 inside the string: // 3.1.16beta: Clean the string after telnet commands are handled, moved to negotiate_with_telnet. //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); } free(check_answer); 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); // 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; } int ignore_unexpected_input(char *answer) { int result = 0; char *p; if (DEVICE.ignore_unexpected_input[0]) { p = DEVICE.ignore_unexpected_input; while (*p && !result) { if (strstr(answer, p)) result = 1; p = strchr(p, 0) + 1; } } 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, char *timeout_count, char *expect) { return put_command0(command, answer, max, timeout_count, expect, 0); } int put_command0(char *command, char *answer, int max, char *timeout_count, char *expect, int silent) { char loganswer[SIZE_LOG_LINE]; time_t start_time; // 3.1.16beta2: Changed timeout counter to use time(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; // 3.1.16beta: unsigned long long got_answer; unsigned long long write_started; if (DEVICE.communication_delay > 0) if (last_command_ended) usleep_until(last_command_ended +DEVICE.communication_delay *1000); // 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) { if (log_read_timing) writelogfile(LOG_DEBUG, 0, "Detecting%s%s", (DEVICE.incoming && DEVICE.detect_message_routing)? " message_routing" : "", (DEVICE.detect_unexpected_input)? " unexpected_input" : ""); *loganswer = 0; do { i = strlen(loganswer); read_from_modem(loganswer, sizeof(loganswer), 2); } 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. if (!ignore_unexpected_input(loganswer)) // 3.1.16beta2. 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); // 3.1.16beta: write_started = time_usec(); // 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 { // 3.1.16beta: put_command_sent = time_usec(); // read_timeout is in seconds. if (!timeout_count) timeout = 0; else timeout = DEVICE.read_timeout * get_read_timeout(timeout_count); if (!silent) { char tmp[64] = {0}; if (log_response_time) snprintf(tmp, sizeof(tmp), "time %i ms ", (int)((put_command_sent - write_started) / 1000)); writelogfile(LOG_DEBUG, 0, "%sCommand is sent, waiting for the answer. (%i)", tmp, timeout); } // 3.1.16beta2: Give some time to modem, before start reading (read_delay). if (DEVICE.read_delay > 0) { if (log_read_timing) writelogfile(LOG_DEBUG, 0, "Spending read_delay (%i ms)", DEVICE.read_delay); usleep_until(time_usec() + DEVICE.read_delay * 1000); } // wait for the modem-answer answer[0] = 0; got_timeout = 1; last_length = 0; start_time = time(0); do { if (regex_allocated) read_from_modem0(answer, max, 2, &re, expect); else read_from_modem(answer, max, 2); // 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: i = strlen(answer); if (i != last_length) { last_length = i; start_time = time(0); } } // repeat until timeout while (time(0) - start_time < timeout); // 3.1.16beta: got_answer = time_usec(); if (got_timeout) { put_command_timeouts++; // 3.1.16beta: //if (expect && expect[0]) // writelogfile(LOG_DEBUG, 1, "put_command expected %s, timeout occurred. %i.", expect, put_command_timeouts); if (expect && expect[0]) { if (!terminate) writelogfile(LOG_DEBUG, 1, "%s answer, put_command expected %s, timeout occurred. %i.", (*answer)? "Incorrect" : "No", expect, put_command_timeouts); else writelogfile(LOG_DEBUG, 1, "%s answer, put_command expected %s, process is terminating. %i.", (*answer)? "Incorrect" : "No", 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, ' '); } // 3.1.16beta: //writelogfile(LOG_DEBUG, 0, "<- %s", loganswer); if (log_response_time) { char tmp[64]; snprintf(tmp, sizeof(tmp), "time %i ms ", (int)((got_answer - put_command_sent) / 1000)); writelogfile(LOG_DEBUG, 0, "%s<- %s", tmp, loganswer); } else 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; 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.16beta: If wakeup_init is defined, send it to the modem: if (DEVICE.wakeup_init[0]) { writelogfile(LOG_INFO, 0, "Sending wakeup_init (%s)", DEVICE.wakeup_init); snprintf(command, sizeof(command), "%s%s", DEVICE.wakeup_init, (DEVICE_IS_SOCKET && DEVICE.telnet_crlf)? "\r\n" : "\n"); put_command(command, 0, 0, "default", 0); usleep_until(time_usec() + 100000); read_from_modem(answer, sizeof(answer), 2); } // 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, "default", 0); usleep_until(time_usec() + 100000); read_from_modem(answer, sizeof(answer), 2); } if (terminate) return 7; // 3.1.16beta: Allow numeric result codes at this stage. retries=0; do { flush_smart_logging(); retries++; //put_command("AT\r", answer, sizeof(answer), 1, EXPECT_OK_ERROR); put_command("AT\r", answer, sizeof(answer), "default", EXPECT_OK_ERROR_0_4); //if (!strstr(answer, "OK") && !strstr(answer, "ERROR")) if (!is_ok_error_0_4_answer(answer)) { 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); put_command("\x1A\r", answer, sizeof(answer), "default", EXPECT_OK_ERROR_0_4); 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")) if (retries >= 5 && !is_ok_0_answer(answer)) { int save_timeouts; int opened; // 3.1.16beta: Keep the value of timeouts: save_timeouts = put_command_timeouts; try_closemodem(1); t_sleep(1); // If open fails, nothing can be done. Error is already logged. Will return 1. //if (!try_openmodem()) // break; opened = try_openmodem(); put_command_timeouts = save_timeouts; if (!opened) break; } } //while (retries <= 10 && !strstr(answer,"OK")); while (retries <= 10 && !is_ok_0_answer(answer)); //if (!strstr(answer,"OK")) if (!is_ok_0_answer(answer)) { // 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; } // 3.1.16beta: Switch modem to use verbose mode if necessary: if (terminate) return 7; if (!is_ok_answer(answer)) { put_command("ATV1\r", answer, sizeof(answer), "default", EXPECT_OK_ERROR); if (is_ok_answer(answer)) writelogfile(LOG_NOTICE, 0, "Switched modem to use verbose result codes"); else writelogfile(LOG_ERR, 1, "Failed to switch modem to use verbose result codes"); } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; if (DEVICE.check_sim) // 3.1.21 { char reset_cmd[sizeof(DEVICE.check_sim_reset)]; int sim_ok = 0; char save_status = STATISTICS->status; writelogfile(LOG_INFO, 0, "Checking the SIM"); sprintf(command, "%s\r", (DEVICE.check_sim_cmd[0])? DEVICE.check_sim_cmd : "AT+CPIN?"); if (!strcasecmp(DEVICE.check_sim_reset, "RADIO_OFF_ON")) strcpy(reset_cmd, "AT+CFUN=0;+CFUN=1"); else if (!strcasecmp(DEVICE.check_sim_reset, "RADIO_OFF_ON_SLOW")) strcpy(reset_cmd, "AT+CFUN=0[3]AT+CFUN=1[5]"); else strcpy(reset_cmd, DEVICE.check_sim_reset); retries = 0; while (DEVICE.check_sim_retries < 0 || retries <= DEVICE.check_sim_retries) { if (try_openmodem()) { if (retries > 0 && *reset_cmd) { char cmd[sizeof(command)]; char *p1, *p2; writelogfile(LOG_INFO, 0, "Trying to reset the modem"); p1 = reset_cmd; while (*p1) { if (*p1 == '[') { if ((p2 = strchr(p1, ']'))) { writelogfile(LOG_DEBUG, 0, "Sleeping %i sec", atoi(p1 + 1)); if (t_sleep(atoi(p1 + 1))) return 7; p1 = p2 + 1; continue; } else break; } if ((p2 = strchr(p1, '['))) { memset(cmd, 0, sizeof(cmd)); strncpy(cmd, p1, (int)(p2 - p1)); } else strcpy(cmd, p1); p1 += strlen(cmd); strcat(cmd, "\r"); put_command(cmd, answer, sizeof(answer), "init", EXPECT_OK_ERROR); } } put_command(command, answer, sizeof(answer), "init", EXPECT_OK_ERROR); if (!strstr(answer, "ERROR")) { sim_ok = 1; break; } } retries++; STATISTICS->status = 't'; if (modem_handle >= 0) tb_sprintf("Check SIM returned ERROR, try: %i", retries); else tb_sprintf("Check SIM could not open the modem, try: %i", retries); writelogfile0(LOG_ERR, 1, tb); alarm_handler0(LOG_ERR, tb); if (DEVICE.check_sim_retries < 0 || retries <= DEVICE.check_sim_retries) { if (DEVICE.check_sim_keep_open == 0) try_closemodem(1); writelogfile(LOG_DEBUG, 0, "Sleeping %i sec before retrying", DEVICE.check_sim_wait); if (t_sleep(DEVICE.check_sim_wait)) return 7; } } if (!sim_ok) { writelogfile0(LOG_ERR, 1, tb_sprintf("Check SIM failed and not retrying anymore")); alarm_handler0(LOG_ERR, tb); abnormal_termination(0); } STATISTICS->status = save_status; if (DEVICE.check_sim == 2) DEVICE.check_sim = 0; } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 3.1.21: Originally pre_init was containing "echo off" and "CMEE=1". If pre_init // is set to no, CLIP=1 and/or CREG=2 are still sent if required. if (DEVICE.pre_init > 0 || DEVICE.phonecalls == 2 || get_loglevel() >= DEVICE.loglevel_lac_ci) { // 3.1.21: Do not concatenate ATE0 and extended commands in pre_initialization. writelogfile(LOG_INFO, 0, "Pre-initializing modem"); if (DEVICE.pre_init > 0) put_command("ATE0\r", answer, sizeof(answer), "preinit", EXPECT_OK_ERROR); *command = 0; if (DEVICE.pre_init > 0) strcpy(command, "AT+CMEE=1"); if (DEVICE.phonecalls == 2) sprintf(strchr(command, 0), "%s+CLIP=1", (*command)? ";" : "AT"); if (get_loglevel() >= DEVICE.loglevel_lac_ci) sprintf(strchr(command, 0), "%s+CREG=2", (*command)? ";" : "AT"); strcat(command, "\r"); put_command(command, answer, sizeof(answer), "preinit", EXPECT_OK_ERROR); if (!strstr(answer,"OK")) writelogfile(LOG_ERR, 1, "Modem did not accept the pre-init string"); } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 3.1.1: //if (pin[0]) if (strcasecmp(DEVICE.pin, "ignore")) { // 3.1.20: Accept OK answer for PIN: //char *cpin_expect = "(READY)|( PIN)|( PUK)|(ERROR)"; // Previously: "(\\+CPIN:.*OK)|(ERROR)" char *cpin_expect = "(OK)|(READY)|( PIN)|( PUK)|(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), "askpin", 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), "enterpin", 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), "askpin", 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); } // 3.1.20: Accept OK answer for PIN: if (strstr(answer,"+CPIN: READY") || strstr(answer, "OK")) 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); } // 3.1.20: Accept OK answer for PIN: if (!strstr(answer, "+CPIN: READY") && !strstr(answer, "OK")) { p = get_gsm_error(answer); writelogfile0(LOG_CRIT, 1, tb_sprintf("PIN handling: expected READY or OK, 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 (terminate) return 7; 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), "init", EXPECT_OK_ERROR); if (!strstr(answer, "OK")) 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), "init", 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 (terminate) return 7; 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), "csq", EXPECT_OK_ERROR); if (!strstr(answer, "OK")) 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; } } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 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; } } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; if (DEVICE.select_pdu_mode) // 3.1.16beta2. { writelogfile(LOG_INFO, 0, "Selecting PDU mode"); strcpy(command,"AT+CMGF=0\r"); retries=0; do { retries++; put_command(command, answer, sizeof(answer), "cmgf", EXPECT_OK_ERROR); if (!strstr(answer, "OK")) 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 (terminate) return 7; 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), "csca", EXPECT_OK_ERROR); if (!strstr(answer, "OK")) 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; } } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 3.1.16beta: Ask IMEI once. if (DEVICE.imei[0] == 0) { sprintf(command,"AT+CGSN\r"); put_command(command, answer, sizeof(answer), "default", 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); snprintf(DEVICE.imei, sizeof(DEVICE.imei), "%s", answer); writelogfile(LOG_NOTICE, 0, "IMEI: %s", DEVICE.imei); } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 3.1beta7, 3.0.9: International Mobile Subscriber Identity is asked once. if (DEVICE.identity[0] == 0) { sprintf(command,"AT+CIMI\r"); put_command(command, DEVICE.identity, SIZE_IDENTITY, "default", 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")) strcpy(DEVICE.identity, DEVICE.imei); 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"); } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 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), "cpms", EXPECT_OK_ERROR); if (strstr(answer, "+CPMS: ,,,,,,,,")) { sprintf(command,"AT+CPMS=?\r"); put_command(command, answer, sizeof(answer), "cpms", 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); } } } // ----------------------------------------------------------------------------------------------- if (terminate) return 7; // 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]); writelogfile0(LOG_DEBUG, 0, tmp); sprintf(command, "%s\r", commands[i]); put_command0(command, answer, sizeof(answer), "default", 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. if (terminate) return 7; return 0; } int initialize_modem_sending(char *new_smsc) { // 3.1.17: Check if modem is disabled here, and log it: if (DEVICE.modem_disabled) { writelogfile(LOG_DEBUG, 0, "initialize_modem_sending"); return 0; } return initmodem(new_smsc, 0); } int initialize_modem_receiving() { // 3.1.17: Check if modem is disabled here, and log it: if (DEVICE.modem_disabled) { writelogfile(LOG_DEBUG, 0, "initialize_modem_receiving"); return 0; } 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) writelogfile0(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 writelogfile0(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 test_openmodem() { int result = 1; int save_device_open_retries = DEVICE.device_open_retries; int save_socket_connection_retries = DEVICE.socket_connection_retries; DEVICE.device_open_retries = 0; DEVICE.socket_connection_retries = 0; try_closemodem(1); if (openmodem() < 0) result = 0; try_closemodem(1); DEVICE.device_open_retries = save_device_open_retries; DEVICE.socket_connection_retries = save_socket_connection_retries; return result; } void show_a_commands() { int i; for (i = 0; i < COMMUNICATE_A_KEY_COUNT; i++) { if (communicate_a_keys[i][0]) { printf("Alt shortcuts:\n"); while (i < COMMUNICATE_A_KEY_COUNT) { if (communicate_a_keys[i][0]) printf("Alt-%i = %s\n", i, communicate_a_keys[i]); i++; } printf("-- \n"); fflush(stdout); break; } } } 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; int echo_on = 0; 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); // 3.1.20: Ctrl-Z can be sent with Alt-Z: //printf("( If you need to send Ctrl-Z, change the suspend character first, like stty susp \\^N )\n"); printf("( If you need to send Ctrl-Z, use Alt-Z )\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; // 3.1.16beta: Automatically set echo on when talking with modem. 3.1.16beta2: Not with network modems which may require login and/or password. if (!DEVICE_IS_SOCKET && modem_handle != -1 && !echo_on) { write_to_modem("ATE1\r", 5, 0, 1); echo_on = 1; } if ((n = read(STDIN_FILENO, tmp, (modem_handle != -1)? sizeof(tmp) -1 : 1)) > 0) { if (modem_handle != -1) { tmp[n] = 0; // 3.1.20: Handle Alt-0...9, Alt-Z and Alt-?: if (n == 2 && tmp[0] == 0x1B) { if (tmp[1] >= 0x30 && tmp[1] <= 0x39) strcpy(tmp, communicate_a_keys[tmp[1] - 0x30]); else if (tmp[1] == 0x5A || tmp[1] == 0x7A) sprintf(tmp, "%c", 0x1A); else if (tmp[1] == 0x3F) { show_a_commands(); *tmp = 0; } } if (*tmp) 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"); // 3.1.20: Show Alt commands if any defined: show_a_commands(); fflush(stdout); 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] : '.'); } writelogfile0(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: // 3.1.16beta: only if logging "not registered": //if (retries > 0) if (retries > 0 && retries >= DEVICE.log_not_registered_after) { put_command("AT+CSQ\r", answer, sizeof(answer), "csq", 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), "creg", "(\\+CREG:.*OK)|(ERROR)"); // 3.1.16beta: Remove everything before "+CREG:" if ((p = strstr(answer, "+CREG:"))) memmove(answer, p, strlen(p) + 1); // 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); } else { // 3.1.16beta: log with level 7, start trouble logging: writelogfile0(LOG_DEBUG, 1, tb_sprintf("MODEM IS NOT REGISTERED, WAITING %i SEC. BEFORE RETRYING %i. TIME", waitnetwork_errorsleeptime, retries +1)); } 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), "csq", 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), "csq", 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 // 3.1.16beta: Flush required when select() was used and there was no modem connected, // without flush the close() has 30 sec. delay: tcflush(modem_handle, TCIOFLUSH); 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; } smstools3/src/logging.h0000755000175000017500000000325613100201007014023 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 #include // 3.1.16beta2: Moved logch() and prch() from charset.c to logging.c: char logch_buffer[8192]; #ifdef __GNUC__ void logch(char* format, ...) __attribute__ ((format(printf, 1, 2))); #else void logch(char* format, ...); #endif char prch(char ch); // 3.1.16beta: changed type: //int trouble_logging_started; time_t 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); #ifdef __GNUC__ void writelogfile(int severity, int trouble, char* format, ...) __attribute__ ((format(printf, 3, 4))); #else void writelogfile(int severity, int trouble, char* format, ...); #endif void flush_smart_logging(); #endif smstools3/src/smsd_cfg.h0000755000175000017500000005776413102127406014213 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 #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 POLL_FASTER_DEFAULT 5 #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 // 3.1.18: Definitions for process_id's: #define PROCESS_ID_MAINPROCESS -1 #define PROCESS_ID_NOTIFIER -2 #define PROCESS_ID_CHILD -3 #define PROCESS_IS_MODEM (process_id >= 0) #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 #define SIZE_IGNORE_UNEXPECTED_INPUT 512 // 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 EXPECT_OK_ERROR_0_4 "(OK)|(ERROR)|(0)|(4)" // 3.1.16beta. #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)) #define ALPHABET_GSM -1 #define ALPHABET_ISO 0 #define ALPHABET_BINARY 1 #define ALPHABET_UCS2 2 #define ALPHABET_UTF8 3 #define ALPHABET_UNKNOWN 4 #define ALPHABET_DEFAULT 0 char process_title[32]; // smsd for main task, NOTIFIER or CHILD, name of a modem for other tasks. int process_id; // -1 for main task, all modems have numbers starting with 0. // This is the same as device, can be used like devices[process_id] if IS_MODEM_PROCESS. time_t process_start_time; int modem_handle; // Handle for modem. int put_command_timeouts; unsigned long long put_command_sent; // 3.1.16beta. char tmpdir[PATH_MAX]; // 3.1.16beta. 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 imei[SIZE_IDENTITY]; // 3.1.16beta: IMEI asked from the modem (CGSN) char conf_identity[SIZE_IDENTITY]; // Identification set in the conf file (CIMI) 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) int cs_convert_optical; // 3.1.16beta2. 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. 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]; int dev_rr_keep_open; // 3.1.16beta2. 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]; char telnet_cmd[64]; // 3.1.16beta. char telnet_cmd_prompt[100]; // 3.1.16beta. int telnet_crlf; // 3.1.16beta. char wakeup_init[64]; // 3.1.16beta. 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. int send_retries; // 3.1.16beta. int report_read_timeouts; // 3.1.16beta. int select_pdu_mode; // 3.1.16beta2. char ignore_unexpected_input[SIZE_IGNORE_UNEXPECTED_INPUT]; // 3.1.16beta2. int national_toa_unknown; // 3.1.16beta2. int reply_path; // 3.1.16beta2. char description[64]; // 3.1.16beta2. char text_is_pdu_key[SIZE_TO]; // 3.1.16beta2. int sentsleeptime; // 3.1.16beta2. int poll_faster; // 3.1.16beta2. int read_delay; // 3.1.16beta2. milliseconds int language; //3.1.16beta2. int language_ext; //3.1.16beta2. int notice_ucs2; // 3.1.16beta2. int receive_before_send; // 3.1.17. Now also a modem setting. int delaytime; // 3.1.18. int delaytime_random_start; // 3.1.18. int read_identity_after_suspend; // 3.1.18. int read_configuration_after_suspend; // 3.1.18. int check_sim; // 3.1.21. char check_sim_cmd[64]; // 3.1.21. int check_sim_keep_open; // 3.1.21. char check_sim_reset[64]; // 3.1.21. int check_sim_retries; // 3.1.21. int check_sim_wait; // 3.1.21. } _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_failed_copy[PATH_MAX]; // 3.1.17. char d_incoming[PATH_MAX]; // Incoming spool directory char d_incoming_copy[PATH_MAX]; // 3.1.16beta2. char d_report[PATH_MAX]; // Incoming report spool directory char d_report_copy[PATH_MAX]; // 3.1.17. 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_sent_copy[PATH_MAX]; // 3.1.17. 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 status_include_uptime; // 3.1.16beta: 1 = include started & uptime line in the status file. 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; // 3.1.16beta2: log_read_timing for performance tuning. int log_read_timing; // 3.1.16beta: int log_response_time; // 3.1.16beta2: int default_alphabet; // 3.1.17: Child process for the mainprocess: char mainprocess_child[PATH_MAX]; char mainprocess_child_args[PATH_MAX]; // 3.1.17: Notifier for the mainprocess: int mainprocess_notifier; // 3.1.17: If *_copy was made, evenhandler can use it instead of original file: int eventhandler_use_copy; // 3.1.17: This defines how long to sleep while looping: int sleeptime_mainprocess; // 3.1.17: Defines how often PID is checked to detect if another smsd is running: int check_pid_interval; // 3.1.18: start script/program for mainprocess: char mainprocess_start[PATH_MAX]; char mainprocess_start_args[PATH_MAX]; int message_count; // Counter for sent messages. Multipart message is one message. volatile sig_atomic_t break_workless_delay; // To break the delay when SIGCONT is received. volatile sig_atomic_t terminate; // To terminate when SIGTERM is received. 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. // 3.1.20: Alt keys in communication mode: #define COMMUNICATE_A_KEY_COUNT 10 char communicate_a_keys[COMMUNICATE_A_KEY_COUNT][256]; /* 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); #ifdef __GNUC__ char *tb_sprintf(char* format, ...) __attribute__ ((format(printf, 1, 2))); #else char *tb_sprintf(char* format, ...); #endif int savephonecall(char *entry_number, int entry_type, char *entry_text); int refresh_configuration(); #endif smstools3/src/locking.h0000755000175000017500000000141513046432777014053 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 smstools3/src/charshift.h0000755000175000017500000000227213046432777014402 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES #ifndef CHARSHIFT_H #define CHARSHIFT_H char *get_language_name ( int value ); int parse_language_setting( char *value ); int select_language_shift_tables ( int *language, int *language_ext, int DEVICE_language, int DEVICE_language_ext ); int utf2gsm_shift ( char *text, size_t text_size, int *textlen, int *language, int *language_ext, char **notice ); int get_language_shift ( char *udh, int *language, int *language_ext ); int gsm2utf8_shift ( char *buffer, size_t buffer_size, int userdatalength, int language, int language_ext ); void print_language_tables ( void ); #endif #endif smstools3/src/alarm.c0000755000175000017500000000277113046432777013522 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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"); } } } smstools3/src/locking.c0000755000175000017500000000463613054332671014045 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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(), // 3.1.18: Use process_title now when there are other process_id's below 0, // even when CHILD or NOTIFIER do not use lockfiles (at least currently): //(process_id == -1) ? "MAINPROCESS" : DEVICE.name); process_title); write(lockfile, pid, strlen(pid)); // 3.1.16beta: Fix: Use fsync instead of sync after close: fsync(lockfile); close(lockfile); 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]; 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)) // 3.1.16beta: Check if file exists if (unlink(lockfilename)) return 0; return 1; } smstools3/src/stats.h0000755000175000017500000000472113052076237013556 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 smstools3/src/whitelist.c0000755000175000017500000000411113046433000014404 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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; } smstools3/src/cfgfile.h0000755000175000017500000000274313046432777014031 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 smstools3/src/whitelist.h0000755000175000017500000000127013046433000014414 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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 smstools3/src/version.h0000755000175000017500000000011613102443006014063 0ustar kekekeke#ifndef VERSION_H #define VERSION_H #define smsd_version "3.1.21" #endif smstools3/src/charshift.c0000755000175000017500000010705613056277034014375 0ustar kekekeke/* SMS Server Tools 3 Copyright (C) 2006- Keijo Kasvi http://smstools3.kekekasvi.com/ Based on SMS Server Tools 2, http://stefanfrings.de/smstools/ 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. */ #ifdef PRINT_NATIONAL_LANGUAGE_SHIFT_TABLES #undef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES #endif #ifndef DISABLE_NATIONAL_LANGUAGE_SHIFT_TABLES #include #include #include #include #include #include #include "logging.h" #include "smsd_cfg.h" #include "pdu.h" #include "charset.h" #include "extras.h" #include "version.h" #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY #define LANGUAGE_NAMES_COUNT 14 #else #define LANGUAGE_NAMES_COUNT 4 #endif char *language_names[] = { "basic", "Turkish", "Spanish", "Portuguese" #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY , "Bengali and Assemese", "Gujarati", "Hindi", "Kannada", "Malayalam", "Oriya", "Punjabi", "Tamil", "Telugu", "Urdu" #endif }; /* Character Set in tables is UTF-8. */ char *locking_shift[][128] = { /* Basic Character Set */ { "@", "£", "$", "¥", "è", "é", "ù", "ì", "ò", "Ç", "\n", "Ø", "ø", "\r", "Å", "å", "Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", 0, "Æ", "æ", "ß", "É", " ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "¡", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§", "¿", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à" }, /* Locking Shift 1 - Turkish */ { "@", "£", "$", "¥", "€", "é", "ù", "ı", "ò", "Ç", "\n", "Ğ", "ğ", "\r", "Å", "å", "Δ", "_", "Φ", "Γ", "Λ", "Ω", "Π", "Ψ", "Σ", "Θ", "Ξ", 0, "Ş", "ş", "ß", "É", " ", "!", "\"", "#", "¤", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "İ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ä", "Ö", "Ñ", "Ü", "§", "ç", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ä", "ö", "ñ", "ü", "à" }, /* Locking Shift 2 - No Locking Shift Table Defined for Spanish */ { }, /* Locking Shift 3 - Portuguese */ { "@", "£", "$", "¥", "ê", "é", "ú", "í", "ó", "ç", "\n", "Ô", "ô", "\r", "Á", "á", "Δ", "_", "ª", "Ç", "À", "∞", "^", "\\", "€", "Ó", "|", 0, "Â", "â", "Ê", "É", " ", "!", "\"", "#", "º", "%", "&", "'", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "Í", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "Ã", "Õ", "Ú", "Ü", "§", "~", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ã", "õ", "`", "ü", "à" } #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY , /* Locking Shift 4 - Bengali and Assemese */ { "ঁ", "ং", "ঃ", "অ", "আ", "ই", "ঈ", "উ", "ঊ", "ঋ", "\n", "ঌ", 0, "\r", 0, "এ", "ঐ", 0, 0, "ও", "ঔ", "ক", "খ", "গ", "ঘ", "ঙ", "চ", 0, "ছ", "জ", "ঝ", "ঞ", " ", "!", "ট", "ঠ", "ড", "ঢ", "ণ", "ত", ")", "(", "থ", "দ", ",", "ধ", ".", "ন", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "প", "ফ", "?", "ব", "ভ", "ম", "য", "র", 0, "ল", 0, 0, 0, "শ", "ষ", "স", "হ", "়", "ঽ", "া", "ি", "ী", "ু", "ূ", "ৃ", "ৄ", 0, 0, "ে", "ৈ", 0, 0, "ো", "ৌ", "্", "ৎ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ৗ", "ড়", "ঢ়", "ৰ", "ৱ" }, /* Locking Shift 5 - Gujarati */ { "ઁ", "ં", "ઃ", "અ", "આ", "ઇ", "ઈ", "ઉ", "ઊ", "ઋ", "\n", "ઌ", "ઍ", "\r", 0, "એ", "ઐ", "ઑ", 0, "ઓ", "ઔ", "ક", "ખ", "ગ", "ઘ", "ઙ", "ચ", 0, "છ", "જ", "ઝ", "ઞ", " ", "!", "ટ", "ઠ", "ડ", "ઢ", "ણ", "ત", ")", "(", "થ", "દ", ",", "ધ", ".", "ન", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "પ", "ફ", "?", "બ", "ભ", "મ", "ય", "ર", 0, "લ", "ળ", 0, "વ", "શ", "ષ", "સ", "હ", "઼", "ઽ", "ા", "િ", "ી", "ુ", "ૂ", "ૃ", "ૄ", "ૅ", 0, "ે", "ૈ", "ૉ", 0, "ો", "ૌ", "્", "ૐ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ૠ", "ૡ", "ૢ", "ૣ", "૱" }, /* Locking Shift 6 - Hindi */ { "ँ", "ं", "ः", "अ", "आ", "इ", "ई", "उ", "ऊ", "ऋ", "\n", "ऌ", "ऍ", "\r", "ऎ", "ए", "ऐ", "ऑ", "ऒ", "ओ", "औ", "क", "ख", "ग", "घ", "ङ", "च", 0, "छ", "ज", "झ", "ञ", " ", "!", "ट", "ठ", "ड", "ढ", "ण", "त", ")", "(", "थ", "द", ",", "ध", ".", "न", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "ऩ", "प", "फ", "?", "ब", "भ", "म", "य", "र", "ऱ", "ल", "ळ", "ऴ", "व", "श", "ष", "स", "ह", "़", "ऽ", "ा", "ि", "ी", "ु", "ू", "ृ", "ॄ", "ॅ", "ॆ", "े", "ै", "ॉ", "ॊ", "ो", "ौ", "्", "ॐ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ॲ", "ॻ", "ॼ", "ॾ", "ॿ" }, /* Locking Shift 7 - Kannada */ { 0, "ಂ", "ಃ", "ಅ", "ಆ", "ಇ", "ಈ", "ಉ", "ಊ", "ಋ", "\n", "ಌ", 0, "\r", "ಎ", "ಏ", "ಐ", 0, "ಒ", "ಓ", "ಔ", "ಕ", "ಖ", "ಗ", "ಘ", "ಙ", "ಚ", 0, "ಛ", "ಜ", "ಝ", "ಞ", " ", "!", "ಟ", "ಠ", "ಡ", "ಢ", "ಣ", "ತ", ")", "(", "ಥ", "ದ", ",", "ಧ", ".", "ನ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "ಪ", "ಫ", "?", "ಬ", "ಭ", "ಮ", "ಯ", "ರ", "ಱ", "ಲ", "ಳ", 0, "ವ", "ಶ", "ಷ", "ಸ", "ಹ", "಼", "ಽ", "ಾ", "ಿ", "ೀ", "ು", "ೂ", "ೃ", "ೄ", 0, "ೆ", "ೇ", "ೈ", 0, "ೊ", "ೋ", "ೌ", "್", "ೕ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ೖ", "ೠ", "ೡ", "ೢ", "ೣ" }, /* Locking Shift 8 - Malayalam */ { 0, "ം", "ഃ", "അ", "ആ", "ഇ", "ഈ", "ഉ", "ഊ", "ഋ", "\n", "ഌ", 0, "\r", "എ", "ഏ", "ഐ", 0, "ഒ", "ഓ", "ഔ", "ക", "ഖ", "ഗ", "ഘ", "ങ", "ച", 0, "ഛ", "ജ", "ഝ", "ഞ", " ", "!", "ട", "ഠ", "ഡ", "ഢ", "ണ", "ത", ")", "(", "ഥ", "ദ", ",", "ധ", ".", "ന", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "പ", "ഫ", "?", "ബ", "ഭ", "മ", "യ", "ര", "റ", "ല", "ള", "ഴ", "വ", "ശ", "ഷ", "സ", "ഹ", 0, "ഽ", "ാ", "ി", "ീ", "ു", "ൂ", "ൃ", "ൄ", 0, "െ", "േ", "ൈ", 0, "ൊ", "ോ", "ൌ", "്", "ൗ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ൠ", "ൡ", "ൢ", "ൣ", "൹" }, /* Locking Shift 9 - Oriya */ { "ଁ", "ଂ", "ଃ", "ଅ", "ଆ", "ଇ", "ଈ", "ଉ", "ଊ", "ଋ", "\n", "ଌ", 0, "\r", 0, "ଏ", "ଐ", 0, 0, "ଓ", "ଔ", "କ", "ଖ", "ଗ", "ଘ", "ଙ", "ଚ", 0, "ଛ", "ଜ", "ଝ", "ଞ", " ", "!", "ଟ", "ଠ", "ଡ", "ଢ", "ଣ", "ତ", ")", "(", "ଥ", "ଦ", ",", "ଧ", ".", "ନ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "ପ", "ଫ", "?", "ବ", "ଭ", "ମ", "ଯ", "ର", 0, "ଲ", "ଳ", 0, "ଵ", "ଶ", "ଷ", "ସ", "ହ", "଼", "ଽ", "ା", "ି", "ୀ", "ୁ", "ୂ", "ୃ", "ୄ", 0, 0, "େ", "ୈ", 0, 0, "ୋ", "ୌ", "୍", "ୖ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ୗ", "ୠ", "ୡ", "ୢ", "ୣ" }, /* Locking Shift 10 - Punjabi */ { "ਁ", "ਂ", "ਃ", "ਅ", "ਆ", "ਇ", "ਈ", "ਉ", "ਊ", 0, "\n", 0, 0, "\r", 0, "ਏ", "ਐ", 0, 0, "ਓ", "ਔ", "ਕ", "ਖ", "ਗ", "ਘ", "ਙ", "ਚ", 0, "ਛ", "ਜ", "ਝ", "ਞ", " ", "!", "ਟ", "ਠ", "ਡ", "ਢ", "ਣ", "ਤ", ")", "(", "ਥ", "ਦ", ",", "ਧ", ".", "ਨ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "ਪ", "ਫ", "?", "ਬ", "ਭ", "ਮ", "ਯ", "ਰ", 0, "ਲ", "ਲ਼", 0, "ਵ", "ਸ਼", 0, "ਸ", "ਹ", "਼", 0, "ਾ", "ਿ", "ੀ", "ੁ", "ੂ", 0, 0, 0, 0, "ੇ", "ੈ", 0, 0, "ੋ", "ੌ", "੍", "ੑ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ੰ", "ੱ", "ੲ", "ੳ", "ੴ" }, /* Locking Shift 11 - Tamil */ { 0, "ஂ", "ஃ", "அ", "ஆ", "இ", "ஈ", "உ", "ஊ", 0, "\n", 0, 0, "\r", "எ", "ஏ", "ஐ", 0, "ஒ", "ஓ", "ஔ", "க", 0, 0, 0, "ங", "ச", 0, 0, "ஜ", 0, "ஞ", " ", "!", "ட", 0, 0, 0, "ண", "த", ")", "(", 0, 0, ",", 0, ".", "ந", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "ன", "ப", 0, "?", 0, 0, "ம", "ய", "ர", "ற", "ல", "ள", "ழ", "வ", "ஶ", "ஷ", "ஸ", "ஹ", 0, 0, "ா", "ி", "ீ", "ு", "ூ", 0, 0, 0, "ெ", "ே", "ை", 0, "ொ", "ோ", "ௌ", "்", "ௐ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ௗ", "௰", "௱", "௲", "௹" }, /* Locking Shift 12 - Telugu */ { "ఁ", "ం", "ః", "అ", "ఆ", "ఇ", "ఈ", "ఉ", "ఊ", "ఋ", "\n", "ఌ", 0, "\r", "ఎ", "ఏ", "ఐ", 0, "ఒ", "ఓ", "ఔ", "క", "ఖ", "గ", "ఘ", "ఙ", "చ", 0, "ఛ", "జ", "ఝ", "ఞ", " ", "!", "ట", "ఠ", "డ", "ఢ", "ణ", "త", ")", "(", "థ", "ద", ",", "ధ", ".", "న", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", 0, "ప", "ఫ", "?", "బ", "భ", "మ", "య", "ర", "ఱ", "ల", "ళ", 0, "వ", "శ", "ష", "స", "హ", 0, "ఽ", "ా", "ి", "ీ", "ు", "ూ", "ృ", "ౄ", 0, "ె", "ే", "ై", 0, "ొ", "ో", "ౌ", "్", "ౕ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ౖ", "ౠ", "ౡ", "ౢ", "ౣ" }, /* Locking Shift 13 - Urdu */ { "ا", "آ", "ب", "ٻ", "ڀ", "پ", "ڦ", "ت", "ۂ", "ٿ", "\n", "ٹ", "ٽ", "\r", "ٺ", "ټ", "ث", "ج", "ځ", "ڄ", "ڃ", "څ", "چ", "ڇ", "ح", "خ", "د", 0, "ڌ", "ڈ", "ډ", "ڊ", " ", "!", "ڏ", "ڍ", "ذ", "ر", "ڑ", "ړ", ")", "(", "ڙ", "ز", ",", "ږ", ".", "ژ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "ښ", "س", "ش", "?", "ص", "ض", "ط", "ظ", "ع", "ف", "ق", "ک", "ڪ", "ګ", "گ", "ڳ", "ڱ", "ل", "م", "ن", "ں", "ڻ", "ڼ", "و", "ۄ", "ە", "ہ", "ھ", "ء", "ی", "ې", "ے", "ٍ", "ِ", "ُ", "ٗ", "ٔ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "ٕ", "ّ", "ٓ", "ٖ", "ٰ" } #endif }; char *single_shift[][128] = { /* Basic Character Set Extension */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\f", 0, 0, 0, 0, 0, 0, 0, 0, 0, "^", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "{", "}", 0, 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 1 - Turkish */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "\f", 0, 0, 0, 0, 0, 0, 0, 0, 0, "^", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "{", "}", 0, 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", 0, 0, 0, 0, 0, 0, "Ğ", 0, "İ", 0, 0, 0, 0, 0, 0, 0, 0, 0, "Ş", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "ç", 0, "€", 0, "ğ", 0, "ı", 0, 0, 0, 0, 0, 0, 0, 0, 0, "ş", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 2 - Spanish */ { 0, 0, 0, 0, 0, 0, 0, 0, 0, "ç", "\f", 0, 0, 0, 0, 0, 0, 0, 0, 0, "^", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "{", "}", 0, 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "Á", 0, 0, 0, 0, 0, 0, 0, "Í", 0, 0, 0, 0, 0, "Ó", 0, 0, 0, 0, 0, "Ú", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "á", 0, 0, 0, "€", 0, 0, 0, "í", 0, 0, 0, 0, 0, "ó", 0, 0, 0, 0, 0, "ú", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 3 - Portuguese */ { 0, 0, 0, 0, 0, "ê", 0, 0, 0, "ç", "\f", "Ô", "ô", 0, "Á", "á", 0, 0, "Φ", "Γ", "^", "Ω", "Π", "Ψ", "Σ", "Θ", 0, 0, 0, 0, 0, "Ê", 0, 0, 0, 0, 0, 0, 0, 0, "{", "}", 0, 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "À", 0, 0, 0, 0, 0, 0, 0, "Í", 0, 0, 0, 0, 0, "Ó", 0, 0, 0, 0, 0, "Ú", 0, 0, 0, 0, 0, "Ã", "Õ", 0, 0, 0, 0, "Â", 0, 0, 0, "€", 0, 0, 0, "í", 0, 0, 0, 0, 0, "ó", 0, 0, 0, 0, 0, "ú", 0, 0, 0, 0, 0, "ã", "õ", 0, 0, "â" } #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY , /* Single Shift 4 - Bengali and Assemese */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "০", "১", 0, "২", "৩", "৪", "৫", "৬", "৭", "৮", "৯", "য়", "ৠ", "ৡ", "ৢ", "{", "}", "ৣ", "৲", "৳", "৴", "৵", "\\", "৶", "৷", "৸", "৹", "৺", 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 5 - Gujarati */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "૦", "૧", "૨", "૩", "૪", "૫", "૬", "૭", "૮", "૯", 0, 0, "{", "}", 0, 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 6 - Hindi */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "०", "१", "२", "३", "४", "५", "६", "७", "८", "९", "॑", "॒", "{", "}", "॓", "॔", "क़", "ख़", "ग़", "\\", "ज़", "ड़", "ढ़", "फ़", "य़", "ॠ", "ॡ", "ॢ", "ॣ", "॰", "ॱ", 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 7 - Kannada */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "೦", "೧", "೨", "೩", "೪", "೫", "೬", "೭", "೮", "೯", "ೞ", "ೱ", "{", "}", "ೲ", 0, 0, 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 8 - Malayalam */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "൦", "൧", "൨", "൩", "൪", "൫", "൬", "൭", "൮", "൯", "൰", "൱", "{", "}", "൲", "൳", "൴", "൵", "ൺ", "\\", "ൻ", "ർ", "ൽ", "ൾ", "ൿ", 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 9 - Oriya */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "୦", "୧", "୨", "୩", "୪", "୫", "୬", "୭", "୮", "୯", "ଡ଼", "ଢ଼", "{", "}", "ୟ", "୰", "ୱ", 0, 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 10 - Punjabi */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "੦", "੧", "੨", "੩", "੪", "੫", "੬", "੭", "੮", "੯", "ਖ਼", "ਗ਼", "{", "}", "ਜ਼", "ੜ", "ਫ਼", "ੵ", 0, "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 11 - Tamil */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "।", "॥", 0, "௦", "௧", "௨", "௩", "௪", "௫", "௬", "௭", "௮", "௯", "௳", "௴", "{", "}", "௵", "௶", "௷", "௸", "௺", "\\", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 12 - Telugu */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", 0, 0, 0, "౦", "౧", "౨", "౩", "౪", "౫", "౬", "౭", "౮", "౯", "ౘ", "ౙ", "{", "}", "౸", "౹", "౺", "౻", "౼", "\\", "౽", "౾", "౿", 0, 0, 0, 0, 0, 0, 0, 0, 0, "[", "~", "]", 0, "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* Single Shift 13 - Urdu */ { "@", "£", "$", "¥", "¿", "\"", "¤", "%", "&", "'", "\f", "*", "+", 0, "-", "/", "<", "=", ">", "¡", "^", "¡", "_", "#", "*", "؀", "؁", 0, "۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹", "،", "؍", "{", "}", "؎", "؏", "ؐ", "ؑ", "ؒ", "\\", "ؓ", "ؔ", "؛", "؟", "ـ", "ْ", "٘", "٫", "٬", "ٲ", "ٳ", "ۍ", "[", "~", "]", "۔", "|", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "€", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } #endif }; char *log_err_str = 0; void logerror(char* format, ...) { va_list argp; char text[2048]; va_start(argp, format); vsnprintf(text, sizeof(text), format, argp); va_end(argp); if (!log_err_str) { log_err_str = (char *)malloc(strlen(text) +1); if (log_err_str) *log_err_str = 0; } else log_err_str = (char *)realloc((void *)log_err_str, strlen(log_err_str) + strlen(text) +1); if (log_err_str) strcat(log_err_str, text); } char *get_language_name ( int value ) { if (value >= 0 && value < LANGUAGE_NAMES_COUNT) return language_names[value]; return ""; } int parse_language_setting ( char *value ) { int i; char test[32]; char *p; snprintf(test, sizeof(test), "%s", value); if ((p = strchr(test, ' '))) *p = '\0'; if (!(*test)) return -1; for (i = 0; i < LANGUAGE_NAMES_COUNT; i++) if (!strncasecmp(test, language_names[i], strlen(test))) return i; #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY if (!strncasecmp(test, "Bengali", strlen(test)) || !strncasecmp(test, "Assemese", strlen(test))) return 4; #endif i = (int)strtol(test, NULL, 10); if (errno == EINVAL || i < 0 || i > LANGUAGE_NAMES_COUNT - 1) return -1; return i; } int select_language_shift_tables ( int *language, int *language_ext, int DEVICE_language, int DEVICE_language_ext ) { // language_ext defaults to language: if (*language_ext < 0) *language_ext = *language; // If only language_ext is given (other than basic), language defaults to basic: if (*language < 0 && *language_ext > 0) *language = 0; if (*language < 0) { if (DEVICE_language_ext < 0) DEVICE_language_ext = DEVICE_language; if (DEVICE_language < 0 && DEVICE_language_ext > 0) DEVICE_language = 0; *language = DEVICE_language; *language_ext = DEVICE_language_ext; } // No locking shift table for Spanish: if (*language == 2) *language = 0; if (*language > 0 || *language_ext > 0) return 1; return 0; } int search_from_table( char *table[], char *test ) { int i; for (i = 0; i < 128; i++) if (table[i] && !strcmp(table[i], test)) return i; return -1; } int utf2gsm_shift ( char *text, size_t text_size, int *textlen, int *language, int *language_ext, char **notice ) { int result = 0; char *source; char *ps, *pd; char *p, *p2; char *max_ps; // If UTF-8 is broken int bytes; int chars; char test[5]; char tmp[64]; int i, l; int found; int retry = 1; int initial_textlen = *textlen; int basic_ok = *language > 0; int basic_ext_ok = *language_ext > 0; int language_used = 0; int language_ext_used = 0; if (!(source = (char *)malloc(initial_textlen + 1))) result = -1; else { strncpy(source, text, initial_textlen); source[initial_textlen] = '\0'; while (retry) { retry = 0; free(log_err_str); log_err_str = 0; memset(text, 0, text_size); ps = source; pd = text; max_ps = ps + initial_textlen -1; *textlen = 0; chars = 0; while (ps <= max_ps && *ps) { bytes = utf8bytes(ps); if (bytes <= 0) { memset(tmp, 0, sizeof(tmp)); for (i = 0; ps[i] && i < 6; i++) sprintf(strchr(tmp, 0), " %02X", (unsigned char)ps[i]); tb_sprintf("ERROR: Invalid UTF-8 sequence after %i characters, starting at%s.\n", chars, tmp); logerror(tb + 7); if (notice) strcat_realloc(notice, tb, 0); result = -1; break; } strncpy(test, ps, bytes); test[bytes] = '\0'; ps += bytes; chars++; found = 0; if (basic_ok && (i = search_from_table(locking_shift[0], test)) >= 0) { found = 1; *pd = (unsigned char)i; pd++; (*textlen)++; } if (!found && basic_ext_ok && (i = search_from_table(single_shift[0], test)) >= 0) { found = 1; *pd = (unsigned char)0x1B; pd++; *pd = (unsigned char)i; pd++; (*textlen) += 2; } if (!found && (i = search_from_table(locking_shift[*language], test)) >= 0) { if (basic_ok) { basic_ok = 0; retry = 1; break; } found = 1; *pd = (unsigned char)i; pd++; (*textlen)++; language_used = 1; } if (!found && (i = search_from_table(single_shift[*language_ext], test)) >= 0) { if (basic_ext_ok) { basic_ext_ok = 0; retry = 1; break; } found = 1; *pd = (unsigned char)0x1B; pd++; *pd = (unsigned char)i; pd++; (*textlen) += 2; language_ext_used = 1; } if (!found) { memset(tmp, 0, sizeof(tmp)); for (i = 0, l = strlen(test); i < l; i++) sprintf(strchr(tmp, 0), "%02X ", (unsigned char)test[i]); tb_sprintf("NOTICE: Cannot convert %i. UTF-8 character %s(%s) to GSM, not found on language tables %i/%i.\n", chars, tmp, test, *language, *language_ext); logerror(tb + 8); if (notice) strcat_realloc(notice, tb, 0); } } } free(source); if (!language_used) *language = 0; if (!language_ext_used) *language_ext = 0; } if (log_err_str) { p = log_err_str; while (p && *p) { if ((p2 = strchr(p, '\n'))) *p2 = 0; writelogfile(LOG_NOTICE, 0, "%s", p); p = (p2)? p2 +1 : NULL; } free(log_err_str); log_err_str = 0; } return result; } int get_language_shift ( char *udh, int *language, int *language_ext ) { int result = 0; int octets, idx, id, i; if (language) *language = -1; if (language_ext) *language_ext = -1; if (!udh || octet2bin_check(udh) <= 0) return 0; octets = strlen(udh) /3; idx = 1; while (idx < octets) { if ((id = octet2bin_check(udh +idx *2 +idx)) < 0) return 0; if (id == 0x25 || id == 0x24) { if (++idx >= octets) return 0; if ((i = octet2bin_check(udh +idx *2 +idx)) != 0x01) return 0; if (++idx >= octets) return 0; i = octet2bin_check(udh +idx *2 +idx); if (i < 0 || i > 13) return 0; if (id == 0x25 && language) *language = i; else if (id == 0x24 && language_ext) *language_ext = i; result++; idx++; continue; } if (++idx >= octets) return 0; if ((i = octet2bin_check(udh +idx *2 +idx)) < 0) return 0; idx += i +1; } return result; } int gsm2utf8_shift ( char *buffer, size_t buffer_size, int userdatalength, int language, int language_ext ) { char *source; int dest_length = 0; int s = 0; int ch; char *p; int l; if (language < 0) language = 0; if (language_ext < 0) language_ext = 0; if (language >= LANGUAGE_NAMES_COUNT || language_ext >= LANGUAGE_NAMES_COUNT) { writelogfile(LOG_ERR, 0, "Message is using language values which are not supported in this compilation: language:%i, language_ext:%i", language, language_ext); return 0; } if ((source = (char *)malloc(userdatalength))) { memcpy(source, buffer, userdatalength); *buffer = '\0'; while (s < userdatalength) { p = 0; if ((ch = source[s]) == 0x1B) { if (++s >= userdatalength) break; ch = source[s]; p = single_shift[language_ext][ch]; // 0x1B is reserved for the extension to another extension table. // On receipt of this code, a receiving entity shall display a space // until another extension table is defined. if (!p && ch == 0x1B) p = locking_shift[0][0x20]; } else p = locking_shift[language][ch]; if (p) { l = strlen(p); if (dest_length + l + 1 > (int)buffer_size) break; strcat(buffer, p); dest_length += l; } s++; } free(source); return dest_length; } return 0; } #ifdef PRINT_NATIONAL_LANGUAGE_SHIFT_TABLES char *utf2ucs2html ( char *dest, size_t dest_size, char *src ) { char ucs2[2]; *dest = 0; if (!utf8_to_ucs2_char(src, 0, ucs2)) snprintf(dest, dest_size, "
ERROR"); else if (ucs2[0]) snprintf(dest, dest_size, "
%02X%02X", (unsigned char)ucs2[0], (unsigned char)ucs2[1]); else snprintf(dest, dest_size, "
 "); return dest; } void print_language_tables ( void ) { int table, locking, row, col, i; char *p; char ucs2html[128]; printf( "\n"\ "\n"\ "\n"\ "\n" "SMS Server Tools 3\n"); printf( "\n\n\n"); printf( "

SMS Server Tools 3

\n"\ "Home\n"\ "

National Language Shift Tables

\n"\ "\n"\ "\n"); printf("

\n"); printf("SMS Server Tools 3 version %s.
\n", smsd_version); printf("Character set is UTF-8.
\n"); printf("Codes shown are UCS-2BE.
\n"); printf("Based on 3GPP TS 23.038 version 13.0.0 Release 13.
\n"); #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY printf("Tables marked with *) are shown using a font from Google Fonts Early Access.
\n"); #endif printf("

\n"); printf( "\n"\ "Tables are:\n"\ "\n"\ "\n"); for (table = 0; table < LANGUAGE_NAMES_COUNT; table++) { locking = (table == 2)? 0 : table; printf("

\n"); printf("%s (%i)", table, language_names[table], table); p = ""; #ifndef NATIONAL_LANGUAGES_EUROPEAN_ONLY switch (table) { case 4: p = "bengali"; break; case 5: p = "gujarati"; break; case 7: p = "kannada"; break; case 8: p = "malayalam"; break; case 9: p = "oriya"; break; case 11: p = "tamil"; break; case 12: p = "telugu"; break; case 13: p = "urdu"; break; } #endif printf("%s
\n", (*p)? "  *)" : "", (*p)? " lang_" : "", p); printf("\n"); for (i = 0; i < 2; i++) { printf("", (!i)? "Locking" : "Single"); for (col = 0; col <= 0x70; col += 0x10) printf("", col); } printf("\n"); for (row = 0; row <= 0x0F; row++) { printf("", row); for (col = 0; col <= 0x70; col += 0x10) { if ((p = locking_shift[locking][row +col])) { utf2ucs2html(ucs2html, sizeof(ucs2html), p); switch (*p) { case '\n': p = "LF"; break; case '\r': p = "CR"; break; case ' ': p = "SP"; break; case '<': p = "<"; break; case '>': p = ">"; break; } printf("", p, ucs2html); } else printf("", (row +col == 0x1B)? "reserved" : "notused"); } printf("", row); for (col = 0; col <= 0x70; col += 0x10) { if ((p = single_shift[table][row +col])) { utf2ucs2html(ucs2html, sizeof(ucs2html), p); switch (*p) { case '\f': p = "FF"; break; case '<': p = "<"; break; case '>': p = ">"; break; } printf("", p, ucs2html); } else printf("", (row +col == 0x0D || row +col == 0x1B)? "reserved" : "notused"); } printf("\n"); } printf("
%s
Shift
0x%02X
0x%02X%s%s0x%02X%s%s
\n
\n"); printf(" top \n"); printf("

\n"); } printf("
\n\n\n"); } #endif #endif