+static inline void preparebody(void)
+{
+FILE *fin=NULL /* GCC happiness */;
+char *finame;
+
+ if (body && readbody) {
+ finame=body;
+ body=NULL;
+ }
+ else {
+ finame=NULL;
+ fin=stdin;
+ }
+ if (body) return;
+ readbody=0;
+ if (!finame) {
+ if (verbose>=1)
+ error(_("\nPlease enter the SMS text body, end with EOF (ctrl-D):"));
+ }
+ else {
+ if (!(fin=fopen(finame,"rt")))
+ error(_("^!Can't open data file \"%s\" for r/o"),finame);
+ }
+ chk(body=malloc(BODYLOAD));
+ bodylen=fread(body,1,BODYLOAD,fin);
+ if (bodylen==-1)
+ error(_("^!Error reading stream \"%s\""),(finame?finame:_("<stdin>")));
+ if (finame) {
+ chkfclose(fin,finame);
+ free(finame);
+ }
+}
+
+static int datawait(char immed)
+{
+int i;
+#ifdef HAVE_POLL
+struct pollfd ufd;
+#else /* HAVE_POLL */
+fd_set rfds,xfds;
+#endif /* HAVE_POLL */
+
+ assert(devfd>=0);
+retry:
+ if (!immed && verbose>=2)
+ error(_(".Waiting for device incoming data.."));
+#ifdef HAVE_POLL
+ ufd.fd=devfd;
+ ufd.events=POLLIN;
+ ufd.revents=0;
+ errno=0;
+ i=poll(&ufd,1,(immed?0:-1));
+#else /* HAVE_POLL */
+#ifdef HAVE_FD_SETSIZE
+ if (devfd>=FD_SETSIZE)
+ error(_("!Device file descriptor %d can't fit in select() FD_SETSIZE (%d)"),
+ devfd,FD_SETSIZE);
+#endif /* HAVE_FD_SETSIZE */
+ FD_ZERO(&rfds); FD_SET(devfd,&rfds);
+ FD_ZERO(&xfds); FD_SET(devfd,&xfds);
+ errno=0;
+ i=select(devfd+1,&rfds,NULL,&xfds,NULL);
+#endif /* HAVE_POLL */
+ if (immed && i==0) return(0);
+ if (i==-1 && errno==EINTR)
+ goto retry; /* silent retry, for example SIGCHLD could occur */
+ if (i!=1)
+ error(_("^Failed (retval %d) while waiting for data, ignoring"),i);
+
+#ifdef HAVE_POLL
+ if (ufd.revents&(POLLERR|POLLHUP))
+#else /* HAVE_POLL */
+ if (FD_ISSET(devfd,&xfds))
+#endif /* HAVE_POLL */
+ error(_("^Error while waiting for data, ignoring"));
+
+#ifdef HAVE_POLL
+ if (!(ufd.revents&POLLIN))
+#else /* HAVE_POLL */
+ if (!(FD_ISSET(devfd,&rfds)))
+#endif /* HAVE_POLL */
+ {
+ error(_("^No data input after waited for data, retrying"));
+ goto retry;
+ }
+ return(1);
+}
+
+static char *check_format(const char *fmt,const char *string)
+{
+static char err[LINE_MAX],sub[50];
+char cf,cs;
+const char *sf,*ss,*subp;
+
+ for (sf=fmt,ss=string;(cf=*sf) && (cs=*ss);sf++,ss++) {
+ subp=NULL;
+ switch (cf) {
+ case '?':
+ break;
+ case '9':
+ if (isdigit(cs)) break;
+ subp=_("digit");
+ break;
+ case '+':
+ if (cs=='+' || cs=='-') break;
+ subp=_("+/- sign");
+ break;
+ default:
+ if (cf==cs) break;
+ VARPRINTF(sub,"'%c'",cf); subp=sub;
+ }
+ if (!subp) continue;
+ VARPRINTF5(err,_("Expected %s, found '%c' at pos %d of string [%s], formatstring [%s]"),
+ subp,cs,ss-string,string,fmt);
+ return(err);
+ }
+ if (*sf) {
+ VARPRINTF2(err,_("String too short for format, string [%s], formatstring [%s]"),
+ string,fmt);
+ return(err);
+ }
+ if (*ss) {
+ VARPRINTF2(err,_("Trailing garbage in string [%s], formatstring [%s]"),
+ string,fmt);
+ return(err);
+ }
+ return(NULL);
+}
+
+static char *receive_number,*receive_smsc;
+static time_t receive_time;
+
+struct tm tm;
+static const struct {
+ off_t strpos;
+ off_t tmpos;
+ int min,max;
+ const char *name;
+ } timeparse[]={
+#define TP_ENT(a,b,c,d,e) { a,offsetof(struct tm,b),c,d,e }
+ TP_ENT( 3,tm_year,0,99,N_("year")),
+ TP_ENT( 6,tm_mon ,1,12,N_("month")),
+ TP_ENT( 9,tm_mday,1,31,N_("day of month")),
+ TP_ENT(12,tm_hour,0,23,N_("hour")),
+ TP_ENT(15,tm_min ,0,59,N_("minute")),
+ TP_ENT(18,tm_sec ,0,59,N_("second")),
+ /* Time zone ignored */
+ };
+#define GETTIME(i) (*(int *)(((char *)&tm)+timeparse[(i)].tmpos))
+
+static void maketime(const char *string)
+{
+int val;
+int i;
+
+ for (i=0;i<NELEM(timeparse);i++) {
+ val=GETTIME(i);
+ if (val<timeparse[i].min || val>timeparse[i].max) {
+ error(_("Weird value of %s, is %d but expected %d..%d, setting to %d"),
+ _(timeparse[i].name),val,timeparse[i].min,timeparse[i].max,timeparse[i].min);
+ GETTIME(i)=timeparse[i].min;
+ }
+ }
+ if (tm.tm_year<70) tm.tm_year+=100;
+ tm.tm_mon--;
+ d7("mktime(y%dm%dd%dh%dm%ds%d)\n",
+ tm.tm_year,tm.tm_mon,tm.tm_mday,tm.tm_hour,tm.tm_min,tm.tm_sec);
+ tm.tm_isdst=-1; /* "timezone" info not available */
+ if ((receive_time=mktime(&tm))==-1)
+ error(_("^mktime(3) failed for %s"),string);
+}
+
+/* +CMT: "+420602431329",,"99/10/25,03:21:03-00" */
+static int receive_headerparse(char *buf)
+{
+char *s,*s1,*err;
+int i;
+
+#define DIGIT2ASC(s) (((s)[0]-'0')*10+((s)[1]-'0'))
+
+ for (s=buf;*s==' ';s++);
+ if (*s++!='"') {
+ error(_("Cannot find initial '\"' in CMT header: %s"),buf);
+ return(0);
+ }
+ for (s1=s;*s && *s!='"';s++);
+ if (!*s) {
+ error(_("Only one '\"' found in CMT header: %s"),buf);
+ return(0);
+ }
+ free(receive_smsc); receive_smsc=NULL;
+ free(receive_number);
+ chk(receive_number=malloc(s-s1+1));
+ memcpy(receive_number,s1,s-s1); receive_number[s-s1]='\0';
+ s++;
+ if ((err=check_phone(receive_number)) ||
+ (err=check_format(",,\"99/99/99,99:99:99+99\"",s))) {
+ error(_("%s in CMT header: %s"),err,buf);
+ return(0);
+ }
+ memset(&tm,0,sizeof(tm)); /* may be redundant */
+ for (i=0;i<NELEM(timeparse);i++)
+ GETTIME(i)=DIGIT2ASC(s+timeparse[i].strpos);
+ maketime(s+2);
+ return(1);
+#undef DIGIT2ASC
+}
+
+static void signal_chld(int signo)
+{
+int status;
+pid_t pid;
+
+ signal(SIGCHLD,(RETSIGTYPE (*)(int))signal_chld);
+ /* we don't care about siginterrupt(3) as it doesn't matter how it is set */
+
+ d2("signal_chld: signo=%d\n",signo);
+ while (0<(pid=waitpid(-1 /* ANY process */,&status,WNOHANG /* options */))) {
+ if (verbose>=2)
+ error(_(".Child process w/PID %d has exited, %s, status=%d"),
+ pid,(WIFEXITED(status)? _("normally") : _("abnormally")),(WIFEXITED(status) ? WEXITSTATUS(status) : -1));
+ }
+}
+
+static void receive_text(char *bodyline)
+{
+char *buf,*s,*s1,*s2,*s3;
+pid_t pid;
+char tbuf[32];
+int i;
+FILE *f;
+
+ d2("receive_text: %s\n",bodyline);
+ signal(SIGCHLD,(RETSIGTYPE (*)(int))signal_chld);
+#if RECEIVE_TEST
+ pid=0;
+#else
+ pid=fork();
+#endif
+ if (pid>0) {
+ if (verbose>=2)
+ error(_(".Spawned child receive-SMS process w/PID %d"),pid);
+ return; /* parent context */
+ }
+ if (pid==-1) {
+ error(_("Can't fork(2), process spawning may block receive"));
+ }
+ else { /* child process */
+ dis_cleanup=1;
+ }
+ for (s=body;*s;) {
+ s1=s;
+ do {
+ s1=strchr(s1+(s1!=s),'%');
+ } while (s1 && s1[1]!='p' && s1[1]!='T' && s1[1]!='t' && s1[1]!='s');
+ if (!s1) {
+ pushargstack_one(s,0);
+ break;
+ }
+ *s1='\0';
+ pushargstack_one(s,0);
+ *s1++='%';
+ s=s1;
+ switch (*s++) {
+ case 'p':
+ pushargstack_one(receive_number,0);
+ break;
+ case 'T':
+ VARPRINTF(tbuf,"%ld",receive_time);
+ pushargstack_one(tbuf,0);
+ break;
+ case 't':
+ if (receive_time==-1) break;
+ if (!(s2=ctime(&receive_time))) {
+ error(_("Failing ctime(3), ignoring substitution"));
+ break;
+ }
+ if ((s3=strchr(s2,'\n'))) *s3='\0';
+ pushargstack_one(s2,0);
+ break;
+ case 's':
+ if (receive_smsc) pushargstack_one(receive_smsc,0);
+ break;
+ default: assert(0);
+ }
+ }
+ buf=glueargstack(NULL,NULL); assert(buf);
+ if (!(f=popen(buf,"w"))) {
+ error(_("^Failing spawn of receive command: %s"),buf);
+ goto err;
+ }
+ if (fputs(bodyline,f)<0 || putc('\n',f)!='\n')
+ error(_("^Failing write to child receive command: %s"),buf);
+ if ((i=pclose(f)))
+ error(_("^Spawned receive command failure (code %d): %s"),i,buf);
+err:
+ free(buf);
+ if (pid==-1) return;
+ exit(EXIT_SUCCESS); /* cleanup() has been disabled */
+}
+
+static inline unsigned char fromhex(unsigned c)
+{
+ c&=0xDF;
+ return(c<'A'?c-('0'&0xDF):(c-('A'&0xDF))+0xA);
+}
+
+static int teldecode(char *text,unsigned char *bin,size_t digits)
+{
+unsigned char b;
+int r=0,i;
+
+ for (i=0;i<digits;text++,i++) {
+ if (!(i&1)) b=*bin;
+ else b=(*bin++)>>4;
+ b&=0x0F;
+ if (b<=0x09)
+ *text=b+'0';
+ else {
+ *text='?';
+ r++;
+ }
+ }
+ *text='\0';
+ return(r);
+}
+
+static void sctsparse(unsigned char *bin,const char *pduline,int offs)
+{
+#define DIGIT2BIN(v) (((v)&0x0F)*10+(((v)>>4)&0x0F))
+int i;
+
+ receive_time=-1;
+ memset(&tm,0,sizeof(tm)); /* may be redundant */
+ for (i=0;i<NELEM(timeparse);offs++,i++) {
+ if ((*bin&0x0F)>0x09 || (*bin&0xF0)>0x90) {
+ error(_("Invalid value of \"%s\" at offset %d in: %s"),
+ timeparse[i].name,offs,pduline);
+ return;
+ }
+ GETTIME(i)=DIGIT2BIN(*bin);
+ bin++;
+ }
+ maketime(pduline);
+
+#undef DIGIT2BIN
+}
+
+static void receive_pdu(char *pduline)
+{
+unsigned char pdu[140+0x100],*pdup,*pdue,oalen,inreg;
+char text[160+1],*textp,*s,*pdulinescan;
+size_t pdulinel=strlen(pduline),want;
+size_t udl,udlb;
+int inb,outb,xb;
+
+ d2("receive_pdu: %s\n",pduline);
+ if (pdulinel>2*sizeof(pdu))
+ { error(_("PDU too long (%d/2) to be valid: %s"),pdulinel,pduline); return; }
+ if (pdulinel&1)
+ { error(_("PDU length odd (%d): %s"),pdulinel,pduline); return; }
+ if (pdulinel<2*13)
+ { error(_("PDU length %d too small (min. 2*%d): %s"),pdulinel,13,pduline); return; }
+ for (pdup=pdu,pdulinescan=pduline;*pdulinescan;pdulinescan+=2) {
+ if (!isxdigit(pdulinescan[0]) || !(isxdigit(pdulinescan[1])))
+ { error(_("Invalid hex byte: %c%c on byte %d in: %s"),
+ pdulinescan[0],pdulinescan[1],pdup-pdu,pduline); return; }
+ *pdup++=(fromhex(pdulinescan[0])<<4)|fromhex(pdulinescan[1]);
+ }
+ pdue=pdup;
+ free(receive_smsc);
+ if (*pdu<=1) {
+ receive_smsc=NULL;
+ }
+ else {
+ if (*pdu>10)
+ { error(_("SMSC length too large (%d, max. %d): %s"),*pdu,10,pduline); return; }
+ chk(receive_smsc=malloc(1+2*(*pdu)+1));
+ s=receive_smsc;
+ if (pdu[1]==ADDR_INT) *s++='+';
+ else {
+ if (pdu[1]!=ADDR_NAT)
+ error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),pdu[1],_("SMSC"),pduline); return;
+ }
+ if (teldecode(s,pdu+2,2*(*pdu-1)-((pdu[1+(*pdu)]&0xF0)==0xF0)))
+ error(_("Some digits unrecognized in %s \"%s\", ignoring in PDU: %s"),_("SMSC"),receive_smsc,pduline);
+ }
+ pdup=pdu+1+(*pdu);
+ if (*pdup&0x03) /* PDU type */
+ error(_("Unrecognized PDU type 0x%02X at offset %d, dropping: %s"),*pdup,pdup-pdu,pduline);
+ pdup++;
+ free(receive_number);
+ if ((oalen=*pdup++)>2*0x10) /* OA len */
+ { error(_("Originating number too large (0x%X, max. 2*0x%X): %s"),oalen,0x10,pduline); return; }
+ if (pdup+(want=1+(oalen+1)/2+10)>pdue)
+ { error(_("PDU length too short (want %d, is %d): %s"),(pdup-pdu)+want,pdue-pdu,pduline); return; }
+ chk(receive_number=malloc(1+2*(*pdup)+1));
+ s=receive_number;
+ if (*pdup==ADDR_INT) *s++='+';
+ else {
+ if (*pdup!=ADDR_NAT)
+ error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),*pdup,_("originating number"),pduline); return;
+ }
+ pdup++;
+ if (teldecode(s,pdup,oalen))
+ error(_("Some digits unrecognized in %s \"%s\", ignoring in PDU at offset %d: %s"),
+ _("originating number"),receive_number,pdup-pdu,pduline);
+ pdup+=(oalen+1)/2;
+ if (*pdup) /* PID */
+ error(_("PID number %02X unsupported, ignoring: %s"),*pdup,pduline);
+ pdup++;
+ if (*pdup) { /* DCS */
+ if ((*pdup&0xF4)==0xF4)
+ { error(_("DCS 0x%02X indicates 8-bit data, unsupported, dropping: %s"),*pdup,pduline); return; }
+ error(_("DCS 0x%02X unsupported, will attempt decoding: %s"),*pdup,pduline);
+ }
+ pdup++;
+ sctsparse(pdup,pduline,pdup-pdu);
+ pdup+=7;
+ /* UDL */
+ udl=*pdup++;
+ if (pdue-pdup>140) {
+ error(_("PDU data (%d) exceed maximum length of %d bytes, cut: %s"),
+ pdue-pdup,140,pduline);
+ pdue=pdup+140;
+ }
+ udlb=(udl*7+7)/8;
+ if (pdup+udlb>pdue) {
+size_t udl1,udlb1;
+
+ udlb1=pdue-pdup;
+ udl1=(udlb1*8)/7;
+ error(_("PDU data length (%d/7->%d/8) longer than data (%d), cut to %d/7->%d/8: %s"),
+ udl,udlb,pdue-pdup,udl1,udlb1,pduline);
+ udl=udl1; udlb=udlb1;
+ }
+ else
+ assert(pdup+udlb==pdue); /* should be checked by 'PDU length too short' above */
+ textp=text;
+ inb=outb=0;
+ inreg=0; /* GCC happiness */
+ while (udl) {
+ if (!inb) {
+ inreg=*pdup++;
+ inb=8;
+ }
+ if (!outb) {
+ assert(textp<text+160);
+ *textp=0x00;
+ outb=7;
+ }
+ xb=MIN(inb,outb);
+#if 0
+ d4("inb=%d,outb=%d,xb=%d\n",inb,outb,xb);
+#endif
+ *textp|=((inreg>>(unsigned)(8-inb))&((1<<xb)-1))<<(unsigned)(7-outb);
+ inb-=xb; outb-=xb;
+ if (!outb) {
+ *textp=charconv_recv(*textp,textp-text);
+ textp++;
+ udl--;
+ }
+ }
+ *textp=0;
+ receive_text(text);
+}
+