d96bd95de0117a45cdfb412b7a4ccc6ab521374f
[mdsms.git] / mdsms.c
1 #include "config.h"
2 #ifndef lint
3 static char rcsid[] ATTR_UNUSED = "$Id$";
4 #endif
5
6 #include "setup.h"
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <signal.h>
12 #include <stdarg.h>
13 #include <limits.h>
14 #include <ctype.h>
15 #include <string.h>
16 #include <termios.h>
17 #include <unistd.h>
18 #include <assert.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <signal.h>
24 #include <time.h>
25
26 #ifdef HAVE_GETOPT_LONG
27 #include <getopt.h>
28 #else
29 #include "getopt.h"
30 #endif
31
32 #define NELEM(x) (sizeof((x))/sizeof(*(x)))
33
34 #ifndef DEBUG
35 #define dbg(cmd)
36 #else
37 #define dbg(cmd) cmd
38 #endif
39 /* ANSI C does not allow macro with variable arguments */
40 #define dO stderr
41 #define dB(a) dbg(fprintf a)
42
43 #define d1(n1)          dB((dO,n1         ))
44 #define d2(n1,n2)       dB((dO,n1,n2      ))
45 #define d3(n1,n2,n3)    dB((dO,n1,n2,n3   ))
46 #define d4(n1,n2,n3,n4) dB((dO,n1,n2,n3,n4))
47
48 static const char version[]="This is Nokia 9110 logo SMS sender (" PACKAGE " " VERSION ")\n";
49
50 static int verbose
51 #ifdef DEBUG
52         =0xFFFF
53 #endif
54         ;
55 static char *pname;
56 static int dis_cleanup=0,devfd=-1;
57
58 static char *phone,*logoname,*device,*logname,*lockfile,*smsc,*maxretry,*readtime,*chartime,*cmdtime,*baud,*gsmnet;
59 static long maxretryn=DEF_MAXRETRY,readtimen=DEF_READTIME,chartimen=DEF_CHARTIME,cmdtimen=DEF_CMDTIME,baudn=DEF_BAUD;
60
61 static char *devicename; /* path stripped */
62 static char lockreal[512],locked;
63
64 static struct termios restios,tios;
65 static char restios_yes;
66 static FILE *logf;
67
68 static void vlogmsg(
69 #ifndef PRINTF_WORKS_PM
70                 char outerr,
71 #endif
72                 const char *fmt,va_list ap)
73 {
74 time_t stamp;
75 char *ctm,*s;
76 pid_t mypid=-1;
77 char host[LINE_MAX];
78
79         if (!logf) return;
80         if (mypid==-1) {
81                 mypid=getpid();
82                 if (gethostname(host,sizeof(host))) strcpy(host,"<ERROR>");
83                 }
84         time(&stamp);
85         ctm=ctime(&stamp);
86         if ((s=strchr(ctm,'\n'))) *s='\0';
87         fprintf(logf,"%s %s %s[%d]: ",ctm,host,pname,mypid);
88         vfprintf(logf,fmt,ap);
89 #ifndef PRINTF_WORKS_PM
90         if (outerr) fputs(strerror(errno),logf);
91 #endif
92         fputc('\n',logf);
93         fflush(logf);
94 }
95
96 static void logmsg(const char *fmt,...)
97 {
98 va_list ap;
99         va_start(ap,fmt);
100         vlogmsg(
101 #ifndef PRINTF_WORKS_PM
102                 0,
103 #endif
104                 fmt,ap);
105         va_end(ap);
106 }
107
108 static void error(const char *fmt,...)
109 {
110 va_list ap;
111 char fatal=*fmt;
112 #ifndef PRINTF_WORKS_PM
113 char pm,*nfmt;
114 size_t fmtl;
115 #endif
116
117         if (fatal=='!' || fatal=='.' || fatal=='\n') fmt++;
118         else fatal=0;
119
120 #ifndef PRINTF_WORKS_PM
121         if (!(nfmt=strdup(fmt))) return;
122         fmtl=strlen(fmt);
123         if ((pm=(fmtl>=2 && !strcmp(fmt+fmtl-2,"%m")))) nfmt[fmtl-2]='\0';
124 #endif
125
126         fprintf(stderr,"%s: ",pname);
127         va_start(ap,fmt);
128         vfprintf(stderr,
129 #ifdef PRINTF_WORKS_PM
130                 fmt
131 #else
132                 nfmt
133 #endif
134                 ,ap);
135         if (fatal=='!') vlogmsg(
136 #ifdef PRINTF_WORKS_PM
137                 fmt
138 #else
139                 pm,nfmt
140 #endif
141                 ,ap);
142         va_end(ap);
143
144 #ifndef PRINTF_WORKS_PM
145         if (pm) {
146                 fputs(strerror(errno),stderr);
147                 free(nfmt);
148                 }
149 #endif
150
151         if (fatal!='\n') fputc((fatal=='.'?'.':'!'),stderr);
152         fputc('\n',stderr);
153         if (fatal=='!') exit(EXIT_FAILURE);
154 }
155
156 static void chk(void *p)
157 {
158         if (p) return;
159         error("!Virtual memory exhausted");
160 }
161
162 static void cleanup(void)
163 {
164         d1("cleanup()\n");
165         if (dis_cleanup) return;
166         if (restios_yes) {
167                 if (tcsetattr(devfd,TCSANOW,&restios))
168                         error("Error restoring termios for device: %m");
169                 restios_yes=0;
170                 }
171         if (locked && *lockreal) {
172                 d2("Removing lockfile \"%s\"\n",lockreal);
173                 if (unlink(lockreal))
174                         error("Error removing my device lockfile \"%s\": %m",lockreal);
175                 locked=0;
176                 }
177         dis_cleanup=1;
178         exit(EXIT_FAILURE);
179 }
180
181 static void usage(void)
182 {
183         fprintf(stderr,"\
184 \n\
185 %s\
186 \n\
187 Usage: " PACKAGE " [-c|--config <cfgfile>] [-d|--device <device>]\n\
188              [-L|--log <file>] [-b|--baud <rate>]\n\
189              [-l|--lockfile <lock>] [-s|--smsc <smsc #>] [-m|--maxretry <#>]\n\
190              [-r|--readtime <sec>] [-t|--chartime <msec>] [-T|--cmdtime <msec>]\n\
191              [-v|--verbose] [-h|--help] [-V|--version]\n\
192              <dest. phone> <logo filename> [<GSMnet id>]\n\
193 \n\
194  -c, --config\tRead this additional config file\n\
195 \t\t(def. \"" CONFIG_MAIN "\" and \"$HOME" CONFIG_HOME "\")\n\
196  -d, --device\tNokia on this serial device (def. \"" DEF_DEVICE "\")\n\
197  -L, --log\tLog all important messages to this file (def. \"" DEF_LOGNAME "\")\n\
198  -b, --baud\tSet baudrate, 2400-57600 supported (def. %d)\n\
199  -l, --lockfile\tLock serial port by this file, \"%%s\" is basename of device\n\
200 \t\t(def. \"%s\")\n\
201  -s, --smsc\tUse this SMS Center number (def. query from Nokia 9110)\n\
202  -m, --maxretry\tMaximum retries of any command before giving up (def. %d)\n\
203  -r, --readtime\tSeconds for maximum wait time for response (def. %ds)\n\
204  -t, --chartime\tMilliseconds between each char (def. %dms)\n\
205  -T, --cmdtime\tMilliseconds before each whole AT command (def. %dms)\n\
206  -v, --verbose\tIncrease verbosity level, more \"-v\"s give more messages\n\
207  -h, --help\tPrint a summary of the options\n\
208  -V, --version\tPrint the version number\n\
209  <GSMnet id>\t* Oper. logo: Enter custom network code MccMnc, e.g. 23002\n\
210 \t\t* Oper. logo: Specify \"" WORD_NET "\" to read network code from NOL file\n\
211 \t\t* Group gfx : Specify \"" WORD_GROUP "\" to send logo as group graphics\n\
212 \n",version,DEF_BAUD,DEF_LOCKFILE,DEF_MAXRETRY,DEF_READTIME,DEF_CHARTIME,DEF_CMDTIME);
213         exit(EXIT_FAILURE);
214 }
215
216 static const struct option longopts[]={
217 {"config"  ,1,0,'c'},
218 {"device"  ,1,0,'d'},
219 {"log"     ,1,0,'L'},
220 {"baud"    ,1,0,'b'},
221 {"lockfile",1,0,'l'},
222 {"smsc"    ,1,0,'s'},
223 {"maxretry",1,0,'m'},
224 {"readtime",1,0,'r'},
225 {"chartime",1,0,'t'},
226 {"cmdtime" ,1,0,'T'},
227 {"verbose" ,0,0,'v'},
228 {"help"    ,0,0,'h'},
229 {"version" ,0,0,'V'}};
230
231 static void processargs(int argp,char **args,const char *from);
232
233 static char *cfgstack[MAXCFGLOOP];
234 static unsigned cfgstacki=0;
235
236 static void chkfclose(FILE *f,const char *fname)
237 {
238         if (fclose(f))
239                 error("Error closing \"%s\": %m",fname);
240 }
241
242 static void readfile(const char *fname,char quiet)
243 {
244 FILE *f;
245 size_t got;
246 char *args[MAXCFGARGS],*d,*s,blank,quote;
247 unsigned argp;
248 char *buf;
249 long size;
250 static unsigned tot=0;
251
252         if (tot++>=MAXCFGNUM) {
253                 if (tot==MAXCFGNUM+1) error("Too many config files to read, max is %d, break-out",MAXCFGNUM);
254                 return;
255                 }
256         if (!(f=fopen(fname,"rt"))) {
257                 if (!quiet) error("Can't open config file \"%s\" for r/o: %m",fname);
258                 return;
259                 }
260         if (verbose>=2) error(".Reading config file \"%s\"",fname);
261         if (fseek(f,0,SEEK_END))
262                 error("Error seeking to end of \"s\": %m",fname);
263         if ((size=ftell(f))<0)
264                 size=0,error("Error measuring \"%s\": %m",fname);
265         if (size>MAXCONFIG) 
266                 error("File \"%s\" is too long, read only %ld bytes",fname,MAXCONFIG);
267         chk(buf=malloc((size?size:MAXCONFIG)+1));
268         rewind(f);
269         got=fread(buf,1,(size?size:MAXCONFIG),f);
270         if (size && got!=size)
271                 error("File \"%s\" read error, got only %u bytes of %ld",fname,got,size);
272         chkfclose(f,fname);
273         buf[got]='\0';
274         args[0]=pname;
275         for (argp=1,d=s=buf,blank=1,quote=0;s<buf+got;s++) {
276 char c=*s;
277
278                 if (!quote && isspace(c)) {
279                         if (!blank) {
280                                 *d='\0';
281                                 blank=1;
282                                 if (verbose>=2) error("\nConfig \"%s\": arg#%d: %s",fname,argp-1,args[argp-1]);
283                                 }
284                         continue;
285                         }
286                 if (blank) {
287                         if (argp>=NELEM(args)-1) {
288                                 error("Too many arguments in \"%s\", from offset %d ignored",fname,s-buf);
289                                 break;
290                                 }
291                         args[argp++]=s;
292                         d=s;
293                         blank=0;
294                         }
295                 if (c=='\\') { *d++=*++s; continue; }
296                 if (c=='"' || c=='\'') {
297                         if (!quote   ) { quote=c; continue; }
298                         if ( quote==c) { quote=0; continue; }
299                         /* FALLTHRU */
300                         }
301                 *d++=c;
302                 }
303         args[argp]=NULL;
304         processargs(argp,args,fname);
305         free(buf);
306 }
307
308 static struct {
309         const char c;
310         char **const var;
311         unsigned stamp;
312         } optset[]={
313                 { 'd',&device   },
314                 { 'L',&logname  },
315                 { 'b',&baud     },
316                 { 'l',&lockfile },
317                 { 's',&smsc     },
318                 { 'm',&maxretry },
319                 { 'r',&readtime },
320                 { 't',&chartime },
321                 { 'T',&cmdtime  },
322         };
323
324 static void processargs(int argp,char **args,const char *from)
325 {
326 int optc;
327 static unsigned seq=0;
328 int i;
329
330         seq++;
331         optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
332         while ((optc=getopt_long(argp,args,"c:d:L:l:s:m:r:t:T:fvhV",longopts,NULL))!=EOF) switch (optc) {
333                 case 'c':
334                         if (cfgstacki>=NELEM(cfgstack)) {
335                                 error("Looping (%d) during attempt to read config file \"%s\", break-out",NELEM(cfgstack),optind);
336                                 break;
337                                 }
338                         chk(cfgstack[cfgstacki++]=strdup(optarg));
339                         break;
340                 case 'd': case 'L': case 'b': case 'l': case 's': case 'm': case 'r': case 't': case 'T':
341                         for (i=0;i<NELEM(optset);i++)
342                                 if (optset[i].c==optc) {
343                                         if (optset[i].stamp && optset[i].stamp!=seq) {
344                                                 assert(!!*optset[i].var);
345                                                 break;
346                                                 }
347                                         free(*optset[i].var);
348                                         chk(*optset[i].var=strdup(optarg));
349                                         optset[i].stamp=seq;
350                                         break;
351                                         }
352                         assert(i<NELEM(optset));
353                         break;
354                 case 'v':
355                         verbose++;
356                         break;
357                 case 'V':
358                         fprintf(stderr,version);
359                         exit(EXIT_FAILURE);
360                 default:
361                         if (optc!='h')
362                                 error("\nLast getopt(3) error occured during parsing option %d from \"%s\"! Follows help:",optind-1,from);
363                         usage();
364                         break;
365                 }
366         if (!phone && optind<argp) {
367 char *s,*s1;
368
369                 chk(phone=strdup(args[optind++]));
370                 for (s=(s1=phone)+(*phone=='+');*s && s-s1<MAXNUMLEN;s++)
371                         if (!isdigit(*s))
372                                 error("!Invalid digit '%c' in destination phone number - at offset %d of option %d from \"%s\": %s",
373                                         *s,s-phone,optind,from,args[optind]);
374                 if (*s)
375                         error("!Destination phone number too long, max. %d digits allowed in option %d from \"%s\": %s",
376                                 s-s1,optind,from,args[optind]);
377                 }
378         if (!logoname && optind<argp)
379                 chk(logoname=strdup(args[optind++]));
380         if (!gsmnet && optind<argp) {
381 char *s,*d,e=0;
382
383                 chk(gsmnet=strdup(args[optind++]));
384                 if (strcasecmp(gsmnet,WORD_NET) && strcasecmp(gsmnet,WORD_GROUP)) {
385                         for (d=s=gsmnet;*s;s++) {
386                                 if (isdigit(*s)) { *d++=*s; continue; }
387                                 if (isspace(*s)) continue;
388                                 error("\nInvalid characted '%c' in GSMnet at offs %d: %s",
389                                         *s,s-gsmnet,args[optind]);
390                                 e=1;
391                                 break;
392                                 }
393                         if ((d-gsmnet)!=5) {
394                                 error("\nGSMnet is required to have exactly 5 digits or to be\n\
395 either \"" WORD_NET "\" or \"" WORD_GROUP "\", but found length %d: %s",
396                                         d-gsmnet,args[optind]);
397                                 e=1;
398                                 }
399                         if (!e) *d='\0';
400                         else {
401                                 error("\nGSMnet option %d from \"%s\" rejected due to previous errors: %s",
402                                         optind,from,args[optind]);
403                                 free(gsmnet);
404                                 gsmnet=NULL;
405                                 }
406                         }
407                 }
408         while (optind<argp)
409                 error("\nExcessive option %d from \"%s\" ignored: %s",optind,from,args[optind]);
410         while (cfgstacki) {
411 char *s=cfgstack[--cfgstacki];
412
413                 assert(cfgstacki>=0);
414                 readfile(s,0);
415                 free(s);
416                 assert(cfgstacki>=0 && cfgstacki<NELEM(cfgstack));
417                 }
418 }
419
420 static const struct {
421         char **var;
422         const char *name;
423         } nullcheck[]={
424                 {&phone,"destination phone number"},
425 #if 0
426                 {&logoname,"logo filename"},
427                 {&gsmnet,"GSM operator network code"},
428                 {&device,"device for communication"},
429 #endif
430         };
431 static char **emptycheck[]={&logname,&smsc,&logoname,&gsmnet};
432
433 static void lockclose(int fd)
434 {
435         if (close(fd))
436                 error("Error closing lockfile \"%s\"",lockreal);
437 }
438
439 static inline void lockdevice(void)
440 {
441 int fd=-1;
442 char buf[64];
443 ssize_t got;
444 int delay=0;
445 char empty=0;
446 pid_t pid;
447
448         for (;;) {
449                 if (fd!=-1) lockclose(fd);
450 recheck:
451                 if (delay) sleep(delay);
452                 delay=DEVLOCK_PERIOD;
453                 if (verbose>=3) error(".Checking the lockfile \"%s\"..",lockreal);
454                 if ((fd=open(lockreal,O_RDONLY))==-1) break;
455                 if ((got=read(fd,buf,sizeof(buf)-1))<=0) {
456 isempty:
457                         if (empty>=DEVLOCK_MAXEMPTY) {
458                                 error(".Lockfile \"%s\" is still not valid, removing it",lockreal);
459                                 goto remove;
460                                 }
461                         empty++;
462                         continue;
463                         }
464                 assert(got<sizeof(buf));
465                 buf[got]='\0';
466                 if (sscanf(buf,"%d",&pid)!=1) goto isempty;
467                 empty=0;
468                 errno=0;
469                 if (kill(pid,0) && errno!=ESRCH && errno!=EPERM)
470                         error("Error during checking consciousness of PID %d: %m",pid);
471                 if (errno!=ESRCH) continue;
472                 error(".Lockfile \"%s\" is stale (PID %d), removing it",lockreal,pid);
473 remove:
474                 lockclose(fd);
475                 if (unlink(lockreal))
476                         error("Error removing foreign lockfile \"%s\": %m",lockreal);
477                 break;
478                 }
479         errno=0;
480         if ((fd=open(lockreal,O_WRONLY|O_CREAT|O_EXCL,0644))==-1) {
481                 if (errno==EEXIST) goto recheck;
482                 error("!Error creating lockfile \"%s\": %m",lockreal);
483                 }
484         locked=1;
485         got=VARPRINTF(buf,"%010d\n",getpid()); assert(got==11);
486         if (write(fd,buf,got)!=got)
487                 error("!Error writing data to lockfile \"%s\": %m",lockreal);
488         lockclose(fd);
489 }
490
491 static char wasalarm=0;
492 static void sigalarm(int signo);
493
494 static void setalarm(void)
495 {
496         signal(SIGALRM,(RETSIGTYPE (*)(int))sigalarm);
497 #ifdef HAVE_SIGINTERRUPT
498         siginterrupt(SIGALRM,1);
499 #endif
500 }
501
502 static void sigalarm(int signo)
503 {
504         setalarm();
505         wasalarm=1;
506         if (verbose>=1) error("Timed out");
507 }
508
509 static void blocking(char yes)
510 {
511 static char state=-1;
512         if (state==yes) return;
513         if (fcntl(devfd,F_SETFL,(yes?0:O_NONBLOCK)))
514                 error("!fcntl() on device for %sblocking mode: %m",(yes?"":"non-"));
515         state=yes;
516 }
517
518 static const char *record;
519 static char *catchdata;
520 static size_t catchdatal,catchdatasiz;
521
522 static void catched(const char *end)
523 {
524 size_t len;
525 void *p;
526
527         if (!record) return;
528         assert(end>=record);
529         if ((p=memchr(record,'\n',end-record))) end=p;
530         if ((len=end-record)) {
531                 if (catchdatal+len>catchdatasiz)
532                         chk(catchdata=realloc(catchdata,
533                                 (catchdatasiz=MAX(LINE_MAX,(catchdatal+len)*2))));
534                 memcpy(catchdata+catchdatal,record,len);
535                 catchdatal+=len;
536                 }
537         record=(p?NULL:end);
538         assert(catchdatal<=catchdatasiz);
539 }
540
541 static int retrycnt=0;
542 static void retrying(void)
543 {
544         if (++retrycnt>maxretryn) error("!Maximum command retry count (%d) exceeded",maxretryn);
545         if (verbose>=2) error(".Retrying phase, %d out of %d..",retrycnt,maxretryn);
546 }
547
548 static char *devcmd(const char *term,const char *catch,const char *send,...)
549 {
550 size_t l,bufl,terml,catchl,fragl,offs;
551 char buf[LINE_MAX];
552 ssize_t got;
553 char *hit,*s;
554 va_list ap;
555 char errout;
556
557         if (!term) term="\nOK\n";
558         if ((errout=(*send=='!'))) send++;
559         if (0) {
560 err:
561                 alarm(0);
562                 if (errout) return(NULL);
563                 retrying();
564                 }
565         catchdatal=0;
566         va_start(ap,send);
567         l=VARVPRINTF(buf,send,ap); bufl=l+1;
568         va_end(ap);
569         if (bufl>=sizeof(buf)-1) error("!Command too big (%d>%d)",bufl,sizeof(buf)-1);
570         if (verbose>=2) error(".devcmd(send=\"%s\",term=\"%s\",catch=\"%s\")",buf,term,catch);
571         buf[l]='\r'; buf[l+1]='\n';
572         for (offs=0,got=0;offs<bufl;offs++) {
573                 alarm(MAXSENDTIME);
574                 usleep((offs?chartimen:cmdtimen)*1000);
575                 if (!offs && tcflush(devfd,TCIOFLUSH))
576                         error("Error flushing I/O queue of device: %m");
577                 if (write(devfd,buf+offs,1)!=1) break;
578                 got++;
579                 if (tcdrain(devfd))
580                         error("Error forcing output of char %d of cmd \"%s\": %m",offs,buf);
581                 }
582         alarm(0);
583         if (got!=bufl) {
584                 error("Wrote only %d of %d bytes of command: %m",got,bufl);
585                 goto err;
586                 }
587
588         if (!(terml=strlen(term))) {
589                 assert(!catch);
590                 return(NULL);
591                 }
592         if (catch) {
593                 catchl=strlen(catch);
594                 fragl=MAX(terml,catchl);
595                 }
596         else fragl=terml;
597         fragl=MAX(fragl,MAX(strlen(ERROR_SUBSTR1),strlen(ERROR_SUBSTR2)));
598         bufl=0;
599         record=NULL;
600         wasalarm=0;
601         alarm(readtimen);
602         for (;;) {
603                 blocking(0);
604                 errno=0;
605                 got=read(devfd,buf+bufl,sizeof(buf)-1-bufl);
606                 if (got==-1 && errno==EAGAIN) {
607                         blocking(1);
608                         errno=0;
609                         got=read(devfd,buf+bufl,1);
610                         }
611                 if (got<=0) {
612                         if (wasalarm) error("Maximum response timeout (%ds) exceeded",readtimen);
613                         else error("Couldn't read device data (ret=%d): %m",got);
614                         goto err;
615                         }
616                 s=buf+bufl;
617                 bufl+=got;
618                 /* FIXME: '\0' conversion */
619                 while (buf+bufl>s && (s=memchr(s,'\r',buf+bufl-s))) *s='\n';
620                 catched(buf+bufl); assert(!record || record==buf+bufl);
621                 assert(bufl<sizeof(buf));
622                 if (bufl>=fragl) {
623                         buf[bufl]='\0';
624                         assert(strlen(buf)==bufl);
625                         /* d3(">%s|%s<\n",buf,term); */
626                         if (strstr(buf,ERROR_SUBSTR1) || strstr(buf,ERROR_SUBSTR2)) {
627                                 error("Found ERROR response on command \"%s\"",send);
628                                 goto err;
629                                 }
630                         if (catch && bufl>=catchl && (hit=strstr(buf,catch))) {
631                                 record=hit+catchl;
632                                 catched(buf+bufl); assert(!record || record==buf+bufl);
633                                 }
634                         if (         bufl>= terml && (hit=strstr(buf,term))) break;
635                         memmove(buf,buf+bufl-(fragl-1),fragl-1);
636                         bufl=fragl-1;
637                         if (record) record=buf+bufl;
638                         }
639                 }
640         alarm(0);
641         if (!catchdatal) {
642                 if (!catch) return(NULL);
643                 error("Data requested on command \"%s\" but no found after term \"%s\"",send,term);
644                 goto err;
645                 }
646         assert(!!catch);
647         record=buf;
648         buf[0]='\0';
649         catched(buf+1);
650         if (verbose>=2) error(".Returning data \"%s\" for cmd \"%s\"",catchdata,send);
651         return(catchdata);
652 }
653
654 static inline char tohex(unsigned char x)
655 {
656         x&=0x0F;
657         if (x<10) return(x   +'0');
658                   return(x-10+'A');
659 }
660
661 static inline void textconv(char *d,unsigned char *s,size_t len)
662 {
663         while (len--) {
664                 *d++=tohex(*s>>4U);
665                 *d++=tohex(*s    );
666                 s++;
667                 }
668         *d='\0';
669 }
670
671 static inline void smscset(void)
672 {
673 char *s,*t,*e,*serr;
674 long l;
675
676         if (smsc) devcmd(NULL,NULL,"\r\nAT+CSCA=\"%s\"",smsc);
677         s=devcmd(NULL,"\n+CSCA:","\r\nAT+CSCA?");
678         while (isspace(*s)) s++;
679         if (!*s || !strcmp(s,"EMPTY"))
680                 error("!No SMS set in Nokia found");
681         if (verbose>=1) error("\nFound default SMSC in Nokia: %s",s);
682         if (*s!='"') error("!No left-quote found in: %s",s);
683         if (!(t=strrchr(s+1,'"'))) error("!No right-quote found in: %s",s);
684         e=t++;
685         while (isspace(*t)) t++;
686         if (*t++!=',') error("!No comma found after quotes in: %s",s);
687         while (isspace(*t)) t++;
688         l=strtol(t,&serr,10);
689         if ((l!=ADDR_NAT && l!=ADDR_INT) || (serr && *serr))
690                 error("!Type parse error in: %s",s);
691         if (l==ADDR_NAT || s[1]=='+') s++;
692         else *s='+';
693         *e='\0';
694         if (verbose>=2) error("\nDecoded SMSC address: %s",s);
695 }
696
697 /* Logo format shamelessly stolen from GNokii-0.3.0: http://www.gnokii.org/
698  * Beware - Nokia Smart Messaging specification 1.0.0 and 2.0.0 is incompatible
699  * with Nokia current product line implementation
700  * http://www.forum.nokia.com/developers/smartmsg/download/ssm2_0_0.pdf
701  */
702
703 static char hexdata[140*2+1];
704
705 static inline void logoread(void)
706 {
707 FILE *f;
708 char buf[32+140*8+1];
709 unsigned char bin[140]={
710         0x06, /* UDH length */
711         0x05, /* IEI */
712         0x04, /* IEDL */
713         0x15, 0x83, /* dest port (group gfx) */
714         0x00, 0x00  /* src port (unused) */
715         };
716 size_t got,r,w;
717 ssize_t chars,bits;
718 char gsmnetf[10];
719 int sizex,sizey,bit;
720
721 #define WORD(n) (((unsigned char)buf[(n)])|(((unsigned char)buf[(n)+1])<<8))
722
723         if (!(f=fopen(logoname,"rb")))
724                 error("!Cannot open logo file \"%s\" for r/o: %m",logoname);
725         got=fread(buf,1,sizeof(buf),f);
726              if (got>=20 && !memcmp(buf,"NOL",4)) {
727                 VARPRINTF2(gsmnetf,"%03.3u%02.2u",WORD(6),WORD(8));
728                 assert(strlen(gsmnetf)==5);
729                 r=10;
730                 if (verbose>=1) error(".Reading NOL file \"%s\", GSMnet \"%s\", word@4=%d..",
731                         logoname,gsmnetf,WORD(4));
732                 }
733         else if (got>=16 && !memcmp(buf,"NGG",4)) {
734                 r=6;
735                 if (verbose>=1) error(".Reading NGG file \"%s\", word@4=%d..",
736                         logoname,gsmnetf,WORD(4));
737                 }
738         else error("!Unknown file format of logo file \"%s\"");
739         if (!strcasecmp(gsmnet,WORD_NET)) {
740                 if (!*gsmnetf) error("!NOL network code detection requested but NOL file not loaded, please specify network code");
741                 gsmnet=gsmnetf;
742                 }
743         if (!strcasecmp(gsmnet,WORD_GROUP) || !*gsmnet) {
744                 error("\nSending logo as: group graphics");
745                 gsmnet=NULL;
746                 }
747         else {
748                 error("\nSending logo as: operator logo for \"%s\"",gsmnet);
749                 bin[4]=0x82; /* dest port 0x1582 */
750                 }
751         
752         sizex=WORD(r); sizey=WORD(r+2);
753         if (verbose>=2) error(".Magic words: @+4=%d, @+6=%d, @+8=%d",
754                         WORD(r+4),WORD(r+6),WORD(r+8));
755         r+=10;
756         if (sizex<1 || sizex>255
757          || sizey<1 || sizey>255) error("!Invalid size: %dx%d",sizex,sizey);
758         chars=((bits=sizex*sizey)+7)/8;
759         if (r+bits>got) error("!Logo file \"%s\" too short - actual=%d, need(%dx%d)=%d",
760                 logoname,got,sizex,sizey,r+chars);
761         else if (r+bits<got)
762                 if (verbose>=1) error("Ignoring trailing garbage in \"%s\", used only %d bytes",logoname,r+bits);
763         if ((got=(7+(gsmnet?3:0)+4+chars))>140)
764                 error("!SMS size would be %d bytes but 140 is maximum",got);
765         w=7;
766         if (gsmnet) {
767                 bin[w++]=((gsmnet[1]&0x0F)<<4)|(gsmnet[0]&0x0F);
768                 bin[w++]=0xF0                 |(gsmnet[2]&0x0F);
769                 bin[w++]=((gsmnet[4]&0x0F)<<4)|(gsmnet[3]&0x0F);
770                 }
771         bin[w++]=0x00; /* RFU by Nokia */
772         bin[w++]=sizex; bin[w++]=sizey;
773         bin[w++]=0x01; /* one B/W plane */
774         while (chars--) {
775                 bin[w]=0;
776                 for (bit=0x80;(bits>0) && (bit>0);bits--,bit>>=1) {
777                         if (buf[r]!='0' && buf[r]!='1')
778                                 error("!Invalid character (neither '0' nor '1')in logo file \"%s\" at offset 0x%X",
779                                         logoname,r);
780                         if (buf[r++]=='1') bin[w]|=bit;
781                         }
782                 w++;
783                 }
784         assert(chars==-1); assert(bits==0); assert(w==got); assert(w<=140);
785         textconv(hexdata,bin,w);
786         if (verbose>=2) error("\nWill send hexdata: %s",hexdata);
787 }
788
789 static struct {
790         char **sp;
791         long *ip;
792         const char *const msg;
793         } numarg[]={
794                 { &maxretry,&maxretryn,"maxretry" },
795                 { &readtime,&readtimen,"readtime" },
796                 { &chartime,&chartimen,"chartime" },
797                 { &cmdtime ,&cmdtimen ,"cmdtime"  },
798                 { &baud    ,&baudn    ,"baud"     },
799         };
800
801 int main(int argc,char **argv)
802 {
803 char *s;
804 int i;
805 unsigned fatal=0;
806 speed_t portbaud;
807
808         if ((s=strrchr((pname=*argv),'/'))) pname=s+1;
809         atexit(cleanup);
810         signal(SIGTERM,(RETSIGTYPE (*)(int))cleanup);
811         signal(SIGQUIT,(RETSIGTYPE (*)(int))cleanup);
812         signal(SIGINT ,(RETSIGTYPE (*)(int))cleanup);
813         signal(SIGHUP ,(RETSIGTYPE (*)(int))cleanup);
814         processargs(argc,argv,"<command-line>");
815         if ((s=getenv("HOME"))) {
816 size_t l=strlen(s);
817 char *buf=malloc(l+50);
818
819                 memcpy(buf,s,l);
820                 strcpy(buf+l,CONFIG_HOME);
821                 readfile(buf,1);
822                 free(buf);
823                 }
824         readfile(CONFIG_MAIN,1);
825
826         for (i=0;i<NELEM(nullcheck);i++)
827                 if (!*nullcheck[i].var) {
828                         error("Missing parameter \"%s\"",nullcheck[i].name);
829                         fatal++;
830                         }
831         if (fatal) error("!Previous error%s considered unrecoverable",(fatal==1?"":"s"));
832         for (i=0;i<NELEM(emptycheck);i++)
833                 if (*emptycheck[i] && !**emptycheck[i]) {
834                         free(*emptycheck[i]);
835                              *emptycheck[i]=NULL;
836                         }
837         if (!logname) logname=DEF_LOGNAME;
838         if (!lockfile) lockfile=DEF_LOCKFILE;
839         if (!device) device=DEF_DEVICE;
840         logoread();
841
842         for (i=0;i<NELEM(numarg);i++) {
843 char *serr;
844                 if (!*numarg[i].sp) continue;
845                 *numarg[i].ip=strtol(*numarg[i].sp,&serr,0);
846                 if (*numarg[i].ip<0 || *numarg[i].ip>=LONG_MAX || !serr || *serr)
847                         error("!Number parse error for parameter \"%s\" of \"%s\" at: %s",
848                                 numarg[i].msg,*numarg[i].sp,serr);
849                 }
850
851         if (!strchr(device,'/')) {
852 size_t l=strlen(device);
853                 chk(s=malloc(5+l+1));
854                 strcpy(s,"/dev/");
855                 strcpy(s+5,device);
856                 free(device);
857                 device=s;
858                 }
859         devicename=strrchr(device,'/')+1; assert(!!(devicename-1));
860         for (i=0,s=lockfile;*s;s++) {
861                 if (*s!='%') continue;
862                 s++;
863                 if (*s=='%') continue;
864                 if (*s=='s') {
865                         if (i) error("!Only one \"%%s\" permitted in lockfile format-string");
866                         i=1; continue;
867                         }
868                 error("!Invalid format-character '%c' in lockfile format-string, only \"%%s\" allowed",*s);
869                 }
870         
871         if (*logname) {
872                 if (!(logf=fopen(logname,"a")))
873                         error("!Error opening log \"%s\" for append: %m",logname);
874                 logmsg("Starting up: " PACKAGE " " VERSION);
875                 }
876         switch (baudn) {
877                 case  2400: portbaud= B2400; break;
878                 case  4800: portbaud= B4800; break;
879                 case  9600: portbaud= B9600; break;
880                 case 19200: portbaud=B19200; break;
881                 case 38400: portbaud=B38400; break;
882                 case 57600: portbaud=B57600; break;
883                 default:
884                         error("!Specified baudrate %d is not supported",baudn);
885                 }
886         if (verbose>=2) error(".Will use baudrate %d with hexval 0x%X",baudn,portbaud);
887                 
888         if (lockfile && *lockfile && VARPRINTF(lockreal,lockfile,devicename)>0) {
889 time_t start,end;
890                 if (verbose>=1) error(".Locking device \"%s\" by \"%s\"..",device,lockreal);
891                 time(&start);
892                 lockdevice();
893                 time(&end);
894                 if ((end-=start)>LOCKREPORT)
895                         logmsg("Device lock succeeded after %d seconds",end);
896                 }
897         if (verbose>=1) error(".Opening device \"%s\"..",device);
898         if ((devfd=open(device,O_RDWR|O_NDELAY))<0)
899                 error("!Cannot open device \"%s\" for rw-access: %m",device);
900         
901         if (tcgetattr(devfd,&restios))
902                 error("Unable to get termios settings: %m");
903         else {
904                 restios.c_cflag=(restios.c_cflag&~(CBAUD|CBAUDEX))|B0|HUPCL;
905                 restios_yes=1;
906                 }
907         tios.c_iflag=IGNBRK|IGNPAR|IXON|IXOFF;
908         tios.c_oflag=0;
909         tios.c_cflag=CS8|CREAD|CLOCAL|HUPCL|portbaud;
910         tios.c_lflag=IEXTEN|NOFLSH;
911         memset(tios.c_cc,_POSIX_VDISABLE,sizeof(tios.c_cc));
912         tios.c_cc[VTIME]=0;
913         tios.c_cc[VMIN ]=1;
914             cfsetispeed(&tios,portbaud);
915         if (cfsetospeed(&tios,portbaud)|cfsetispeed(&tios,portbaud))
916                 error("Error setting termios baudrate on device: %m");
917         if (tcflush(devfd,TCIOFLUSH))
918                 error("Error flushing termios (TCIOFLUSH) on device: %m");
919         if (tcsetattr(devfd,TCSANOW,&tios))
920                 error("!Unable to set initial termios device settings: %m");
921
922         setalarm();
923         do {
924                 devcmd("",NULL,"\r\nAT\033");
925                 devcmd(NULL,NULL,"\r\nAT");
926                 smscset();
927                 devcmd(NULL,NULL,"\r\nAT+CSMP=81,,0,245");
928                 devcmd("\n> ",NULL,"\r\nAT+CMGS=\"%s\"",phone);
929                 if (!(s=devcmd(NULL,"\n+CMGS:","!%s\032",hexdata))) {
930                         retrying();
931                         continue;
932                         }
933                 devcmd(NULL,NULL,"\r\nAT+CSMP=17,,0,0");
934                 devcmd(NULL,NULL,"\r\nAT");
935                 } while (0);
936         while (isspace(*s)) s++;
937         if (verbose>=1) error("\nMessage successfuly sent with MR: %s",s);
938         devcmd(NULL,NULL,"\r\nAT");
939
940         logmsg("SMS sent (after %d retries), message reference: %s",retrycnt,s);
941         return(EXIT_SUCCESS);
942 }