#pragma module SEND_SMS "SEND_SMS-1-B" #define __MODULE__ "SEND_SMS" /* **++ ** FACILITY: SMTP ** ** MODULE DESCRIPTION: ** This command line (CLI) utility program is a sender of a ** Short Message using the SMPP protocol to SMS Center. ** ** ** AUTHORS: ** ** Ruslan R. Laishev ** ** CREATION DATE: 11-JUN-2003 ** ** BUILD: ** $ CC /NOWARNING/INCLUDE=[] SEND_SMS.C ** $ LINK SEND_SMS,SYS$INPUT/OPT ** ESME_API_SHR/SHARE ** ^Z ** ** USAGE: ** $ SEND_SMS :==$dev:[dir]SEND_SMS.EXE ** $ SEND_SMS [option_list] ** ** Parameters: ** - a text file of message itself ** - a phone numbers list file: one number - one line ** - a phone number displayed as a "sender" on the phone ** ** Options: ** /AFTER - a Schedule Delivery Time, VMS absolute time format ** /EXPIRED - a Validity Period time, VMS absolute time format ** /INTERVAL - a time interval between sending of SMS, VMS Delta time ** /BATCH_SIZE - a number of SMS sending between /INTERVAL ** /CONNECTION_STRING - A connection string in form "SMSC.ZZTop.RU:9000/symb/VAX/zztop/1/1", ** if this options is ommited then logical SEND_SMS$CONNSTS is searched. ** /URGENT - a higest priority of delivering ** /LOGGING - no comments ** /DEBUG - no comments ** ** EXAMPLE OF USAGE: $ SEND_SMD MSGFILE.TXT PHLIST.LIS 89011163222 /LOG /URG /AFTER=13:00 /EXPI=TOMORROW - _$ /CONNECTION_STRING="SMSC.ZZtop.NET:9000/symb/VAX/zztop/1/1" 16-JUN-2003 12:47:21.06 %SEND_SMS-S-OK, Message was sent to 89013000314, Message ID 42A2D2C7 16-JUN-2003 12:47:21.08 %SEND_SMS-S-OK, Message was sent to 89013000366, Message ID 676465DC 16-JUN-2003 12:47:21.08 %ESME-E-RSUBMITFAIL, submit_sm or submit_multi failed 16-JUN-2003 12:47:21.08 %SEND_SMS-E-ERR, Message was not sent to zzzzzz, status = %x080A811A 16-JUN-2003 12:47:21.08 %SEND_SMS-I-STAT, Total processed phone numbers 3, accepted for delivery 2, rejected 1 ** ** OUTPUT PRODUCED BY TPSMT Utility on the SMSC (Logica Telepath 2700): ** +-----------------------TELEPATH SYSTEM MANAGER TERMINAL-----------------------+ |View Send Testsend Accept Replace Delete Byid Predefined | |Messages.View | |------------------------------------------------------------------------------| |+------------------------------VIEW A MESSAGE-------------------------------+ | || |+| || ID: 1734632924 Rgt'd:None Submit: 03/06/16 12:47:21 |+| || Priority: High DCS:6 Sched: 03/06/16 13:00:00 |+| +| || State: Scheduled PID:0 Expiry: 03/06/17 00:00:00 |+| || Status: Undelivered Atmpts:0 Done: 00/00/00 00:00:00 |+| || Msg Class: 0 Error:0 ? |+| || RetryPrf: 0 Last NW Error:0 ? |+| || Msg Ref: 0 From To |+| ||Cust ID/UG: telepath /0 telepath /0 | | || MSISDN: 89011163222 89013000366 | | || TON/NPI: National /E164 National /E164 | | || +----------------------------Text----------------------------+ | | || |+ +++ ! | | | || |- + + + + + + ++, | |-| || |+ ++ ++ + + ++ + + + ++++. | | | || +------------------------------------------------------------+ | | |+---------------------------------------------------------------------------+ | |Retrieving Message 676465DC,0...Ok. | +------------------------------------------------------------------------------+ ** ** ** ** MODIFICATION HISTORY: ** ** 17-JUN-2003 RRL Added a truncation of the trailer spaces and tabs in phone neumber. ** 29-APR-2004 RRL Added a som changes in send SMSes to SMSC logic (see /INTERVAL & /BATCH_SIZE) ** options. ** ** {@tbs@}... **-- */ /* ** ** INCLUDE FILES ** */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __NEW_STARLET 1 #include #undef __NEW_STARLET /* ** ** SMPP AND ESME API INCLUDE FILES ** */ #include "smppdef.h" #include "esme_msg.h" #include "send_sms_msg.h" /* ** ** MACRO DEFINITIONS ** */ #ifdef __VAX #define LONGWORD_SZ 4 #else #define LONGWORD_SZ 8 #endif #define min(x,y) ((x > y)?y:x) #define max(x,y) ((x < y)?y:x) #define INIT_DDESC(dsc) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_D;(dsc).dsc$w_length = 0;\ (dsc).dsc$a_pointer = 0;} #define INIT_SDESC(dsc, len, ptr) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\ (dsc).dsc$b_class = DSC$K_CLASS_S; (dsc).dsc$w_length = (short) (len);\ (dsc).dsc$a_pointer = (ptr);} #define DEBUG if(debug_flag)printf("%-24.24s:%-08.0u ",__MODULE__,__LINE__);if(debug_flag)printf extern void *SEND_SMS_CLD; $DESCRIPTOR(p_phlist, "PHONE_LIST"); $DESCRIPTOR(p_msgfile, "MSG_FILE"); $DESCRIPTOR(p_from, "FROM_PHONE"); $DESCRIPTOR(q_after, "AFTER"); $DESCRIPTOR(q_expired, "EXPIRED"); $DESCRIPTOR(q_intvl, "INTERVAL"); $DESCRIPTOR(q_batch, "BATCH_SIZE"); $DESCRIPTOR(q_connsts, "CONNECTION_STRING"); $DESCRIPTOR(q_urgent, "URGENT"); $DESCRIPTOR(q_log, "LOGGING"); $DESCRIPTOR(q_debug, "DEBUG"); unsigned tm_after[2],tm_expired[2],tm_intvl[2], debug_flag,qlog,efn = 0,batch_size = 0; char after[32],expire[32],priority = 0; short afterlen,expirelen; /* ** A time coversion related stuff */ int tm_context = 0; $DESCRIPTOR(tm_fmt,"|!Y2!MN0!D0!H04!M0!S0|"); /* YYMMDDHHMMSS */ struct FAB fab; struct RAB rab; struct dsc$descriptor phone_list,msg_file,from_phone; char connsts[255]; char stype[32],ton,npi; short stypelen; int esmeio_shut ( void *ectx ) { int status,resp,sts,context; /* ** Send an UNBIND request to close session with the SMSC */ if ( !(1 & (status = esme_api_tx(ectx,SMPP_CMD$K_UNBIND,NULL,NULL,NULL))) ) lib$signal(status); /* ** Shutdown ESME API */ return esme_api_shut(&ectx); } int esmeio_init ( void **ectx, char *connsts ) { int status,resp, sts,seq; char buf[256],name[256],sid[32],pwd[32],ston[4],snpi[4]; struct dsc$descriptor buf_dsc,name_dsc; /* ** Got parameters from the given connection string, we expect to see: ** "SMSC.DeltaTel.RU:9000/symb/VAX/zztop/1/1" */ if ( 6 != sscanf(connsts,"%[^/]/%[^/]/%[^/]/%[^/]/%[^/]/%[^\n]",&name,&sid,&pwd,&stype,&ston,&snpi) ) return SS$_INSFARG; /* ** Initalize ESME API */ INIT_SDESC(name_dsc,strnlen(name,sizeof(name)),name); if ( !(1 & (status = esme_api_init(ectx,&name_dsc))) ) return status; stypelen = (char) strnlen(stype,sizeof(stype)); ton = (char)atoi(ston); npi = (char)atoi(snpi); /* ** Send a BIND request to open a session with the SMSC */ { /* ** Items list for BIND_TRANSMITTER request */ ILE3 bind_items [] = { {strlen(sid), SMPP_PTAG$K_SID, &sid}, {strlen(pwd), SMPP_PTAG$K_PWD, &pwd}, {stypelen, SMPP_PTAG$K_STYPE, &stype}, {1, SMPP_PTAG$K_TON, &ton}, {1, SMPP_PTAG$K_NPI, &npi}, {0,0,0}}; if ( !(1 & (status = esme_api_tx(*ectx,SMPP_CMD$K_BIND_TRANSMITTER,bind_items,NULL,NULL))) ) return status; } /* ** Receive a RESPONSE to the sent BIND request */ status = esme_api_rx(*ectx,NULL,&resp,&sts,NULL,NULL,&seq); /* ** Return a translated to VMS condition code SMPP/ESME error status */ return (status & 1)?esme_api_err2cond(sts):status; } int esmeio_sendsms ( void *ectx, char *src, short srclen, char *dest, short destlen, char *sms, short smslen, char *mid, short *midlen ) { int status,resp,sts,seq, zero = 0; char dc = DC$K_ISOCYR; struct dsc$descriptor buf_dsc,name_dsc; ILE3 submit_items [] = { {stypelen, SMPP_PTAG$K_STYPE, &stype}, {1, SMPP_PTAG$K_SRCTON, &ton}, {1, SMPP_PTAG$K_SRCNPI, &npi}, {srclen, SMPP_PTAG$K_SRC, src}, {1, SMPP_PTAG$K_DESTTON, &ton}, {1, SMPP_PTAG$K_DESTNPI, &npi}, {destlen, SMPP_PTAG$K_DEST, dest}, {1, SMPP_PTAG$K_DC, &dc}, {1, SMPP_PTAG$K_SMSLEN, &zero}, {afterlen, SMPP_PTAG$K_SCHEDTM, after}, {expirelen, SMPP_PTAG$K_PERIOD, expire}, {1, SMPP_PTAG$K_PRIO, &priority}, // {smslen, SMPP_PTAG$K_SMSBODY, sms}, {smslen, SMPP_PTAG$K_MSGPAYLD, sms}, {0,0,0}}, submit_items_resp [] = { {65, SMPP_PTAG$K_MSGID, mid,midlen}, {0,0,0,0}}; /* ** Send a SMS by SUBMIT_SM request */ if ( !(1 & (status = esme_api_tx(ectx,SMPP_CMD$K_SUBMIT_SM,submit_items,NULL,NULL))) ) return status; /* ** Receive a RESPONSE to the sent SUBMIT_SM request */ status = esme_api_rx(ectx,submit_items_resp,&resp,&sts,NULL,NULL,&seq); /* ** Return a translated to VMS condition code SMPP/ESME error status */ return (status & 1)?esme_api_err2cond(sts):status; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Put message to standard output device (SYS$OUTPUT). ** ** FORMAL PARAMETERS: ** ** msgid: ** VMS condition code ** variable agriments list ** ** RETURN VALUE: ** ** VMS condition code ** ** **-- */ int put_log ( int msgid, ... ) { long status,retvalue = msgid; va_list args; char buf[1024] = {"!%D "},outadr[4]; struct dsc$descriptor opr_dsc,buf_dsc,fao_dsc; int argc,argl[32],idx,flag=15,lvl; char msg_buf[1024]; /* ** Get a message text with given msgid */ INIT_SDESC(fao_dsc,sizeof(buf)-4,&buf[4]); if ( !(1 & (status = sys$getmsg (msgid,&fao_dsc.dsc$w_length,&fao_dsc,flag,&outadr))) ) lib$signal(status); /* ** Reorganize parameters list for $FAOL */ va_start(args,msgid); argl[0] = 0; for (idx = 1,va_count(argc);idx < argc;idx++,args += LONGWORD_SZ) argl[idx] = *((long *)args); va_end((char *) msgid); /* ** Format a message, put it to SYS$OUTPUT */ fao_dsc.dsc$a_pointer -=4; fao_dsc.dsc$w_length +=4; INIT_SDESC(buf_dsc, sizeof(msg_buf),msg_buf); if ( !(1 & (status = sys$faol(&fao_dsc,&buf_dsc.dsc$w_length,&buf_dsc,argl))) ) lib$signal(status); lib$put_output(&buf_dsc); return retvalue; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Processing input command qalifiers and filling out program ** configuration vector. ** ** FORMAL PARAMETERS: ** ** None. ** ** RETURN VALUE: ** ** VMS Condition code ** ** **-- */ int send_sms (void) { int status,diff = 0; char buf[512],*cp; struct dsc$descriptor junk; char accmode = PSL$C_EXEC; short retlen = 0; $DESCRIPTOR(tabnam, "LNM$SYSTEM_TABLE"); $DESCRIPTOR(logname, "SYS$TIMEZONE_DIFFERENTIAL"); ILE3 itmlst[] = {{sizeof(buf),LNM$_STRING,buf,&retlen},{0,0,0,0}}; $DESCRIPTOR(fao_dsc,"!AS!3ZB"); /* ** Processing qualifiers */ debug_flag = CLI$_PRESENT == cli$present (&q_debug); qlog = CLI$_PRESENT == cli$present (&q_log); priority = (CLI$_PRESENT == cli$present (&q_urgent))?3:1; /* ** Get a time zone differential time in seconds */ INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = sys$trnlnm (0,&tabnam,&logname,&accmode,&itmlst))) ) lib$signal(status); lib$cvt_dtb (retlen,junk.dsc$a_pointer,&diff); diff /= (15*60); DEBUG("Local time offset in quarter hours = %d (%d seconds)\n",diff,diff*(15*60)); /* ** /CONNECTION_STRING */ if ( CLI$_PRESENT == cli$present (&q_connsts) ) { INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = cli$get_value(&q_connsts,&junk,&junk.dsc$w_length))) ) lib$signal(status); /* ** Skip double-quotes */ if ( *junk.dsc$a_pointer == '"' ) { junk.dsc$a_pointer++; junk.dsc$w_length -= 2; } *(junk.dsc$a_pointer + junk.dsc$w_length) = '\0'; strncpy(connsts,junk.dsc$a_pointer,min(sizeof(connsts)-1,junk.dsc$w_length)); } else if ( cp = getenv("SEND_SMS$CONNSTS") ) strncpy(connsts,cp,sizeof(connsts)-1); else return SEND_SMS_NOCONNSTS; DEBUG("Connection string = '%s'\n",connsts); INIT_DDESC(phone_list); if ( !(1 & (status = cli$get_value(&p_phlist,&phone_list,0))) ) lib$signal(status); INIT_DDESC(msg_file); if ( !(1 & (status = cli$get_value(&p_msgfile,&msg_file,0))) ) lib$signal(status); INIT_DDESC(from_phone); if ( !(1 & (status = cli$get_value(&p_from,&from_phone,0))) ) lib$signal(status); /* ** Get a /INTERVAL option value */ if ( CLI$_PRESENT == cli$present (&q_intvl) ) { INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = cli$get_value(&q_intvl,&junk,&junk.dsc$w_length))) ) lib$signal(status); if ( !(1 & (status = sys$bintim(&junk,tm_intvl))) ) lib$signal(status); if ( !(1 & (status = lib$get_ef(&efn))) ) lib$signal(status); } /* ** Get a /BATCH_SIZE option value */ if ( CLI$_PRESENT == cli$present (&q_batch) ) { INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = cli$get_value(&q_batch,&junk,&junk.dsc$w_length))) ) lib$signal(status); lib$cvt_dtb (junk.dsc$w_length,junk.dsc$a_pointer,&batch_size); batch_size = batch_size?batch_size:1; } /* ** Time range criterias */ if ( CLI$_PRESENT == cli$present (&q_after) ) { struct dsc$descriptor tmp; INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = cli$get_value(&q_after,&junk,&junk.dsc$w_length))) ) lib$signal(status); if ( !(1 & (status = lib$convert_date_string(&junk,tm_after))) ) lib$signal(status); INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = lib$format_date_time (&junk,&tm_after,&tm_context,&junk.dsc$w_length,0))) ) lib$signal(status); INIT_SDESC(tmp,sizeof(after),after); if ( !(1 & (status = sys$fao(&fao_dsc,&tmp,&tmp.dsc$w_length,&junk,diff))) ) lib$signal(status); afterlen = tmp.dsc$w_length; after [afterlen++] = (diff<0)?'-':'+'; DEBUG("Scheduled time (SMPP format) = '%.*s'\n",afterlen,after); } if ( CLI$_PRESENT == cli$present (&q_expired) ) { struct dsc$descriptor tmp; INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = cli$get_value(&q_expired,&junk,&junk.dsc$w_length))) ) lib$signal(status); if ( !(1 & (status = lib$convert_date_string(&junk,tm_expired))) ) lib$signal(status); INIT_SDESC(junk,sizeof(buf),buf); if ( !(1 & (status = lib$format_date_time (&junk,&tm_expired,&tm_context,&junk.dsc$w_length,0))) ) lib$signal(status); INIT_SDESC(tmp,sizeof(expire),expire); if ( !(1 & (status = sys$fao(&fao_dsc,&tmp,&tmp.dsc$w_length,&junk,diff))) ) lib$signal(status); expirelen = tmp.dsc$w_length; expire [expirelen++] = (diff<0)?'-':'+'; DEBUG("Validity Period time (SMPP format) = '%.*s'\n",expirelen,expire); } return SS$_NORMAL; } /* **++ ** FUNCTIONAL DESCRIPTION: ** ** Main entry of the program. ** ** FORMAL PARAMETERS: ** ** None. ** ** RETURN VALUE: ** ** VMS condition code ** ** **-- */ int main (void) { int status,flag = 0,component = LIB$K_OUTPUT_FORMAT,buflen = 0, total = 0,sentok = 0,senterr = 0,reqidt = 135; void *ectx = NULL; char buf [ 8192 ] = {"SEND_SMS "},sms [ SMPP$K_SMSLEN ],mid[66]; short srclen,destlen,smslen = 0,lfcount = 0,midlen = 0; struct dsc$descriptor buf_dsc; /* ** Initialize a context for date/time manipulations */ if ( !(1 & (status = lib$init_date_time_context (&tm_context,&component,&tm_fmt))) ) return status; /* ** Check the presence of the comand line agruments */ INIT_SDESC(buf_dsc,sizeof(buf)-9,buf+9); if ( !(1 & (status = lib$get_foreign(&buf_dsc,0,&buf_dsc.dsc$w_length,&flag))) ) return status; buf_dsc.dsc$w_length += 9; buf_dsc.dsc$a_pointer -= 9; if ( CLI$_NORMAL != (status = cli$dcl_parse (&buf_dsc,&SEND_SMS_CLD,0,0,0)) ) return status; else if ( !(1 & (status = cli$dispatch())) ) return status; DEBUG("Reading message file '%.*s'\n",msg_file.dsc$w_length,msg_file.dsc$a_pointer); fab = cc$rms_fab; fab.fab$b_fac = FAB$M_GET; fab.fab$b_shr = FAB$M_SHRGET; fab.fab$l_dna = ".TXT"; fab.fab$b_dns = 4; fab.fab$l_fna = msg_file.dsc$a_pointer; fab.fab$b_fns = msg_file.dsc$w_length; if ( !(1 & (status = sys$open(&fab))) ) return status; rab = cc$rms_rab; rab.rab$l_fab = &fab; if ( !(1 & (status = sys$connect(&rab))) ) lib$signal(status,rab.rab$l_stv); rab.rab$l_ubf = buf; rab.rab$w_usz = sizeof(buf); /* ** Read an SMS body from Message file ** */ while ( 1 & (status = sys$get(&rab)) ) { DEBUG("Got message line '%.*s',%u bytes\n",rab.rab$w_rsz,buf,rab.rab$w_rsz); /* ** Skip empty lines... */ if ( !rab.rab$w_rsz ) continue; /* ** Check free cpace in the buffer */ if ( (smslen + 1) >= sizeof(sms) ) break; if ( (smslen - lfcount) % 16 ) { sms[smslen++] = '\n'; lfcount++; } /* ** Add a gotten record to the sms buffer */ memcpy(&sms[smslen],buf,min(rab.rab$w_rsz,sizeof(sms)-smslen)); smslen += min(rab.rab$w_rsz,sizeof(sms)-smslen); if ( smslen >= sizeof(sms) ) break; } /* ** Close input message file */ status = sys$close(&fab); DEBUG("Short message (%u bytes) '%.*s'\n",smslen,smslen,sms); /* ** Open a Phone List file */ DEBUG("Reading phone list file '%.*s'\n",phone_list.dsc$w_length,phone_list.dsc$a_pointer); fab = cc$rms_fab; fab.fab$b_fac = FAB$M_GET; fab.fab$b_shr = FAB$M_SHRGET; fab.fab$l_dna = ".LIS"; fab.fab$b_dns = 4; fab.fab$l_fna = phone_list.dsc$a_pointer; fab.fab$b_fns = phone_list.dsc$w_length; if ( !(1 & (status = sys$open(&fab))) ) sys$exit(status); rab = cc$rms_rab; rab.rab$l_fab = &fab; rab.rab$b_rac = RAB$C_SEQ; rab.rab$v_nlk = rab.rab$v_rah = 1; if ( !(1 & (status = sys$connect(&rab))) ) lib$signal(status,rab.rab$l_stv); rab.rab$l_ubf = &buf; rab.rab$w_usz = sizeof(buf); rab.rab$v_nlk = rab.rab$v_rah = 1; /* ** Main Phone List file reading loop */ while ( 1 & (status = sys$get(&rab)) ) { { struct dsc$descriptor_s s; INIT_SDESC(s,rab.rab$w_rsz,buf); if ( !(1 & (status = str$trim (&s,&s,&rab.rab$w_rsz))) && (status != STR$_TRU) ) lib$signal(status); } DEBUG("Got phone number '%.*s'\n",rab.rab$w_rsz,buf); /* ** Delay a sending for /INTERVAL time */ if ( total && batch_size && !(total%batch_size) && tm_intvl[0] && tm_intvl[1] ) { if (qlog) put_log(SEND_SMS_DELAY,&tm_intvl,total); if ( !(1 & (status = sys$setimr(efn, &tm_intvl,0,reqidt,0))) ) lib$signal(status); if ( !(1 & (status = sys$waitfr(efn))) ) lib$signal(status); } total++; /* ** Open an SMPP session with SMSC if it need */ if ( !ectx && !(1 & (status = esmeio_init(&ectx,connsts))) ) break; /* ** Sending formed SMS */ status = esmeio_sendsms(ectx,from_phone.dsc$a_pointer,from_phone.dsc$w_length, rab.rab$l_rbf,rab.rab$w_rsz,sms,smslen,mid,&midlen); if ( qlog && (1 & status) ) { put_log(SEND_SMS_OK,rab.rab$w_rsz,rab.rab$l_rbf,midlen,mid); sentok++; } else if ( !(1 & status) ) { put_log(status); put_log(SEND_SMS_ERR,rab.rab$w_rsz,rab.rab$l_rbf,status); senterr++; } } if ( !(1 & rab.rab$l_sts) && (rab.rab$l_sts != RMS$_EOF) ) lib$signal(rab.rab$l_sts,rab.rab$l_stv); /* ** Close input message file */ sys$close(&fab); if ( ectx ) esmeio_shut(ectx); if (qlog) put_log(SEND_SMS_STAT,total,sentok,senterr); return ((status == RMS$_EOF)?SS$_NORMAL:status); }