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