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