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