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