845c6297691f88a198dd2a8d495f33aa32cab68f
[mdsms.git] / mdsms.c
1 #include "config.h"
2 #ifndef lint
3 static char rcsid[] ATTR_UNUSED = "$Id$";
4 #endif
5
6 #include "setup.h"
7
8 #ifdef HAVE_STDIO_H
9 #include <stdio.h>
10 #endif
11 #ifdef HAVE_STDLIB_H
12 #include <stdlib.h>
13 #endif
14 #ifdef HAVE_STRING_H
15 #include <string.h>
16 #endif
17 #ifdef HAVE_SIGNAL_H
18 #include <signal.h>
19 #endif
20 #ifdef HAVE_STDARG_H
21 #include <stdarg.h>
22 #endif
23 #ifdef HAVE_LIMITS_H
24 #include <limits.h>
25 #endif
26 #ifdef HAVE_CTYPE_H
27 #include <ctype.h>
28 #endif
29 #ifdef HAVE_TERMIOS_H
30 #include <termios.h>
31 #endif
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif
35 #ifdef HAVE_ASSERT_H
36 #include <assert.h>
37 #endif
38 #ifdef HAVE_SYS_TYPES_H
39 #include <sys/types.h>
40 #endif
41 #ifdef HAVE_SYS_STAT_H
42 #include <sys/stat.h>
43 #endif
44 #ifdef HAVE_FCNTL_H
45 #include <fcntl.h>
46 #endif
47 #ifdef HAVE_ERRNO_H
48 #include <errno.h>
49 #endif
50 #ifdef HAVE_TIME_H
51 #include <time.h>
52 #endif
53 #ifdef HAVE_SYS_TIME_H
54 #include <sys/time.h>
55 #endif
56 #ifdef HAVE_SYS_POLL_H
57 #include <sys/poll.h>
58 #endif
59
60 #ifdef HAVE_GETOPT_LONG
61 #include <getopt.h>
62 #else
63 #include "getopt.h"
64 #endif
65
66 #define NELEM(x) (sizeof((x))/sizeof(*(x)))
67
68 #ifndef DEBUG
69 #define dbg(cmd)
70 #else
71 #define dbg(cmd) cmd
72 #endif
73 /* ANSI C does not allow macro with variable arguments */
74 #define dO stderr
75 #define dB(a) dbg(fprintf a)
76
77 #define d1(n1)                      dB((dO,n1                     ))
78 #define d2(n1,n2)                   dB((dO,n1,n2                  ))
79 #define d3(n1,n2,n3)                dB((dO,n1,n2,n3               ))
80 #define d4(n1,n2,n3,n4)             dB((dO,n1,n2,n3,n4            ))
81 #define d5(n1,n2,n3,n4,n5)          dB((dO,n1,n2,n3,n4,n5         ))
82 #define d6(n1,n2,n3,n4,n5,n6)       dB((dO,n1,n2,n3,n4,n5,n6      ))
83 #define d7(n1,n2,n3,n4,n5,n6,n7)    dB((dO,n1,n2,n3,n4,n5,n6,n7   ))
84 #define d8(n1,n2,n3,n4,n5,n6,n7,n8) dB((dO,n1,n2,n3,n4,n5,n6,n7,n8))
85
86 static const char version[]="This is Mobile Device SMS tool (" PACKAGE " " VERSION ")\n";
87
88 static int verbose
89 #ifdef DEBUG
90         =0xFFFF
91 #endif
92         ;
93 static char *pname;
94 static int dis_cleanup=0,devfd=-1;
95
96 static char *phone,*device,*logname,*lockfile,*smsc,*maxretry,*readtime,*chartime,*cmdtime,*baud,*restore;
97 static int readbody;
98 static long maxretryn=DEF_MAXRETRY,readtimen=-1,chartimen=DEF_CHARTIME,cmdtimen=DEF_CMDTIME,baudn=DEF_BAUD;
99 static size_t bodylen;
100 /* --send / --send-mobildock / --receive specific */
101 static char *body;
102 /* --logo-send specific */
103 static char *logoname,*gsmnet;
104
105 static enum modenum {
106   MODE_UNKNOWN=0,
107 /* must differ from regular char-s */
108   MODE_FIRST         =0x3400,
109         MODE_SEND          =MODE_FIRST+0, /* --send / --send-mobildock */
110         MODE_SEND_MOBILDOCK=MODE_FIRST+1, /* --send-mobildock in before readtimen is set */
111         MODE_RECEIVE       =MODE_FIRST+2, /* --receive */
112         MODE_LOGO_SEND     =MODE_FIRST+3  /* --logo-send */
113         } mode=MODE_UNKNOWN;
114 #define MODE_ORDER(x) ((x)-MODE_FIRST)
115 #define MODE_NAME(x) (longopts[MODE_ORDER((x))].name)
116 #define MODE_BIT(x) (1<<MODE_ORDER((x)))
117
118 static unsigned mode_stamp;
119
120 /* pdusmsc variable has to be filled in */
121 #define NEED_PDUSMSC() (mode==MODE_SEND)
122
123 static char *devicename; /* path stripped */
124 static char lockreal[512],locked;
125
126 static struct termios restios,tios;
127 static char restios_yes;
128 static FILE *logf;
129
130 static void vlogmsg(char pm,char fatal,const char *fmt,va_list ap) ATTR_PRINTFORMAT(3,0);
131 static void vlogmsg(char pm,char fatal,const char *fmt,va_list ap)
132 {
133 time_t stamp;
134 char *ctm,*s;
135 pid_t mypid=-1;
136 char host[LINE_MAX];
137
138         if (!logf) return;
139         if (mypid==-1) {
140                 mypid=getpid();
141                 if (gethostname(host,sizeof(host))) strcpy(host,"<ERROR>");
142                 }
143         time(&stamp);
144         ctm=ctime(&stamp);
145         if ((s=strchr(ctm,'\n'))) *s='\0';
146         fprintf(logf,"%s %s %s[%d]: ",ctm,host,pname,mypid);
147         vfprintf(logf,fmt,ap);
148         if (pm) { fputs(": ",logf); fputs(strerror(errno),logf); }
149         if (fatal!='\n') fputc((fatal=='.'?'.':'!'),logf);
150         fputc('\n',logf);
151         fflush(logf);
152 }
153
154 static void logmsg(const char *fmt,...) ATTR_PRINTFORMAT(1,2);
155 static void logmsg(const char *fmt,...)
156 {
157 va_list ap;
158         va_start(ap,fmt);
159         vlogmsg(0,'\n',fmt,ap);
160         va_end(ap);
161 }
162
163 static void error(const char *fmt,...) ATTR_PRINTFORMAT(1,2);
164 static void error(const char *fmt,...)
165 {
166 va_list ap;
167 char fatal,pm;
168
169         if ((pm=(*fmt=='^'))) fmt++;
170         fatal=*fmt;
171         if (fatal=='!' || fatal=='.' || fatal=='\n') fmt++;
172         else fatal=0;
173
174         fprintf(stderr,"%s: ",pname);
175         va_start(ap,fmt);
176         vfprintf(stderr,fmt,ap);
177         if (fatal=='!') vlogmsg(pm,fatal,fmt,ap);
178         va_end(ap);
179         if (pm) { fputs(": ",stderr); fputs(strerror(errno),stderr); }
180         if (fatal!='\n') fputc((fatal=='.'?'.':'!'),stderr);
181         fputc('\n',stderr);
182         if (fatal=='!') exit(EXIT_FAILURE);
183 }
184
185 static void chk(const void *p)
186 {
187         if (p) return;
188         error("!Virtual memory exhausted");
189 }
190
191 static char *devcmd(const char *term,const char *catch,const char *send,...) ATTR_PRINTFORMAT(3,4);
192
193 static void unlockdevice(int hard)
194 {
195         d2("unlockdevice(), locked=%d\n",locked);
196         if (!locked || !*lockreal) return;
197         if (!hard && locked>1) { locked--; return; }
198         d2("Removing lockfile \"%s\"\n",lockreal);
199         if (unlink(lockreal))
200                 error("^Error removing my device lockfile \"%s\"",lockreal);
201         locked=0;
202 }
203
204 static void cleanup(void)
205 {
206         d1("cleanup()\n");
207         if (dis_cleanup) return;
208         if (restore) {
209 char *cmd=restore;
210                 restore=NULL;
211                 devcmd(NULL,NULL,"\r\nAT");
212                 devcmd(NULL,NULL,cmd);
213                 devcmd(NULL,NULL,"\r\nAT");
214                 }
215         if (restios_yes) {
216                 if (tcsetattr(devfd,TCSANOW,&restios))
217                         error("^Error restoring termios for device");
218                 restios_yes=0;
219                 }
220         unlockdevice(1);
221         dis_cleanup=1;
222         exit(EXIT_FAILURE);
223 }
224
225 static void usage(void)
226 {
227         fprintf(stderr,"\
228 \n\
229 %s\
230 \n\
231 Usage: " PACKAGE " [-c|--config <cfgfile>] [-d|--device <device>]\n\
232              {--send | --send-mobildock | --receive | --logo-send}\n\
233              [-L|--log <file>] [-b|--baud <rate>]\n\
234              [-l|--lockfile <lock>] [-s|--smsc <smsc #>] [-m|--maxretry <#>]\n\
235              [-r|--readtime <sec>] [-t|--chartime <msec>] [-T|--cmdtime <msec>]\n\
236              [-v|--verbose] [-h|--help] [-V|--version]\n\
237   --send / --send-mobildock:\n\
238              [-f|--file] <dest. phone> <msg text|msg filename>\n\
239   --receive:\n\
240              <command name>\n\
241   --logo-send:\n\
242              <dest. phone> <logo filename> [<GSMnet id>]\n\
243 \n\
244  -c, --config\tRead this additional config file\n\
245 \t\t(def. \"" CONFIG_MAIN "\" and \"$HOME" CONFIG_HOME "\")\n\
246  -d, --device\tMobile on this serial device (def. \"" DEF_DEVICE "\")\n\
247  -L, --log\tLog all important messages to this file (def. \"" DEF_LOGNAME "\")\n\
248  -b, --baud\tSet baudrate, 2400-57600 supported (def. %d)\n\
249  -l, --lockfile\tLock serial port by this file, \"%%s\" is basename of device\n\
250 \t\t(def. \"%s\")\n\
251  -s, --smsc\tUse this SMS Center number (def. query from mobile)\n\
252  -m, --maxretry\tMaximum retries of any command before giving up (def. %d)\n\
253  -r, --readtime\tSeconds for maximum wait time for response\n\
254 \t\t(def. %ds standard, %ds for MobilDock modes, multiplied %dx for long cmds)\n\
255  -t, --chartime\tMilliseconds between each char (def. %dms)\n\
256  -T, --cmdtime\tMilliseconds before each whole AT command (def. %dms)\n\
257  -v, --verbose\tIncrease verbosity level, more \"-v\"s give more messages\n\
258  -h, --help\tPrint a summary of the options\n\
259  -V, --version\tPrint the version number\n\
260 \n\
261 --send / --send-mobildock:\n\
262  -f, --file\tRead contents of message from file instead\n\
263 --receive:\n\
264  <command name>\tProgram to run on receive, message will be on stdin\n\
265 \t\tFollowing substitutes are recognized:\n\
266 \t\t%%p - source phone number\n\
267 \t\t%%T - timestamp from SMSC as number of seconds from 1970\n\
268 \t\t%%t - ctime(3) style timestamp (e.g. \"Wed Jun 30 21:49:08 1993\")\n\
269 --logo-send:\n\
270  <GSMnet id>\t* Oper. logo: Enter custom network code MccMnc, e.g. 23002\n\
271 \t\t* Oper. logo: Specify \"" WORD_NET "\" to read network code from NOL file\n\
272 \t\t* Group gfx : Specify \"" WORD_GROUP "\" to send logo as group graphics\n\
273 \n\
274 You may need to use the following line to catch all of this help text:\n\
275 ./mdsms 2>&1|more\n\
276 \n",version,DEF_BAUD,DEF_LOCKFILE,DEF_MAXRETRY,
277 DEF_READTIME,DEF_READTIME_MOBILDOCK,EXT_READTIME,DEF_CHARTIME,DEF_CMDTIME);
278         exit(EXIT_FAILURE);
279 }
280
281 static const struct option longopts[]={
282 /* Modes has to be in-order on exact positions */
283 {"send"          ,0,0,MODE_SEND},
284 {"send-mobildock",0,0,MODE_SEND_MOBILDOCK},
285 {"receive"       ,0,0,MODE_RECEIVE},
286 {"logo-send"     ,0,0,MODE_LOGO_SEND},
287 /* Mode aliases may follow in no particular order *
288  * as long as no non-mode options is between them */
289 {"send-md"       ,0,0,MODE_SEND_MOBILDOCK},
290 {"recv"          ,0,0,MODE_RECEIVE},
291 {"config"  ,1,0,'c'},
292 {"device"  ,1,0,'d'},
293 {"log"     ,1,0,'L'},
294 {"baud"    ,1,0,'b'},
295 {"lockfile",1,0,'l'},
296 {"smsc"    ,1,0,'s'},
297 {"maxretry",1,0,'m'},
298 {"readtime",1,0,'r'},
299 {"chartime",1,0,'t'},
300 {"cmdtime" ,1,0,'T'},
301 {"file"    ,0,0,'f'},
302 {"verbose" ,0,0,'v'},
303 {"help"    ,0,0,'h'},
304 {"version" ,0,0,'V'}};
305
306 static void processargs(int argp,char **args,const char *from);
307
308 static char *cfgstack[MAXCFGLOOP];
309 static unsigned cfgstacki=0;
310
311 static void chkfclose(FILE *f,const char *fname)
312 {
313         if (fclose(f))
314                 error("^Error closing \"%s\"",fname);
315 }
316
317 static void readfile(const char *fname,char quiet)
318 {
319 FILE *f;
320 size_t got;
321 char *args[MAXCFGARGS],*d,*s,blank,quote;
322 unsigned argp;
323 char *buf;
324 long size;
325 static unsigned tot=0;
326
327         if (tot++>=MAXCFGNUM) {
328                 if (tot==MAXCFGNUM+1) error("Too many config files to read, max is %d, break-out",MAXCFGNUM);
329                 return;
330                 }
331         if (!(f=fopen(fname,"rt"))) {
332                 if (!quiet) error("^Can't open config file \"%s\" for r/o",fname);
333                 return;
334                 }
335                 
336         if (verbose>=2) error(".Reading config file \"%s\"",fname);
337         if (fseek(f,0,SEEK_END))
338                 error("^Error seeking to end of \"%s\"",fname);
339         if ((size=ftell(f))<0)
340                 size=0,error("^Error measuring \"%s\"",fname);
341         if (size>MAXCONFIG) 
342                 error("File \"%s\" is too long, read only %d bytes",fname,MAXCONFIG);
343         chk(buf=malloc((size?size:MAXCONFIG)+1));
344         rewind(f);
345         got=fread(buf,1,(size?size:MAXCONFIG),f);
346         if (size && got!=size)
347                 error("File \"%s\" read error, got only %u bytes of %ld",fname,got,size);
348         chkfclose(f,fname);
349         buf[got]='\0';
350         args[0]=pname;
351         for (argp=1,d=s=buf,blank=1,quote=0;s<buf+got;s++) {
352 char c=*s;
353
354                 if (!quote && isspace(c)) {
355                         if (!blank) {
356                                 *d='\0';
357                                 blank=1;
358                                 if (verbose>=2) error("\nConfig \"%s\": arg#%d: %s",fname,argp-1,args[argp-1]);
359                                 }
360                         continue;
361                         }
362                 if (blank) {
363                         if (argp>=NELEM(args)-1) {
364                                 error("Too many arguments in \"%s\", from offset %d ignored",fname,s-buf);
365                                 break;
366                                 }
367                         args[argp++]=s;
368                         d=s;
369                         blank=0;
370                         }
371                 if (c=='\\') { *d++=*++s; continue; }
372                 if (c=='"' || c=='\'') {
373                         if (!quote   ) { quote=c; continue; }
374                         if ( quote==c) { quote=0; continue; }
375                         /* FALLTHRU */
376                         }
377                 *d++=c;
378                 }
379         args[argp]=NULL;
380         processargs(argp,args,fname);
381         free(buf);
382 }
383
384 static struct argstack {
385         struct argstack *next;
386         int num,offset;
387         const char *from;
388         char *arg[1];
389         } *argstack;
390
391 static struct argstack **argstack_tail=&argstack;
392 static size_t lastargstack_len;
393 static const char *lastargstack_from;
394 static int lastargstack_index;
395
396 static size_t argstack_size;
397 static int argstack_num;
398
399 static void pushargstack(char **args,int num,int offset,const char *from,char stack)
400 {
401 struct argstack *as;
402 int i;
403
404         if (!num) return;
405         assert(num>=1);
406         chk(as=malloc(sizeof(*as)+sizeof(as->arg)*(num-1)));
407         as->num=num;
408         as->offset=offset;
409         if (!from) as->from=NULL;
410         else chk(as->from=strdup(from));
411         for (i=0;i<num;i++) {
412                 chk(as->arg[i]=strdup(args[i]));
413                 argstack_size+=strlen(args[i]);
414                 }
415         argstack_num+=num;
416         if (stack) {
417                 as->next=argstack;
418                 argstack=as;
419                 }
420         else {
421                 as->next=NULL;
422                 *argstack_tail=as;
423                 argstack_tail=&as->next;
424                 }
425 }
426
427 static void pushargstack_one(char *s,char stack)
428 { pushargstack(&s,1,0,NULL,stack); }
429
430 static char *nextargstack(void)
431 {
432 static int order=0;
433 char *r;
434
435         if (argstack && order==argstack->num) {
436 struct argstack *as=argstack;
437                 if (!(argstack=as->next))
438                         argstack_tail=&argstack;
439                 order=0;
440                 free((char *)as->from);
441                 lastargstack_from=NULL;
442                 free(as);
443                 }
444         if (!argstack) {
445                 assert(!argstack_num); assert(!argstack_size);
446                 return(NULL);
447                 }
448         assert(order<argstack->num);
449         lastargstack_index=argstack->offset+order;
450         r=argstack->arg[order++];
451         assert(argstack_num>0); argstack_num--;
452         lastargstack_len=strlen(r);
453         assert(argstack_size>=lastargstack_len); argstack_size-=lastargstack_len;
454         lastargstack_from=argstack->from;
455         return(r);
456 }
457
458 static char *glueargstack(size_t *destlenp,const char *glue)
459 {
460 size_t gluel=(glue?strlen(glue):0),destlen;
461 char *dest,*d,*s;
462
463         if (!argstack_num) {
464                 chk(dest=strdup(""));
465                 if (destlenp) *destlenp=0;
466                 return(dest);
467                 }
468         destlen=argstack_size+(argstack_num-1)*gluel;
469         if (destlenp) *destlenp=destlen;
470         chk(dest=malloc(destlen+1));
471         for (d=dest;(s=nextargstack());) {
472                 memcpy(d,s,lastargstack_len);
473                 free(s);
474                 d+=lastargstack_len;
475                 if (!argstack_num) break;
476                 if (!glue) continue;
477                 memcpy(d,glue,gluel);
478                 d+=gluel;
479                 assert(d<=dest+destlen);
480                 }
481         assert(!argstack_num);
482         assert(d==dest+destlen);
483         *d='\0';
484         return(dest);
485 }
486
487 static struct {
488         const char c;
489         char **const var;
490         unsigned stamp;
491         } optset[]={
492                 { 'd',&device   },
493                 { 'L',&logname  },
494                 { 'b',&baud     },
495                 { 'l',&lockfile },
496                 { 's',&smsc     },
497                 { 'm',&maxretry },
498                 { 'r',&readtime },
499                 { 't',&chartime },
500                 { 'T',&cmdtime  },
501         };
502
503 static void processargs(int argp,char **args,const char *from)
504 {
505 int optc;
506 static unsigned seq=0;
507 int i;
508
509         seq++;
510         optarg=NULL; optind=0; /* FIXME: Possible portability problem. */
511         while ((optc=getopt_long(argp,args,"c:d:L:b:l:s:m:r:t:T:fvhV",longopts,NULL))!=EOF) switch (optc) {
512                 case 'c':
513                         if (cfgstacki>=NELEM(cfgstack)) {
514                                 error("Looping (%d) during attempt to read config file \"%s\", break-out",NELEM(cfgstack),from);
515                                 break;
516                                 }
517                         chk(cfgstack[cfgstacki++]=strdup(optarg));
518                         break;
519                 case 'd': case 'L': case 'b': case 'l': case 's': case 'm': case 'r': case 't': case 'T':
520                         for (i=0;i<NELEM(optset);i++)
521                                 if (optset[i].c==optc) {
522                                         if (optset[i].stamp && optset[i].stamp!=seq) {
523                                                 assert(!!*optset[i].var);
524                                                 break;
525                                                 }
526                                         free(*optset[i].var);
527                                         chk(*optset[i].var=strdup(optarg));
528                                         optset[i].stamp=seq;
529                                         break;
530                                         }
531                         assert(i<NELEM(optset));
532                         break;
533                 case MODE_SEND:
534                 case MODE_SEND_MOBILDOCK:
535                 case MODE_RECEIVE:
536                 case MODE_LOGO_SEND:
537                         if (mode_stamp && mode_stamp!=seq) break;
538                         mode=optc;
539                         mode_stamp=seq;
540                         break;
541                 case 'f':
542                         readbody++;
543                         break;
544                 case 'v':
545                         verbose++;
546                         break;
547                 case 'V':
548                         fprintf(stderr,version);
549                         exit(EXIT_FAILURE);
550                 default:
551                         if (optc!='h')
552                                 error("\nLast getopt(3) error occured during parsing option %d from \"%s\"! Follows help:",optind-1,from);
553                         usage();
554                         break;
555                 }
556         pushargstack(args+optind,argp-optind,optind,from,1);
557         while (cfgstacki) {
558 char *s=cfgstack[--cfgstacki];
559
560                 assert(cfgstacki>=0);
561                 readfile(s,0);
562                 free(s);
563                 assert(cfgstacki>=0 && cfgstacki<NELEM(cfgstack));
564                 }
565 }
566
567 static const struct nullcheck {
568         char **var;
569         enum modenum reqd;
570         const char *name;
571         } nullcheck[]={
572                 {&phone,MODE_BIT(MODE_SEND)|MODE_BIT(MODE_SEND_MOBILDOCK)|MODE_BIT(MODE_LOGO_SEND),
573                         "destination phone number"},
574                 {&logoname,MODE_BIT(MODE_LOGO_SEND),"logo filename"},
575                 {&body,MODE_BIT(MODE_RECEIVE),"body text"}, /* we allow empty bodies for SENDs */
576 #if 0
577                 {&gsmnet,"GSM operator network code",MODE_LOGO_SEND},
578                 {&device,"device for communication",0},
579 #endif
580         };
581 static char **emptycheck[]={&logname,&smsc,&logoname,&gsmnet};
582
583 static inline void emptyclean(void)
584 {
585 int i;
586
587         for (i=0;i<NELEM(emptycheck);i++)
588                 if (*emptycheck[i] && !**emptycheck[i]) {
589                         free(*emptycheck[i]);
590                              *emptycheck[i]=NULL;
591                         }
592 }
593
594 static inline void cmdline_done(void)
595 {
596 char *s;
597         while ((s=nextargstack())) {
598                 error("\nExcessive option %d from \"%s\" ignored: %s",
599                         lastargstack_index,lastargstack_from,s);
600                 free(s);
601                 }
602         emptyclean();
603 }
604
605 static char *check_phone(const char *phone)
606 {
607 const char *s,*s1;
608 static char err[LINE_MAX];
609
610         for (s=s1=(phone+(*phone=='+'));*s && s-s1<MAXNUMLEN;s++)
611                 if (!isdigit(*s)) {
612                         VARPRINTF2(err,"Invalid digit '%c' in phone number - at offset %d",
613                                 *s,s-phone);
614                         return(err);
615                         }
616         if (!*s) return(NULL);
617         VARPRINTF2(err,"Phone number too long (%d), max. %d digits allowed",
618                 strlen(s1),MAXNUMLEN);
619         return(err);
620 }
621
622 static void cmdline_phone(void)
623 {
624         if (!phone && (phone=nextargstack())) {
625 char *s;
626
627                 if ((s=check_phone(phone)))
628                         error("!%s in option %d from \"%s\": %s",
629                                         s,lastargstack_index,lastargstack_from,phone);
630                 }
631 }
632
633 static inline void cmdline_receive(void)
634 {
635 char *s;
636
637         if (!body && argstack_num) {
638                 body=glueargstack(&bodylen," ");
639                 for (s=body;(s=strchr(s,'%'));s++)
640                         switch (*++s) {
641                                 case 'p': case 'T': case 't': break;
642                                 default:
643                                         error("Unknown formatsymbol '%c', use (\"%%%%%c\" to fix it) at pos %d in: %s",
644                                                 *s,*s,s-body,body);
645                                 }
646                 }
647 }
648
649 static inline void cmdline_send(void)
650 {
651         cmdline_phone();
652         if (!body && argstack_num)
653                 body=glueargstack(&bodylen," ");
654 }
655
656 static inline void cmdline_logo_send(void)
657 {
658 char *ogsmnet;
659
660         cmdline_phone();
661         if (!logoname) logoname=nextargstack();
662         if (!gsmnet && (ogsmnet=nextargstack())) {
663 char *s,*d,e=0;
664
665                 chk(gsmnet=strdup(ogsmnet));
666                 if (strtrycasecmp(gsmnet,WORD_NET) && strtrycasecmp(gsmnet,WORD_GROUP)) {
667                         for (d=s=gsmnet;*s;s++) {
668                                 if (isdigit(*s)) { *d++=*s; continue; }
669                                 if (isspace(*s)) continue;
670                                 error("\nInvalid characted '%c' in GSMnet at offs %d: %s",
671                                         *s,s-gsmnet,ogsmnet);
672                                 e=1;
673                                 break;
674                                 }
675                         if ((d-gsmnet)!=5) {
676                                 error("\nGSMnet is required to have exactly 5 digits or to be\n\
677 either \"" WORD_NET "\" or \"" WORD_GROUP "\", but found length %d: %s",
678                                         d-gsmnet,ogsmnet);
679                                 e=1;
680                                 }
681                         if (!e) *d='\0';
682                         else {
683                                 error("\nGSMnet option %d from \"%s\" rejected due to previous errors: %s",
684                                         lastargstack_index,lastargstack_from,ogsmnet);
685                                 free(gsmnet);
686                                 gsmnet=NULL;
687                                 }
688                         }
689                 }
690 }
691
692 static void lockclose(int fd)
693 {
694         if (close(fd))
695                 error("Error closing lockfile \"%s\"",lockreal);
696 }
697
698 static inline int lockdevice(int attempt)
699 {
700 int fd=-1;
701 char buf[64];
702 ssize_t got;
703 int delay=0;
704 char empty=0;
705 pid_t pid;
706
707         d2("lockdevice(), locked=%d\n",locked);
708         if (locked) return (++locked);
709         for (;;) {
710                 if (fd!=-1) lockclose(fd);
711 recheck:
712                 if (delay) sleep(delay);
713                 delay=DEVLOCK_PERIOD;
714                 if (verbose>=3) error(".Checking the lockfile \"%s\"..",lockreal);
715                 if ((fd=open(lockreal,O_RDONLY))==-1) break;
716                 if ((got=read(fd,buf,sizeof(buf)-1))<=0) {
717 isempty:
718                         if (empty>=DEVLOCK_MAXEMPTY) {
719                                 error(".Lockfile \"%s\" is still not valid, removing it",lockreal);
720                                 goto remove;
721                                 }
722                         empty++;
723                         continue;
724                         }
725                 assert(got<sizeof(buf));
726                 buf[got]='\0';
727                 if (sscanf(buf,"%d",&pid)!=1) goto isempty;
728                 empty=0;
729                 errno=0;
730                 if (kill(pid,0) && errno!=ESRCH && errno!=EPERM)
731                         error("^Error during checking consciousness of PID %d",pid);
732                 if (errno!=ESRCH) {
733                         if (attempt) return(0);
734                         continue;
735                         }
736                 error(".Lockfile \"%s\" is stale (PID %d), removing it",lockreal,pid);
737 remove:
738                 lockclose(fd);
739                 if (unlink(lockreal))
740                         error("^Error removing foreign lockfile \"%s\"",lockreal);
741                 break;
742                 }
743         errno=0;
744         if ((fd=open(lockreal,O_WRONLY|O_CREAT|O_EXCL,0644))==-1) {
745                 if (errno==EEXIST) goto recheck;
746                 error("^!Error creating lockfile \"%s\"",lockreal);
747                 }
748         locked=1;
749         got=VARPRINTF(buf,"%010d\n",getpid()); assert(got==11);
750         if (write(fd,buf,got)!=got)
751                 error("^!Error writing data to lockfile \"%s\"",lockreal);
752         lockclose(fd);
753         return((locked=1));
754 }
755
756 static char wasalarm=0;
757 static void sigalarm(int signo);
758
759 static void setalarm(void)
760 {
761         signal(SIGALRM,(RETSIGTYPE (*)(int))sigalarm);
762 #ifdef HAVE_SIGINTERRUPT
763         siginterrupt(SIGALRM,1);
764 #endif
765 }
766
767 static void sigalarm(int signo)
768 {
769         setalarm();
770         wasalarm=1;
771         if (verbose>=1) error("Timed out");
772 }
773
774 static void blocking(char yes)
775 {
776 static char state=-1;
777         if (state==yes) return;
778         if (fcntl(devfd,F_SETFL,(yes?0:O_NONBLOCK)))
779                 error("^!fcntl() on device for %sblocking mode",(yes?"":"non-"));
780         state=yes;
781 }
782
783 static const char *record;
784 static char *catchdata;
785 static size_t catchdatal,catchdatasiz;
786
787 static void catched(const char *end)
788 {
789 size_t len;
790 const char *p1,*p2;
791
792         if (!record) return;
793         assert(end>=record);
794         p1=memchr(record,'\n',end-record);
795         p2=memchr(record,'\r',end-record);
796         if (!p1 || (p1 && p2 && p2<p1)) p1=p2;
797         if ((len=(p1?p1:end)-record)) {
798                 if (!catchdata)
799                         chk(catchdata=malloc((catchdatasiz=LINE_MAX)));
800                 if (catchdatal+len>catchdatasiz)
801                         chk(catchdata=realloc(catchdata,
802                                 (catchdatasiz=(catchdatal+len)*2)));
803                 memcpy(catchdata+catchdatal,record,len);
804                 catchdatal+=len;
805                 }
806         record=(p1?NULL:end);
807         assert(catchdatal<=catchdatasiz);
808 }
809
810 static int retrycnt=0;
811 static void retrying(void)
812 {
813         if (maxretryn>=0 && ++retrycnt>maxretryn) error("!Maximum command retry count (%ld) exceeded",maxretryn);
814         if (verbose>=2) error(".Retrying phase, %d out of %ld..",retrycnt,maxretryn);
815 }
816
817 static char *reform(const char *s,int slot)
818 {
819 static struct formslot {
820         char *s;
821         size_t l;
822         } arr[3];
823 char c,*d;
824 struct formslot *fs;
825
826         assert(slot>=0 && slot<NELEM(arr));
827         if (!s) return("<unset>");
828         if (!(fs=&arr[slot])->s)
829                 chk(fs->s=malloc(fs->l=LINE_MAX));
830         d=fs->s;
831         for (*d++='"';(c=*s);s++) {
832                 if (d>=fs->s+fs->l-10) {
833 off_t o=d-fs->s;
834                         chk(fs->s=realloc(fs->s,(fs->l=(fs->l?fs->l*2:LINE_MAX))));
835                         d=fs->s+o;
836                         }
837                 if (c!='\\' && c!='"' && isprint(c)) { *d++=c; continue; }
838                 *d++='\\';
839                 switch (c) {
840                         case '\\': case '"': *d++=c; break;
841                         case '\n': *d++='n'; break;
842                         case '\r': *d++='r'; break;
843                         case '\032': *d++='Z'; break;
844                         case '\033': *d++='e'; break;
845                         default:
846                                 d+=sprintf(d,"x%02X",(unsigned char)c);
847                                 break;
848                         }
849                 }
850         *d++='"'; *d='\0';
851         return(fs->s);
852 }
853
854 static char *devcmd(const char *term,const char *catch,const char *send,...) ATTR_PRINTFORMAT(3,4);
855 static char *devcmd(const char *term,const char *catch,const char *send,...)
856 {
857 size_t l,bufl2,terml,catchl,fragl,offs;
858 static char buf[LINE_MAX];
859 static size_t bufl;
860 ssize_t got;
861 char *hit,*s;
862 va_list ap;
863 char errout,extend,convcr;
864 long alarmtime;
865 const char *osend;
866 static const char emptystring[]="";
867
868         if (!term) term="\nOK\n";
869         convcr=!strchr(term,'\r');
870         if (!strcmp(send," ")) send=NULL; /* GCC formatstring-check workaround */
871         if (!(osend=send)) send="";
872         if ((errout=(*send=='!'))) send++;
873         errout|=(maxretryn==-1);
874         if ((extend=(*send=='~'))) send++;
875         alarmtime=readtimen*(extend?EXT_READTIME:1);
876         d8("devcmd(), alarmtime=%ld, errout=%d, extend=%d, convcr=%d, osend=%p, bufl=%d, buf: %s\n",
877                 alarmtime,errout,extend,convcr,osend,bufl,reform(buf,0));
878         if (0) {
879 err:
880                 alarm(0);
881                 if (errout) return(NULL);
882                 retrying();
883                 }
884         catchdatal=0;
885         if (osend) {
886                 bufl=0;
887                 d1("Resetting bufl.\n");
888                 va_start(ap,send);
889                 l=VARVPRINTF(buf,send,ap); bufl=l+(!!osend);
890                 va_end(ap);
891                 if (bufl>=sizeof(buf)-1) error("!Command too big (%d>%d)",bufl,sizeof(buf)-1);
892                 if (verbose>=2) error(".devcmd(send=%s,term=%s,catch=%s,timeout=%ld)",
893                         reform(buf,0),reform(term,1),reform(catch,2),alarmtime);
894                 if (osend) buf[l]='\r';
895                 for (offs=0,got=0;offs<bufl;offs++) {
896                         alarm(MAXSENDTIME);
897                         usleep((offs?chartimen:cmdtimen)*1000);
898                         if (!offs && tcflush(devfd,TCIOFLUSH))
899                                 error("^Error flushing I/O queue of device");
900                         if (write(devfd,buf+offs,1)!=1) break;
901                         got++;
902                         if (tcdrain(devfd))
903                                 error("^Error forcing output of char at pos %d of cmd %s",offs,reform(buf,0));
904                         }
905                 alarm(0);
906                 if (got!=bufl) {
907                         error("^Wrote only %d of %d bytes of command",got,bufl);
908                         goto err;
909                         }
910                 }
911
912         if (!(terml=strlen(term))) {
913                 assert(!catch);
914                 return(NULL);
915                 }
916         if (catch) {
917                 catchl=strlen(catch);
918                 fragl=MAX(terml,catchl);
919                 }
920         else fragl=terml;
921         fragl=MAX(fragl,MAX(strlen(ERROR_SUBSTR1),strlen(ERROR_SUBSTR2)));
922         record=NULL;
923         wasalarm=0;
924         alarm(alarmtime);
925         if (!osend) {
926                 got=bufl;
927                 bufl=0;
928                 goto skipread;
929                 }
930         for (;;) {
931                 blocking(0);
932                 errno=0;
933                 got=read(devfd,buf+bufl,sizeof(buf)-1-bufl);
934                 if (got==-1 && errno==EAGAIN) {
935                         blocking(1);
936                         errno=0;
937                         got=read(devfd,buf+bufl,1);
938                         }
939                 if (got<=0) {
940                         if (wasalarm) error("Maximum response timeout (%lds) exceeded",alarmtime);
941                         else error("^Couldn't read device data (ret=%d)",got);
942                         goto err;
943                         }
944 skipread:
945                 bufl2=bufl+got;
946                 buf[bufl2]='\0';
947                 s=buf+bufl;
948                 while (buf+bufl2>s && (s=memchr(s,'\0',buf+bufl2-s))) *s++=REPL_NULLCHAR;
949                 if (verbose>=2)
950                         error("\nGot chunk of data from device: %s",reform(buf+bufl,0));
951                 if (convcr) {
952                         s=buf+bufl;
953                         while (buf+bufl2>s && (s=memchr(s,'\r',buf+bufl2-s))) *s++='\n';
954                         }
955                 bufl=bufl2;
956                 catched(buf+bufl); assert(!record || record==buf+bufl);
957                 assert(bufl<sizeof(buf)-1);
958                 buf[bufl]='\0';
959                 assert(strlen(buf)==bufl);
960                 /* d3(">%s|%s<\n",buf,term); */
961                 if (strstr(buf,ERROR_SUBSTR1) || strstr(buf,ERROR_SUBSTR2)) {
962                         error("Found ERROR response on command %s",reform(send,0));
963                         goto err;
964                         }
965                 if (catch && bufl>=catchl && (hit=strstr(buf,catch))) {
966                         record=hit+catchl;
967                         catched(buf+bufl); assert(!record || record==buf+bufl);
968                         }
969                 if (         bufl>= terml && (hit=strstr(buf,term))) {
970                         memmove(buf,hit+terml,(bufl2=(buf+bufl)-(hit+terml))); bufl=bufl2;
971                         break;
972                         }
973                 if (bufl<fragl) continue;
974                 memmove(buf,buf+bufl-(fragl-1),(bufl2=fragl-1)); bufl=bufl2;
975                 if (record) record=buf+bufl;
976                 }
977         alarm(0);
978         if (!catchdatal) {
979                 if (!catch) return("");
980                 error("Data requested on command %s but no found after term %s",reform(send,0),reform(term,1));
981                 goto err;
982                 }
983         assert(!!catch);
984         record=emptystring;
985         catched(record+1);
986         if (verbose>=2) error(".Returning data %s for cmd %s",reform(catchdata,0),reform(send,1));
987         return(catchdata);
988 }
989
990 static int prepaddr(unsigned char *d,const char *addr)
991 {
992 int tot=0;
993 char flip=0,plus;
994 unsigned char n;
995
996         if ((plus=(*addr=='+'))) addr++;
997         *++d=(plus?ADDR_INT:ADDR_NAT);
998         while (*addr) {
999                 if (*addr<'0' || *addr>'9')
1000                         error("!Error during conversion of number at: %s",addr);
1001                 tot++;
1002                 n=(*addr++)-'0';
1003                 if ((flip=!flip)) *++d=0xF0|n;
1004                 else *d=(*d&0x0F)|(n<<4U);
1005                 }
1006         return(tot);
1007 }
1008
1009 static char *finalsmsc;
1010 #define SMSCBINSIZE (1+1+(MAXNUMLEN+1)/2)
1011 static char pdusmsc[SMSCBINSIZE*2+1];
1012
1013 static inline char tohex(unsigned char x)
1014 {
1015         x&=0x0F;
1016         if (x<10) return(x   +'0');
1017                   return(x-10+'A');
1018 }
1019
1020 static inline void textconv(char *d,unsigned char *s,size_t len)
1021 {
1022         while (len--) {
1023                 *d++=tohex(*s>>4U);
1024                 *d++=tohex(*s    );
1025                 s++;
1026                 }
1027         *d='\0';
1028 }
1029
1030 static inline void smscset(void)
1031 {
1032 char *s,*t,*e,*serr;
1033 long l;
1034 unsigned char bin[2+(MAXNUMLEN+1)/2];
1035
1036         if (smsc) devcmd(NULL,NULL,"\r\nAT+CSCA=\"%s\"",smsc);
1037         s=devcmd(NULL,"\n+CSCA:","\r\nAT+CSCA?");
1038         while (isspace(*s)) s++;
1039         if (!*s || !strcmp(s,"EMPTY"))
1040                 error("!No SMS set in mobile station found, please use option \"-s\"");
1041         if (verbose>=1) error("\nFound default SMSC in mobile: %s",s);
1042         if (*s!='"') error("!No left-quote found in: %s",s);
1043         if (!(t=strrchr(s+1,'"'))) error("!No right-quote found in: %s",s);
1044         if (s+1==t)
1045                 error("!No SMS set in mobile station found, please use option \"-s\"");
1046         e=t++;
1047         while (isspace(*t)) t++;
1048         if (*t++!=',') error("!No comma found after quotes in: %s",s);
1049         while (isspace(*t)) t++;
1050         l=strtol(t,&serr,10);
1051         if ((l!=ADDR_NAT && l!=ADDR_INT) || (serr && *serr))
1052                 error("!Type parse error in: %s",s);
1053         if (l==ADDR_NAT || s[1]=='+') s++;
1054         else *s='+';
1055         *e='\0';
1056         if (verbose>=2) error("\nDecoded SMSC address: %s",s);
1057         if (!NEED_PDUSMSC()) return;
1058         chk(finalsmsc=strdup(s));
1059         bin[0]=1+(prepaddr(bin,finalsmsc)+1)/2;
1060         textconv(pdusmsc,bin,bin[0]+1);
1061 }
1062
1063 static inline unsigned char charconv(char c,size_t offs)
1064 {
1065         switch (c) {
1066                 case '@': return(0);
1067                 case '$': return(2);
1068                 case 0: assert(0);
1069                 default:
1070                         return(c&0x7F);
1071                 }
1072 #if 0
1073         if ((c>='A' && c<='Z') || (c>='a' && c<='z') || (c>='0' && c<='9')) return(c);
1074         error("Can't convert character '%c' (0x%02X) at offs %d (0-based), substituted '?'",
1075                 c,(unsigned char)c,offs);
1076         return('?');
1077 #endif
1078 }
1079
1080 /* Logo format shamelessly stolen from GNokii-0.3.0: http://www.gnokii.org/
1081  * Beware - Nokia Smart Messaging specification 1.0.0 and 2.0.0 is incompatible
1082  * with Nokia current product line implementation
1083  * http://www.forum.nokia.com/developers/smartmsg/download/ssm2_0_0.pdf
1084  */
1085
1086 static char *pdudata;
1087 static char hexdata[140*2+1];
1088
1089 static inline void logoread(void)
1090 {
1091 FILE *f;
1092 char buf[32+140*8+1];
1093 unsigned char bin[140]={
1094         0x06, /* UDH length */
1095         0x05, /* IEI */
1096         0x04, /* IEDL */
1097         0x15, 0x83, /* dest port (group gfx) */
1098         0x00, 0x00  /* src port (unused) */
1099         };
1100 size_t got,r,w;
1101 ssize_t chars,bits;
1102 char gsmnetf[10];
1103 int sizex,sizey,bit;
1104
1105 #define WORD(n) (((unsigned char)buf[(n)])|(((unsigned char)buf[(n)+1])<<8))
1106
1107         if (!(f=fopen(logoname,"rb")))
1108                 error("^!Cannot open logo file \"%s\" for r/o",logoname);
1109         got=fread(buf,1,sizeof(buf),f);
1110              if (got>=20 && !memcmp(buf,"NOL",4)) {
1111                 VARPRINTF2(gsmnetf,"%03.3u%02.2u",WORD(6),WORD(8));
1112                 assert(strlen(gsmnetf)==5);
1113                 r=10;
1114                 if (verbose>=1) error(".Reading NOL file \"%s\", GSMnet \"%s\", word@4=%d..",
1115                         logoname,gsmnetf,WORD(4));
1116                 }
1117         else if (got>=16 && !memcmp(buf,"NGG",4)) {
1118                 r=6;
1119                 if (verbose>=1) error(".Reading NGG file \"%s\", word@4=%d..",
1120                         logoname,WORD(4));
1121                 }
1122         else error("!Unknown file format of logo file \"%s\"",logoname);
1123         if (gsmnet && !strtrycasecmp(gsmnet,WORD_NET)) {
1124                 if (!*gsmnetf) error("!NOL network code detection requested but NOL file not loaded, please specify network code");
1125                 gsmnet=gsmnetf;
1126                 }
1127         if (!gsmnet || !strtrycasecmp(gsmnet,WORD_GROUP) || !*gsmnet) {
1128                 error("\nSending logo as: group graphics");
1129                 gsmnet=NULL;
1130                 }
1131         else {
1132                 error("\nSending logo as: operator logo for \"%s\"",gsmnet);
1133                 bin[4]=0x82; /* dest port 0x1582 */
1134                 }
1135         
1136         sizex=WORD(r); sizey=WORD(r+2);
1137         if (verbose>=2) error(".Magic words: @+4=%d, @+6=%d, @+8=%d",
1138                         WORD(r+4),WORD(r+6),WORD(r+8));
1139         r+=10;
1140         if (sizex<1 || sizex>255
1141          || sizey<1 || sizey>255) error("!Invalid size: %dx%d",sizex,sizey);
1142         chars=((bits=sizex*sizey)+7)/8;
1143         if (r+bits>got) error("!Logo file \"%s\" too short - actual=%d, need(%dx%d)=%d",
1144                 logoname,got,sizex,sizey,r+chars);
1145         else if (r+bits<got)
1146                 if (verbose>=1) error("Ignoring trailing garbage in \"%s\", used only %d bytes",logoname,r+bits);
1147         if ((got=(7+(gsmnet?3:0)+4+chars))>140)
1148                 error("!SMS size would be %d bytes but 140 is maximum",got);
1149         w=7;
1150         if (gsmnet) {
1151                 bin[w++]=((gsmnet[1]&0x0F)<<4)|(gsmnet[0]&0x0F);
1152                 bin[w++]=0xF0                 |(gsmnet[2]&0x0F);
1153                 bin[w++]=((gsmnet[4]&0x0F)<<4)|(gsmnet[3]&0x0F);
1154                 }
1155         bin[w++]=0x00; /* RFU by Nokia */
1156         bin[w++]=sizex; bin[w++]=sizey;
1157         bin[w++]=0x01; /* one B/W plane */
1158         while (chars--) {
1159                 bin[w]=0;
1160                 for (bit=0x80;(bits>0) && (bit>0);bits--,bit>>=1) {
1161                         if (buf[r]!='0' && buf[r]!='1')
1162                                 error("!Invalid character (neither '0' nor '1')in logo file \"%s\" at offset 0x%X",
1163                                         logoname,r);
1164                         if (buf[r++]=='1') bin[w]|=bit;
1165                         }
1166                 w++;
1167                 }
1168         assert(chars==-1); assert(bits==0); assert(w==got); assert(w<=140);
1169         textconv(hexdata,bin,w);
1170         if (verbose>=2) error("\nWill send hexdata: %s",hexdata);
1171 #undef WORD
1172 }
1173
1174 static inline void genpdu(void)
1175 {
1176 static unsigned char pdu[64+MAXNUMLEN/2+(MAXBODYLEN*7)/8];
1177 unsigned char *d=pdu;
1178 int i;
1179 char inb=0,outb=0,xb,*bodyr;
1180 unsigned char inreg;
1181 size_t offs=0;
1182
1183         *d++=PDU_TYPE;
1184         *d++=PDU_MR;
1185         i=prepaddr(d,phone);
1186         *d=i; d+=1+1+(i+1)/2;
1187         *d++=PDU_PID;
1188         *d++=PDU_DCS;
1189         *d++=PDU_VP;
1190         if (bodylen>MAXBODYLEN) {
1191                 error("Body too large (%d>%d), cut",bodylen,MAXBODYLEN);
1192                 body[(bodylen=MAXBODYLEN)]='\0';
1193                 }
1194         bodyr=body;
1195         *d=bodylen;
1196         assert(d<pdu+sizeof(pdu));
1197         while (bodylen || inb) {
1198                 if (!inb) {
1199                         assert(bodylen>0); assert(!!*body);
1200                         inreg=charconv(*bodyr++,offs++);
1201                         bodylen--;
1202                         inb=7;
1203                         }
1204                 if (!outb) {
1205                         *++d=0x00;
1206                         outb=8;
1207                         }
1208                 xb=MIN(inb,outb);
1209 #if 0
1210                 d4("inb=%d,outb=%d,xb=%d\n",inb,outb,xb);
1211 #endif
1212                 *d|=((inreg>>(unsigned)(7-inb))&((1<<xb)-1))<<(unsigned)(8-outb);
1213                 inb-=xb; outb-=xb;
1214                 }
1215         d++;
1216         assert(d<pdu+sizeof(pdu));
1217         pdudata=malloc(2*(d-pdu)+1);
1218         textconv(pdudata,pdu,d-pdu);
1219 }
1220
1221 static inline void preparebody(void)
1222 {
1223 FILE *fin;
1224 char *finame;
1225
1226         if (body && readbody) {
1227                 finame=body;
1228                 body=NULL;
1229                 }
1230         else {
1231                 finame=NULL;
1232                 fin=stdin;
1233                 }
1234         if (body) return;
1235         readbody=0;
1236         if (!finame) {
1237                 if (verbose>=1)
1238                         error("\nPlease enter the SMS text body, end with EOF (ctrl-D):");
1239                 }
1240         else {
1241                 if (!(fin=fopen(finame,"rt")))
1242                         error("^!Can't open data file \"%s\" for r/o",finame);
1243                 }
1244         chk(body=malloc(BODYLOAD));
1245         bodylen=fread(body,1,BODYLOAD,fin);
1246         if (bodylen==-1)
1247                 error("^!Error reading stream \"%s\"",(finame?finame:"<stdin>"));
1248         if (finame) {
1249                 chkfclose(fin,finame);
1250                 free(finame);
1251                 }
1252 }
1253
1254 static int datawait(char immed)
1255 {
1256 int i;
1257 #ifdef HAVE_POLL
1258 struct pollfd ufd;
1259 #else /* HAVE_POLL */
1260 fd_set rfds,xfds;
1261 #endif /* HAVE_POLL */
1262
1263         assert(devfd>=0);
1264 retry:
1265         errno=0;
1266
1267 #ifdef HAVE_POLL
1268         ufd.fd=devfd;
1269         ufd.events=POLLIN;
1270         ufd.revents=0;
1271         i=poll(&ufd,1,(immed?0:-1));
1272 #else /* HAVE_POLL */
1273 #ifdef HAVE_FD_SETSIZE
1274         if (devfd>=FD_SETSIZE)
1275                 error("!Device file descriptor %d can't fit in select() FD_SETSIZE (%d)",
1276                         devfd,FD_SETSIZE);
1277 #endif /* HAVE_FD_SETSIZE */
1278         FD_ZERO(&rfds); FD_SET(devfd,&rfds);
1279         FD_ZERO(&xfds); FD_SET(devfd,&xfds);
1280         i=select(devfd+1,&rfds,NULL,&xfds,NULL);
1281 #endif /* HAVE_POLL */
1282         if (immed && i==0) return(0);
1283         if (i!=1)
1284                 error("^Failed (retval %d) while waiting for data, ignoring",i);
1285
1286 #ifdef HAVE_POLL
1287         if (ufd.revents&(POLLERR|POLLHUP))
1288 #else /* HAVE_POLL */
1289         if (FD_ISSET(devfd,&xfds))
1290 #endif /* HAVE_POLL */
1291                 error("^Error while waiting for data, ignoring");
1292
1293 #ifdef HAVE_POLL
1294         if (!(ufd.revents&POLLIN))
1295 #else /* HAVE_POLL */
1296         if (!(FD_ISSET(devfd,&rfds)))
1297 #endif /* HAVE_POLL */
1298                 {
1299                 error("^No data input after waited for data, retrying");
1300                 goto retry;
1301                 }
1302         return(1);
1303 }
1304
1305 static char *check_format(const char *fmt,const char *string)
1306 {
1307 static char err[LINE_MAX],sub[50];
1308 char *subp,cf,cs;
1309 const char *sf,*ss;
1310
1311         for (sf=fmt,ss=string;(cf=*sf) && (cs=*ss);sf++,ss++) {
1312                 subp=NULL;
1313                 switch (cf) {
1314                         case '?':
1315                                 break;
1316                         case '9':
1317                                 if (isdigit(cs)) break;
1318                                 subp="digit";
1319                                 break;
1320                         case '+':
1321                                 if (cs=='+' || cs=='-') break;
1322                                 subp="+/- sign";
1323                                 break;
1324                         default:
1325                                 if (cf==cs) break;
1326                                 VARPRINTF(sub,"'%c'",cf); subp=sub;
1327                         }
1328                 if (!subp) continue;
1329                 VARPRINTF5(err,"Expected %s, found '%c' at pos %d of string [%s], formatstring [%s]",
1330                         subp,cs,ss-string,string,fmt);
1331                 return(err);
1332                 }
1333         if (*sf) {
1334                 VARPRINTF2(err,"String too short for format, string [%s], formatstring [%s]",
1335                         string,fmt);
1336                 return(err);
1337                 }
1338         if (*ss) {
1339                 VARPRINTF2(err,"Trailing garbage in string [%s], formatstring [%s]",
1340                         string,fmt);
1341                 return(err);
1342                 }
1343         return(NULL);
1344 }
1345
1346 static char *receive_number;
1347 static time_t receive_time;
1348
1349 /* +CMT: "+420602431329",,"99/10/25,03:21:03-00" */
1350 static int receive_headerparse(char *buf)
1351 {
1352 char *s,*s1,*err;
1353 struct tm tm;
1354 static const struct {
1355         off_t strpos;
1356         off_t tmpos;
1357         int min,max;
1358         const char *name;
1359         } timeparse[]={
1360 #define TP_ENT(a,b,c,d,e) { a,offsetof(struct tm,b),c,d,e }
1361                 TP_ENT( 3,tm_year,0,99,"year"),
1362                 TP_ENT( 6,tm_mon ,1,12,"month"),
1363                 TP_ENT( 9,tm_mday,1,31,"day of month"),
1364                 TP_ENT(12,tm_hour,0,23,"hour"),
1365                 TP_ENT(15,tm_min ,0,59,"minute"),
1366                 TP_ENT(18,tm_sec ,0,59,"second"),
1367                 /* Time zone ignored */
1368                 };
1369 int i,val;
1370
1371 #define DIGIT2(s) (((s)[0]-'0')*10+((s)[1]-'0'))
1372
1373         for (s=buf;*s==' ';s++);
1374         if (*s++!='"') {
1375                 error("Cannot find initial '\"' in CMT header: %s",buf);
1376                 return(0);
1377                 }
1378         for (s1=s;*s && *s!='"';s++);
1379         if (!*s) {
1380                 error("Only one '\"' found in CMT header: %s",buf);
1381                 return(0);
1382                 }
1383         free(receive_number);
1384         chk(receive_number=malloc(s-s1+1));
1385         memcpy(receive_number,s1,s-s1); receive_number[s-s1]='\0';
1386         s++;
1387         if ((err=check_phone(receive_number)) ||
1388             (err=check_format(",,\"99/99/99,99:99:99+99\"",s))) {
1389                 error("%s in CMT header: %s",err,buf);
1390                 return(0);
1391                 }
1392         memset(&tm,0,sizeof(tm)); /* may be redundant */
1393         for (i=0;i<NELEM(timeparse);i++) {
1394                 val=DIGIT2(s+timeparse[i].strpos);
1395                 if (val<timeparse[i].min || val>timeparse[i].max) {
1396                         error("Weird value of %s, is %d but expected %d..%d, setting to %d",
1397                                 timeparse[i].name,val,timeparse[i].min,timeparse[i].max,timeparse[i].min);
1398                         val=timeparse[i].min;
1399                         }
1400                 *(int *)(((char *)&tm)+timeparse[i].tmpos)=val;
1401                 }
1402         if (tm.tm_year<70) tm.tm_year+=100;
1403         tm.tm_mon--;
1404         d7("mktime(y%dm%dd%dh%dm%ds%d)\n",
1405                 tm.tm_year,tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
1406         if ((receive_time=mktime(&tm))==-1) {
1407                 error("^mktime(3) failed for %s",s+2);
1408                 return(0);
1409                 }
1410         return(1);
1411 #undef DIGIT2
1412 }
1413
1414 static void receive_accept(char *bodyline)
1415 {
1416 char *buf,*s,*s1,*s2,*s3;
1417 pid_t pid;
1418 char tbuf[32];
1419 int i;
1420 FILE *f;
1421
1422         d2("receive_accept: %s\n",bodyline);
1423 #if RECEIVE_TEST
1424         pid=0;
1425 #else
1426         pid=fork();
1427 #endif
1428         if (pid>0) return; /* parent context */
1429         if (pid==-1) {
1430                 error("Can't fork(2), process spawning may block receive");
1431                 }
1432         else { /* child process */
1433                 dis_cleanup=1;
1434                 }
1435         for (s=body;*s;) {
1436                 s1=s;
1437                 do {
1438                         s1=strchr(s1+(s1!=s),'%');
1439                         } while (s1 && s1[1]!='p' && s1[1]!='T' && s1[1]!='t');
1440                 if (!s1) {
1441                         pushargstack_one(s,0);
1442                         break;
1443                         }
1444                 *s1='\0';
1445                 pushargstack_one(s,0);
1446                 *s1++='%';
1447                 s=s1;
1448                 switch (*s++) {
1449                         case 'p':
1450                                 pushargstack_one(receive_number,0);
1451                                 break;
1452                         case 'T':
1453                                 VARPRINTF(tbuf,"%ld",receive_time);
1454                                 pushargstack_one(tbuf,0);
1455                                 break;
1456                         case 't':
1457                                 if (!(s2=ctime(&receive_time))) {
1458                                         error("Failing ctime(3), ignoring substitution");
1459                                         break;
1460                                         }
1461                                 if ((s3=strchr(s2,'\n'))) *s3='\0';
1462                                 pushargstack_one(s2,0);
1463                                 break;
1464                         default: assert(0);
1465                         }
1466                 }
1467         buf=glueargstack(NULL,NULL); assert(buf);
1468         if (!(f=popen(buf,"w"))) {
1469                 error("^Failing spawn of receive command: %s",buf);
1470                 goto err;
1471                 }
1472         if (fputs(bodyline,f)<0 || putc('\n',f)!='\n')
1473                 error("^Failing write to child receive command: %s",buf);
1474         if ((i=pclose(f)))
1475                 error("^Spawned receive command failure (code %d): %s",i,buf);
1476 err:
1477         free(buf);
1478         if (pid==-1) return;
1479         exit(EXIT_SUCCESS); /* cleanup() has been disabled */
1480 }
1481
1482 static struct {
1483         char **sp;
1484         long *ip;
1485         const char *const msg;
1486         } numarg[]={
1487                 { &maxretry,&maxretryn,"maxretry" },
1488                 { &readtime,&readtimen,"readtime" },
1489                 { &chartime,&chartimen,"chartime" },
1490                 { &cmdtime ,&cmdtimen ,"cmdtime"  },
1491                 { &baud    ,&baudn    ,"baud"     },
1492         };
1493
1494 int main(int argc,char **argv)
1495 {
1496 char *s;
1497 int i;
1498 unsigned fatal=0;
1499 speed_t portbaud;
1500 enum modenum argsmode;
1501
1502         if ((s=strrchr((pname=*argv),'/'))) pname=s+1;
1503 #ifdef HAVE_ATEXIT
1504         atexit(cleanup);
1505 #else
1506         error("atexit(3) not available at compilation time, device cleanup may be missed");
1507 #endif
1508         signal(SIGTERM,(RETSIGTYPE (*)(int))cleanup);
1509         signal(SIGQUIT,(RETSIGTYPE (*)(int))cleanup);
1510         signal(SIGINT ,(RETSIGTYPE (*)(int))cleanup);
1511         signal(SIGHUP ,(RETSIGTYPE (*)(int))cleanup);
1512         assert(mode==MODE_UNKNOWN);
1513         for (i=0;i<NELEM(longopts);i++) {
1514                 if (longopts[i].val<MODE_FIRST) break;
1515                 if (!strstr(pname,longopts[i].name)) continue;
1516                 if (mode==MODE_UNKNOWN) {
1517                         mode=longopts[i].val;
1518                         continue;
1519                         }
1520                 mode=MODE_UNKNOWN;
1521                 break;
1522                 }
1523         argsmode=mode;
1524         processargs(argc,argv,"<command-line>");
1525         if ((s=getenv("HOME"))) {
1526 size_t l=strlen(s);
1527 char *buf=malloc(l+50);
1528
1529                 memcpy(buf,s,l);
1530                 strcpy(buf+l,CONFIG_HOME);
1531                 readfile(buf,1);
1532                 free(buf);
1533                 }
1534         readfile(CONFIG_MAIN,1);
1535         if (verbose>=1) {
1536                 if (argsmode)
1537                         error(".Detected mode \"%s\" from my program name \"%s\"",MODE_NAME(argsmode),pname);
1538                 else
1539                         error(".Automatic mode detection unsuccessul for my progam name \"%s\"",pname);
1540                 }
1541
1542         if (!mode)
1543                 error("!Operation mode unset, use --send or similiar command, see help (-h)");
1544         error(".Running program in mode \"%s\"",MODE_NAME(mode));
1545         switch (mode) {
1546
1547                 case MODE_SEND:           /* FALLTHRU */
1548                 case MODE_SEND_MOBILDOCK: cmdline_send     (); break;
1549                 case MODE_LOGO_SEND:      cmdline_logo_send(); break;
1550                 case MODE_RECEIVE:        cmdline_receive  (); break;
1551                 default: assert(0);
1552                 }
1553         cmdline_done();
1554         for (i=0;i<NELEM(nullcheck);i++) {
1555 const struct nullcheck *n=nullcheck+i;
1556
1557                 if (*n->var) continue;
1558                 if (n->reqd && !(MODE_BIT(mode)&n->reqd)) continue;
1559                 error("Missing parameter \"%s\"",n->name);
1560                 fatal++;
1561                 }
1562         if (fatal) error("!Previous error%s considered unrecoverable",(fatal==1?"":"s"));
1563         emptyclean();
1564         if (!logname) logname=DEF_LOGNAME;
1565         if (!lockfile) lockfile=DEF_LOCKFILE;
1566         if (!device) device=DEF_DEVICE;
1567
1568         for (i=0;i<NELEM(numarg);i++) {
1569 char *serr;
1570                 if (!*numarg[i].sp) continue;
1571                 *numarg[i].ip=strtol(*numarg[i].sp,&serr,0);
1572                 if (*numarg[i].ip<0 || *numarg[i].ip>=LONG_MAX || !serr || *serr)
1573                         error("!Number parse error for parameter \"%s\" of \"%s\" at: %s",
1574                                 numarg[i].msg,*numarg[i].sp,serr);
1575                 }
1576         if (readtimen==-1)
1577                 readtimen=(mode==MODE_SEND_MOBILDOCK?DEF_READTIME_MOBILDOCK:DEF_READTIME);
1578         if (mode==MODE_SEND_MOBILDOCK) mode=MODE_SEND;
1579
1580         if (!strchr(device,'/')) {
1581 size_t l=strlen(device);
1582                 chk(s=malloc(5+l+1));
1583                 strcpy(s,"/dev/");
1584                 strcpy(s+5,device);
1585                 free(device);
1586                 device=s;
1587                 }
1588         devicename=strrchr(device,'/')+1; assert(!!(devicename-1));
1589         for (i=0,s=lockfile;*s;s++) {
1590                 if (*s!='%') continue;
1591                 s++;
1592                 if (*s=='%') continue;
1593                 if (*s=='s') {
1594                         if (i) error("!Only one \"%%s\" permitted in lockfile format-string");
1595                         i=1; continue;
1596                         }
1597                 error("!Invalid format-character '%c' in lockfile format-string, only \"%%s\" allowed",*s);
1598                 }
1599         
1600         if (*logname) {
1601                 if (!(logf=fopen(logname,"a")))
1602                         error("^!Error opening log \"%s\" for append",logname);
1603                 logmsg("Starting up: " PACKAGE " " VERSION);
1604                 }
1605
1606         switch (mode) {
1607                 case MODE_SEND:
1608                         preparebody();
1609                         genpdu();
1610                         readbody=0;
1611                         break;
1612                 case MODE_LOGO_SEND:
1613                         logoread();
1614                         break;
1615                 case MODE_RECEIVE: break;
1616                 default: assert(0);
1617                 }
1618         if (readbody)
1619                 error("Warning: -f / --file is forbidden with mode \"%s\"",MODE_NAME(mode));
1620
1621         switch (baudn) {
1622                 case  2400: portbaud= B2400; break;
1623                 case  4800: portbaud= B4800; break;
1624                 case  9600: portbaud= B9600; break;
1625                 case 19200: portbaud=B19200; break;
1626                 case 38400: portbaud=B38400; break;
1627                 case 57600: portbaud=B57600; break;
1628                 default:
1629                         error("!Specified baudrate %ld is not supported",baudn);
1630                 }
1631         if (verbose>=2) error(".Will use baudrate %ld with hexval 0x%X",baudn,portbaud);
1632                 
1633         if (lockfile && *lockfile && VARPRINTF(lockreal,lockfile,devicename)>0) {
1634 time_t start,end;
1635                 if (verbose>=1) error(".Locking device \"%s\" by \"%s\"..",device,lockreal);
1636                 time(&start);
1637                 lockdevice(0);
1638                 time(&end);
1639                 if ((end-=start)>LOCKREPORT)
1640                         logmsg("Device lock succeeded after %ld seconds",(long)end);
1641                 }
1642         if (verbose>=1) error(".Opening device \"%s\"..",device);
1643         if ((devfd=open(device,O_RDWR|O_NDELAY))<0)
1644                 error("^!Cannot open device \"%s\" for r/w access",device);
1645         
1646         if (tcgetattr(devfd,&restios))
1647                 error("^Unable to get termios settings");
1648         else {
1649                 restios.c_cflag=(restios.c_cflag&~(CBAUD|CBAUDEX))|B0|HUPCL;
1650                 restios_yes=1;
1651                 }
1652         tios.c_iflag=IGNBRK|IGNPAR|IXON|IXOFF;
1653         tios.c_oflag=0;
1654         tios.c_cflag=CS8|CREAD|CLOCAL|HUPCL|portbaud;
1655         tios.c_lflag=IEXTEN|NOFLSH;
1656         memset(tios.c_cc,_POSIX_VDISABLE,sizeof(tios.c_cc));
1657         tios.c_cc[VTIME]=0;
1658         tios.c_cc[VMIN ]=1;
1659             cfsetispeed(&tios,portbaud);
1660         if (cfsetospeed(&tios,portbaud)|cfsetispeed(&tios,portbaud))
1661                 error("^Error setting termios baudrate on device");
1662         if (tcflush(devfd,TCIOFLUSH))
1663                 error("^Error flushing termios (TCIOFLUSH) on device");
1664         if (tcsetattr(devfd,TCSANOW,&tios))
1665                 error("^!Unable to set initial termios device settings");
1666
1667         setalarm();
1668
1669 retryall:
1670                 devcmd("",NULL,"\r\nAT\033\032"); /* ESCAPE, CTRL-Z */
1671                 devcmd(NULL,NULL,"\r\nAT");
1672                 smscset();
1673                 switch (mode) {
1674                         case MODE_SEND:
1675 retrysendcmgf:
1676                                 if (!devcmd(NULL,NULL,"!\r\nAT+CMGF=0")) {
1677                                         if (!devcmd(NULL,NULL,"!\r\nAT+CMGF=1"))
1678                                                 { retrying(); goto retrysendcmgf; }
1679                 /* CMGF=1 */
1680                                         devcmd("\n> ",NULL,"\r\nAT+CMGS=\"%s\"",phone);
1681                                         s=devcmd(NULL,"\n+CMGS:","!~%s\032",body);
1682                                         }
1683                                 else {
1684                 /* CMGF=0 */
1685                                         devcmd("\n> ",NULL,"\r\nAT+CMGS=%d",(strlen(pdusmsc)+strlen(pdudata))/2);
1686                                         s=devcmd(NULL,"\n+CMGS:","!~%s%s\032",pdusmsc,pdudata);
1687                                         }
1688                                 break;
1689                         case MODE_LOGO_SEND:
1690                                 restore="\r\nAT+CSMP=17,,0,0";
1691                                 devcmd(NULL,NULL,"\r\nAT+CSMP=81,,0,245");
1692                                 devcmd("\n> ",NULL,"\r\nAT+CMGS=\"%s\"",phone);
1693                                 s=devcmd(NULL,"\n+CMGS:","!~%s\032",hexdata);
1694                                 break;
1695                         case MODE_RECEIVE:
1696                                 devcmd(NULL,NULL,"\r\nAT+CMGF=1");
1697                                 restore="\r\nAT+CNMI=,0";
1698                                 devcmd(NULL,NULL,"\r\nAT+CNMI=,2");
1699                                 unlockdevice(0);
1700                                 /* Never bail-out when we got up to this point */
1701                                 if (maxretryn!=-1 && verbose>=1)
1702                                         error(".Initialization successful, infinite retry count set");
1703                                 maxretryn=-1;
1704 #if RECEIVE_TEST
1705                 receive_headerparse(" \"+420602123456\",,\"99/10/25,03:21:03-00\"");
1706                 receive_accept("TESTBODY");
1707                 exit(EXIT_SUCCESS);
1708 #endif
1709                                 datawait(0);
1710                                 if (!lockdevice(1)) {
1711                                         if (verbose>=1)
1712                                                 error(".Dialout detected, waiting for lock..");
1713                                         lockdevice(0);
1714                                         goto retryall;
1715                                         }
1716                                 d1("Lock-device succeeded\n");
1717                                 do {
1718                                         d1("Reading a message for us...\n");
1719                                         if (!(s=devcmd("\r","+CMT:"," ")))
1720                                                 goto retryall;
1721                                         if (!(i=receive_headerparse(s)))
1722                                                 error("Receive-header parsing failed on: %s",s);
1723                                         if (!(s=devcmd("\r","\n"," ")))
1724                                                 goto retryall;
1725                                         if (i) receive_accept(s);
1726                                         if (!devcmd("\n",NULL," ")) /* eat last '\n' */
1727                                                 goto retryall;
1728                                         } while (datawait(1));
1729                                 goto retryall;
1730                                 break;
1731                         default: assert(0);
1732                         }
1733                 if (!s) { retrying(); goto retryall; }
1734
1735         while (isspace(*s)) s++;
1736         if (verbose>=1) error("\nMessage successfuly sent with MR: %s",s);
1737         devcmd(NULL,NULL,"\r\nAT");
1738
1739         logmsg("SMS sent (after %d retries), message reference: %s",retrycnt,s);
1740         return(EXIT_SUCCESS);
1741 }