X-Git-Url: http://git.jankratochvil.net/?p=mdsms.git;a=blobdiff_plain;f=mdsms.c;h=932dc490f6cbc8e7d265e2555988b513ed8c5843;hp=a630131a73c38d9808e1a5e252518592090b2b0d;hb=4afdfe40fe5a409d93c2be811bfb61c858a4e0e3;hpb=f5a4142536d9895cf78d2e83c1d7ea0391081af0 diff --git a/mdsms.c b/mdsms.c index a630131..932dc49 100644 --- a/mdsms.c +++ b/mdsms.c @@ -96,9 +96,29 @@ static int verbose static char *pname; static int dis_cleanup=0,devfd=-1; -static char *phone,*device,*logname,*lockfile,*smsc,*maxretry,*readtime,*chartime,*cmdtime,*baud,*restore; +static char *phone,*device,*logname,*lockfile,*smsmode,*pdusmscmode,*smsc,*maxretry,*readtime,*chartime,*cmdtime,*baud,*restore; static int readbody; static long maxretryn=DEF_MAXRETRY,readtimen=-1,chartimen=DEF_CHARTIME,cmdtimen=DEF_CMDTIME,baudn=DEF_BAUD; +#ifdef HAVE_CRTSCTS +static int handshake_rtscts; +static unsigned handshake_stamp; +#else +#define handshake_rtscts (0) +#endif +static enum { + FSM_AUTO=0, + FSM_PDU, + FSM_TEXT + } force_smsmode=FSM_AUTO; +static enum { + FPSM_AUTO=0, + FPSM_COUNT_IN, + FPSM_COUNT_OUT, + FPSM_NONE + } force_pdusmscmode=FPSM_AUTO, +#define FPSM_MIN (FPSM_COUNT_IN) +#define FPSM_MAX (FPSM_NONE) + try_pdusmscmode=FPSM_MIN; static size_t bodylen; /* --send / --send-mobildock / --receive specific */ static char *body; @@ -235,11 +255,13 @@ static void usage(void) %s\ \n\ Usage: %s [-c|--config ] [-d|--device ]\n\ - {--send | --send-mobildock | --receive | --logo-send}\n\ - [-L|--log ] [-b|--baud ]\n\ - [-l|--lockfile ] [-s|--smsc ] [-m|--maxretry <#>]\n\ + [-L|--log ] [-l|--lockfile ]\n\ + [-b|--baud ] [-x|--xonxoff] [-C|--rtscts]\n\ + [-M|--smsmode ] [-P|--pdusmscmode ]\n\ + [-s|--smsc ] [-m|--maxretry <#>]\n\ [-r|--readtime ] [-t|--chartime ] [-T|--cmdtime ]\n\ [-v|--verbose] [-h|--help] [-V|--version]\n\ + {--send | --send-mobildock | --receive | --logo-send}\n\ --send / --send-mobildock:\n\ [-f|--file] \n\ --receive:\n\ @@ -253,9 +275,13 @@ Usage: %s [-c|--config ] [-d|--device ]\n\ \t\t(def. \"%s\" and \"$HOME%s\")\n\ -d, --device\tMobile on this serial device (def. \"%s\")\n\ -L, --log\tLog all important messages to this file (def. \"%s\")\n\ - -b, --baud\tSet baudrate, 2400-57600 supported (def. %d)\n\ -l, --lockfile\tLock serial port by this file, \"%%s\" is basename of device\n\ \t\t(def. \"%s\")\n\ + -b, --baud\tSet baudrate, 2400-57600 supported (def. %d)\n\ + -x, --xonxoff\tUse XON/XOFF (AKA software) serial port handshaking - default\n\ + -C, --rtscts\tUse RTS/CTS (AKA hardware) serial port handshaking%s\n\ + -M, --smsmode\tForce SMS as: \"pdu\" or 0: PDU mode, \"text\" or 1: text mode\n\ + -P, --pdusmscmode\tForce PDU as: \"count-in\", \"count-out\", \"none\"\n\ -s, --smsc\tUse this SMS Center number (def. query from mobile)\n\ -m, --maxretry\tMaximum retries of any command before giving up (def. %d)\n\ -r, --readtime\tSeconds for maximum wait time for response\n\ @@ -284,8 +310,13 @@ Usage: %s [-c|--config ] [-d|--device ]\n\ \n\ You may need to use the following line to catch all of this help text:\n\ ./mdsms 2>&1|more\n\ -\n"),version,PACKAGE,CONFIG_MAIN,CONFIG_HOME,DEF_DEVICE,DEF_LOGNAME,DEF_BAUD,DEF_LOCKFILE,DEF_MAXRETRY, -DEF_READTIME,DEF_READTIME_MOBILDOCK,EXT_READTIME,DEF_CHARTIME,DEF_CMDTIME, +\n"),version,PACKAGE,CONFIG_MAIN,CONFIG_HOME,DEF_DEVICE,DEF_LOGNAME,DEF_LOCKFILE,DEF_BAUD, +#ifdef HAVE_CRTSCTS + "", +#else + _("\n\t\t(Not supported on this platform!)"), +#endif +DEF_MAXRETRY,DEF_READTIME,DEF_READTIME_MOBILDOCK,EXT_READTIME,DEF_CHARTIME,DEF_CMDTIME, WORD_NET,WORD_GROUP); exit(EXIT_FAILURE); } @@ -303,20 +334,25 @@ static const struct option longopts[]={ {"recv" ,0,0,MODE_RECEIVE}, {"logo" ,0,0,MODE_LOGO_SEND}, {"ring" ,0,0,MODE_RING_SEND}, -{"config" ,1,0,'c'}, -{"device" ,1,0,'d'}, -{"log" ,1,0,'L'}, -{"baud" ,1,0,'b'}, -{"lockfile",1,0,'l'}, -{"smsc" ,1,0,'s'}, -{"maxretry",1,0,'m'}, -{"readtime",1,0,'r'}, -{"chartime",1,0,'t'}, -{"cmdtime" ,1,0,'T'}, -{"file" ,0,0,'f'}, -{"verbose" ,0,0,'v'}, -{"help" ,0,0,'h'}, -{"version" ,0,0,'V'}}; +{"config" ,1,0,'c'}, +{"device" ,1,0,'d'}, +{"log" ,1,0,'L'}, +{"lockfile" ,1,0,'l'}, +{"baud" ,1,0,'b'}, +{"xonxoff" ,0,0,'x'}, +{"rtscts" ,0,0,'C'}, +{"smsmode" ,1,0,'M'}, +{"pdusmscmode" ,1,0,'P'}, +{"smsc" ,1,0,'s'}, +{"maxretry" ,1,0,'m'}, +{"readtime" ,1,0,'r'}, +{"chartime" ,1,0,'t'}, +{"cmdtime" ,1,0,'T'}, +{"file" ,0,0,'f'}, +{"verbose" ,0,0,'v'}, +{"help" ,0,0,'h'}, +{"version" ,0,0,'V'}, +{NULL ,0,0,0 }}; static void processargs(int argp,char **args,const char *from); @@ -512,15 +548,17 @@ static struct { char **const var; unsigned stamp; } optset[]={ - { 'd',&device }, - { 'L',&logname }, - { 'b',&baud }, - { 'l',&lockfile }, - { 's',&smsc }, - { 'm',&maxretry }, - { 'r',&readtime }, - { 't',&chartime }, - { 'T',&cmdtime }, + { 'd',&device }, + { 'L',&logname }, + { 'l',&lockfile }, + { 'b',&baud }, + { 'M',&smsmode }, + { 'P',&pdusmscmode }, + { 's',&smsc }, + { 'm',&maxretry }, + { 'r',&readtime }, + { 't',&chartime }, + { 'T',&cmdtime }, }; static void processargs(int argp,char **args,const char *from) @@ -531,7 +569,7 @@ int i; seq++; optarg=NULL; optind=0; /* FIXME: Possible portability problem. */ - while ((optc=getopt_long(argp,args,"c:d:L:b:l:s:m:r:t:T:fvhV",longopts,NULL))!=EOF) switch (optc) { + while ((optc=getopt_long(argp,args,"c:d:L:l:b:xCM:P:s:m:r:t:T:fvhV",longopts,NULL))!=EOF) switch (optc) { case 'c': if (cfgstacki>=NELEM(cfgstack)) { error(_("Looping (%d) during attempt to read config file \"%s\", break-out"),NELEM(cfgstack),from); @@ -539,7 +577,7 @@ int i; } chk(cfgstack[cfgstacki++]=strdup(optarg)); break; - case 'd': case 'L': case 'b': case 'l': case 's': case 'm': case 'r': case 't': case 'T': + case 'd': case 'L': case 'b': case 'l': case 'M': case 'P': case 's': case 'm': case 'r': case 't': case 'T': for (i=0;i=0 && ++retrycnt>maxretryn) error(_("!Maximum command retry count (%ld) exceeded"),maxretryn); + if (maxretryn>=0 && ++retrycnt>=maxretryn) error(_("!Maximum command retry count (%ld) exceeded"),maxretryn); if (verbose>=2) error(_(".Retrying phase, %d out of %ld.."),retrycnt,maxretryn); } @@ -889,21 +939,27 @@ static size_t bufl; ssize_t got; char *hit,*s; va_list ap; -char errout,extend,noconvcr,edata; +char errout,extend,catch_any,edata; long alarmtime; const char *osend; static const char emptystring[]=""; +size_t discard; if (!term) term="\nOK\n"; if (!strcmp(send," ")) send=NULL; /* GCC formatstring-check workaround */ + if (verbose>=2) error(_(".devcmd(sendfmt=%s,term=%s,catch=%s)"), + reform(send,0),reform(term,1),reform(catch,2)); if (!(osend=send)) send=""; - if ((noconvcr=(catch && *catch=='@'))) catch++; + if ((catch_any=(catch && !strcmp(catch,"@")))) catch=NULL; if ((errout=(*send=='!'))) send++; errout|=(maxretryn==-1); if ((extend=(*send=='~'))) send++; alarmtime=readtimen*(extend?EXT_READTIME:1); - d8("devcmd(), alarmtime=%ld, errout=%d, extend=%d, noconvcr=%d, osend=%p, bufl=%d, buf: %s\n", - alarmtime,errout,extend,noconvcr,osend,bufl,reform(buf,0)); + buf[bufl]='\0'; /* for d8() below */ + d8("devcmd(), alarmtime=%ld, errout=%d, extend=%d, catch_any=%d, osend=%p, bufl=%d, buf: %s\n", + alarmtime,errout,extend,catch_any,osend,bufl,reform(buf,0)); + assert(!catch || !strchr(catch,'\r')); /* we are no longer supporting 'noconvcr'! */ + assert(!term || !strchr(term ,'\r')); if (0) { err: alarm(0); @@ -918,8 +974,7 @@ err: l=VARVPRINTF(buf,send,ap); bufl=l+(!!osend); va_end(ap); if (bufl>=sizeof(buf)-1) error(_("!Command too big (%d>%d)"),bufl,sizeof(buf)-1); - if (verbose>=2) error(_(".devcmd(send=%s,term=%s,catch=%s,timeout=%ld)"), - reform(buf,0),reform(term,1),reform(catch,2),alarmtime); + if (verbose>=2) error(_(".devcmd formatted send=%s%s"),reform(buf,0),(osend?"+\"\\r\"":"")); if (osend) buf[l]='\r'; for (offs=0,got=0;offss && (s=memchr(s,'\0',buf+bufl2-s))) *s++=REPL_NULLCHAR; if (verbose>=3) error(_("\nGot chunk of data from device: %s"),reform(buf+bufl,0)); - if (!noconvcr) { + /* convert CR */ { s=buf+bufl; while (buf+bufl2>s && (s=memchr(s,'\r',buf+bufl2-s))) *s++='\n'; } bufl=bufl2; +skipread: catched(buf+bufl,edata); assert(!record || record==buf+bufl); assert(bufl=catchl && (hit=strstr(buf,catch))) { +/* "record" may get NULLed here after successful 'catch' + * but "recordend" will never be NULLed + */ + if (catch && !recordend && bufl>=catchl && (hit=strstr(buf,catch))) { record=hit+catchl; catched(buf+bufl,edata); assert(!record || record==buf+bufl); } - if ( bufl>= terml && (hit=strstr(buf,term))) { + if (catch_any && !recordend && buf[discard=strspn(buf,"\n" /* accept */)]) { + record=buf+discard; + catched(buf+bufl,edata); assert(!record || record==buf+bufl); + } + if (((!catch && !catch_any) || catchdatal) && bufl>= terml + && (hit=strstr((recordend?recordend:buf),term))) { memmove(buf,hit+terml,(bufl2=(buf+bufl)-(hit+terml))); bufl=bufl2; break; } if (bufl=2) error(_(".Returning data %s for cmd %s"),reform(catchdata,0),reform(send,1)); @@ -1060,7 +1123,6 @@ static inline void textconv(char *d,unsigned char *s,size_t len) static inline void smscset(void) { char *s,*t,*e,*serr; -long l; unsigned char bin[2+(MAXNUMLEN+1)/2]; if (smsc) devcmd(NULL,NULL,"\r\nAT+CSCA=\"%s\"",smsc); @@ -1069,19 +1131,22 @@ unsigned char bin[2+(MAXNUMLEN+1)/2]; if (!*s || !strcmp(s,"EMPTY")) error(_("!No SMSC set in mobile station found, please use option \"-s\"")); if (verbose>=1) error(_("\nFound default SMSC in mobile: %s"),s); - if (*s!='"') error(_("!No left-quote found in: %s"),s); - if (!(t=strrchr(s+1,'"'))) error(_("!No right-quote found in: %s"),s); - if (s+1==t) + if (*s++!='"') error(_("!No left-quote found in: %s"),s); + if (!(t=strrchr(s,'"'))) error(_("!No right-quote found in: %s"),s); + if (s==t) error(_("!No SMS set in mobile station found, please use option \"-s\"")); e=t++; while (isspace(*t)) t++; - if (*t++!=',') error(_("!No comma found after quotes in: %s"),s); - while (isspace(*t)) t++; - l=strtol(t,&serr,10); - if ((l!=ADDR_NAT && l!=ADDR_INT) || (serr && *serr)) - error(_("!Type parse error in: %s"),s); - if (l==ADDR_NAT || s[1]=='+') s++; - else *s='+'; + if (*t) { +long l; + + if (*t++!=',') error(_("!No comma found after quotes in: %s"),s); + while (isspace(*t)) t++; + l=strtol(t,&serr,10); + if ((l!=ADDR_NAT && l!=ADDR_INT) || (serr && *serr)) + error(_("!Type parse error in: %s"),s); + if (l==ADDR_INT && *s!='+') *--s='+'; + } *e='\0'; if (verbose>=2) error(_("\nDecoded SMSC address: %s"),s); if (!NEED_PDUSMSC()) return; @@ -1256,19 +1321,19 @@ long size; #define WORD(n) (((unsigned char)buf[(n)])|(((unsigned char)buf[(n)+1])<<8)) - if (!(f=fopen(logoname,"rb"))) - error(_("^!Cannot open ring file \"%s\" for r/o"),logoname); - if ((size=getfilesize(f,logoname))==-1) + if (!(f=fopen(ringname,"rb"))) + error(_("^!Cannot open ring file \"%s\" for r/o"),ringname); + if ((size=getfilesize(f,ringname))==-1) error(_("!File size determination is essential to continue operation")); if (size<0x103) error(_("!File \"%s\" size %ld too small (must >=0x103)! Is it .000 file?"), - logoname,size); + ringname,size); if (fseek(f,0x100,SEEK_SET)) - error(_("^Seeking error on \"%s\", ignoring"),logoname); + error(_("^Seeking error on \"%s\", ignoring"),ringname); size-=0x100; if (size<=BIN1_PAYLOAD) { if ((got=fread(bin1+7,1,size,f))!=size) - error(_("^Read error on \"%s\", wanted %ld, got %d"),logoname,size,got); + error(_("^Read error on \"%s\", wanted %ld, got %d"),ringname,size,got); error(_("\nSending ring tone \"%s\" as single SMS (size %ld, max %d)"), ringname,size,BIN1_PAYLOAD); nokiaprep(bin1,7+size); @@ -1289,12 +1354,12 @@ long size; binn[11]=fragn; want=MIN(size,BINN_PAYLOAD); if ((got=fread(binn+12,1,want,f))!=want) - error(_("^Read error on \"%s\", wanted %d, got %d"),logoname,want,got); + error(_("^Read error on \"%s\", wanted %d, got %d"),ringname,want,got); nokiaprep(binn,12+want); size-=want; } } - chkfclose(f,logoname); + chkfclose(f,ringname); #undef WORD } @@ -1508,6 +1573,7 @@ int i; 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); } @@ -1622,10 +1688,10 @@ err: exit(EXIT_SUCCESS); /* cleanup() has been disabled */ } -static inline unsigned char fromhex(char c) +static inline unsigned char fromhex(unsigned c) { c&=0xDF; - return(c<'A'?c-'0':(c-'A')+0xA); + return(c<'A'?c-('0'&0xDF):(c-('A'&0xDF))+0xA); } static int teldecode(char *text,unsigned char *bin,size_t digits) @@ -1672,7 +1738,7 @@ int i; static void receive_pdu(char *pduline) { unsigned char pdu[140+0x100],*pdup,*pdue,oalen,inreg; -char text[160+1],*textp,*s; +char text[160+1],*textp,*s,*pdulinescan; size_t pdulinel=strlen(pduline),want; size_t udl,udlb; int inb,outb,xb; @@ -1684,11 +1750,11 @@ int inb,outb,xb; { 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;*pduline;pduline+=2) { - if (!isxdigit(pduline[0]) || !(isxdigit(pduline[1]))) + 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"), - pduline[0],pduline[1],pdup-pdu,pduline); return; } - *pdup++=(fromhex(pduline[0])<<4)|fromhex(pduline[1]); + pdulinescan[0],pdulinescan[1],pdup-pdu,pduline); return; } + *pdup++=(fromhex(pdulinescan[0])<<4)|fromhex(pdulinescan[1]); } pdue=pdup; free(receive_smsc); @@ -1703,7 +1769,7 @@ int inb,outb,xb; if (pdu[1]==ADDR_INT) *s++='+'; else { if (pdu[1]!=ADDR_NAT) - error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),_("SMSC"),pdu[1],pduline); return; + 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); @@ -1713,8 +1779,8 @@ int inb,outb,xb; error(_("Unrecognized PDU type 0x%02X at offset %d, dropping: %s"),*pdup,pdup-pdu,pduline); pdup++; free(receive_number); - if ((oalen=*pdup++)>10) /* OA len */ - { error(_("Originating number too large (%d, max. %d): %s"),oalen,10,pduline); return; } + 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)); @@ -1722,7 +1788,7 @@ int inb,outb,xb; if (*pdup==ADDR_INT) *s++='+'; else { if (*pdup!=ADDR_NAT) - error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),_("originating number"),*pdup,pduline); return; + error(_("Unknown address type 0x%02X of %s, ignoring in PDU: %s"),*pdup,_("originating number"),pduline); return; } pdup++; if (teldecode(s,pdup,oalen)) @@ -1752,14 +1818,13 @@ int inb,outb,xb; size_t udl1,udlb1; udlb1=pdue-pdup; - udl1=(udlb*8)/7; + 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,pduline); + udl,udlb,pdue-pdup,udl1,udlb1,pduline); udl=udl1; udlb=udlb1; } else - error(_("Trailing garbage ignored in PDU data (UDL %d/7->%d/8, got %d) in: %s"), - udl,udlb,pdue-pdup,pduline); + assert(pdup+udlb==pdue); /* should be checked by 'PDU length too short' above */ textp=text; inb=outb=0; inreg=0; /* GCC happiness */ @@ -1777,7 +1842,7 @@ size_t udl1,udlb1; #if 0 d4("inb=%d,outb=%d,xb=%d\n",inb,outb,xb); #endif - *textp|=((inreg>>(unsigned)(7-inb))&((1<>(unsigned)(8-inb))&((1<=1) @@ -2019,8 +2105,18 @@ retryall: s=devcmd(NULL,"\n+CMGS:","!~%s\032",body); } else { - devcmd("\n> ",NULL,"\r\nAT+CMGS=%d",(strlen(pdusmsc)+strlen(pdudata))/2); - s=devcmd(NULL,"\n+CMGS:","!~%s%s\032",pdusmsc,pdudata); + devcmd("\n> ",NULL,"\r\nAT+CMGS=%d",( + (try_pdusmscmode==FPSM_COUNT_IN ? strlen(pdusmsc) : 0) + +strlen(pdudata))/2); + s=devcmd(NULL,"\n+CMGS:","!~%s%s\032", + (try_pdusmscmode!=FPSM_NONE ? pdusmsc : ""), + pdudata); + if (!s && force_pdusmscmode==FPSM_AUTO) { + if (FPSM_MAX==try_pdusmscmode++) + try_pdusmscmode=FPSM_MIN; + else + retrycnt--; + } } break; case MODE_LOGO_SEND: @@ -2029,11 +2125,12 @@ struct hexdata *hd; restore="\r\nAT+CSMP=17,,0,0"; devcmd(NULL,NULL,"\r\nAT+CSMP=81,,0,245"); - devcmd("\n> ",NULL,"\r\nAT+CMGS=\"%s\"",phone); while ((hd=hexdata)) { + devcmd("\n> ",NULL,"\r\nAT+CMGS=\"%s\"",phone); if (!(s=devcmd(NULL,"\n+CMGS:","!~%s\032",hd->data))) break; - hexdata=hd->next; + if ((hexdata=hd->next)) pushargstack_one(s,0); free(hd); + parts++; } } break; case MODE_RECEIVE: @@ -2060,18 +2157,16 @@ struct hexdata *hd; d1("Lock-device succeeded\n"); do { d1("Reading a message for us...\n"); - if (!(s=devcmd("\r","@+CMT:"," "))) + if (!(s=devcmd("\n","+CMT:"," "))) goto retryall; if (cmgf && !(i=receive_headerparse(s))) error(_("Receive-header parsing failed on: %s"),s); - if (!(s=devcmd("\r","@\n"," "))) + if (!(s=devcmd("\n","@"," "))) goto retryall; if (cmgf) { if (i) receive_text(s); } else receive_pdu(s); - if (!devcmd("\n",NULL," ")) /* eat last '\n' */ - goto retryall; } while (datawait(1)); goto retryall; break; @@ -2079,10 +2174,13 @@ struct hexdata *hd; } if (!s) { retrying(); goto retryall; } - while (isspace(*s)) s++; - if (verbose>=1) error(_("\nMessage successfuly sent with MR (Message Reference): %s"),s); + pushargstack_one(s,0); s=NULL; + if (verbose>=1) while ((s=nextargstack())) { + while (isspace(*s)) s++; + error(_("\nMessage successfuly sent with MR (Message Reference): %s"),s); + } devcmd(NULL,NULL,"\r\nAT"); - logmsg(_("SMS sent (after %d retries), message reference: %s"),retrycnt,s); + logmsg(_("SMS sent (after %d retries), %d part(s)"),retrycnt,parts); return(EXIT_SUCCESS); }