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