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