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