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