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