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