Sending texts as part of a 2FA (2 Factor Authentication) process on the IBM i
In the following article we will highlight example code showing how to text directly from the IBM i using just a cellular modem, an asynchronous communication line, an ICF file and all the code for an SQLRPGLE program that does the communication to the modem in order to send an SMS text out onto the mobile network. This is part of a multipart article outlining a complete example web app on the IBM i.
The code examples are supplied purely as a guide on how these various facets of application security could be deployed on the IBM i platform. The example application that we will outline in the future parts of this blog is not meant to be a finished product but is an aide which may help you develop the framework and direction necessary to drive a web based project forward. The entire app will be downloadable and will setup all the required configuration descriptions and settings using the supplied commands.
First off let us start by setting up the hardware needed to send an SMS text message directly from the IBM i. In this instance we have installed a 2742 into an IBM Power machine (also known as feature 6805 when used in an IOPless Power machine). Be careful to honour the limits on how much bandwidth can be shared across the cards in each slot. Sometimes extra cards will be required if too many ethernet ports are in use on one card for example. Once installed use WRKHDWRSC *CMN to find out the resource names associated with the WAN IOA ports of the 2742 card. In our case it is CMN15.
In order to send the SMS test messages out onto the mobile network a GSM / GPRS Cellular Modem will be required which can be sourced from major online electrical component suppliers and other major online retailers. They usually have a 15pin female D type connector which will be connected to the IBM i V24 WAN IOA cable. More of that later. A SIM card will need to be inserted into the modem. Make sure the SIM is not locked with a password or pin or the like which may prevent the modem from accessing the SIM.
Below is an example of the create commands used to build the configuration descriptions for the line, controller and device. The default LINESPEED may vary on some cellular modems if not 115200 baud then it maybe 9600 or 19200.
CRTLINASC LIND(SMSTXTLIN) RSRCNAME(CMN15) ONLINE(*NO) INTERFACE(*RS232V24) CNN(*NONSWTPP) SNBU(*NO) BITSCHAR(8) PARITY(*NONE) STOPBITS(1) DUPLEX(*FULL) ECHO(*NONE) LINESPEED(115200) MODEM(*NORMAL) MAXBUFFER(4096) FLOWCNTL(*NO) DSRDRPTMR(6) AUTOANSTYP(*CDSTL) RMTANSTMR(60) TEXT('SMS Text Async Mobile Phone Line') MODEMRATE(*FULL) THRESHOLD(*MAX) IDLTMR(5) CTSTMR(25) CMNRCYLMT(2 5)
CRTCTLASC CTLD(SMSTXTCTL) LINKTYPE(*ASYNC) ONLINE(*NO) SNBU(*NO) LINE(SMSTXTLIN) TEXT('SMS Text Async Mobile Phone Controller') CMNRCYLMT(2 5)
CRTDEVASC DEVD(SMSTXTDEV) RMTLOCNAME(SMSTXT01) ONLINE(*NO) CTL(SMSTXTCTL) TEXT('SMS Text Async Mobile Phone Device')
Using a V24 cable like a #0348 attach it to the correct WAN IOA port on the IBM i and to the cellular modem at the other end. The modem will probably require a 9-Way D Male to 25-Way D Female Serial Port Adaptor.
Turn the modem on with the supplied power supply and then vary the line on. The line, controller and device should all go active.
Type STRITF SMSTXT01 and then “AT” (excluding the quotes) which should return OK.
Next we need to create the ICF file used to communicate with the modem. Place the following code into a source member named “SMSICF” of type “ICFF” and compile using option 14.
R INDTA TEXT('INPUT DATA') INDTA# 4096A R FMH TEXT('FUNCTION MANAGEMENT HEADER') FMH FMH# 7A R INVITE TEXT('INVITE') INVITE R OUTDTA TEXT('OUTPUT DATA') VARLEN(&OUTDTALEN) OUTDTA# 1024A OUTDTALEN 5S P R TIMER TEXT('RECORD TIMEOUT') TIMER(&TIMERLEN) TIMERLEN 6S P
In order to run the program as a command place the following command source code into a source member of type “CMD” and compile using option 14.
CMD PROMPT('Send SMS Text Message') PARM KWD(MOBPHONENO) TYPE(*CHAR) LEN(16) + RSTD(*NO) MIN(1) ALWUNPRT(*NO) FULL(*NO) + CASE(*MIXED) PROMPT('Mobile Phone Number') PARM KWD(SMSTEXTMSG) TYPE(*CHAR) LEN(140) + RSTD(*NO) MIN(1) ALWUNPRT(*NO) FULL(*NO) + CASE(*MIXED) PROMPT('SMS Text Message')
Copy the RPG source below into a SQLRPGLE source member named SENDSMSTXT and compile using option 14.
**free //---------------------------------------------------------------------------------------------// // All source code included in this document are provided on an "AS IS" basis without warranty // // of any kind. NOVAGEM WILL NOT BE LIABLE FOR ANY ACTUAL, DIRECT, SPECIAL, INCIDENTAL, OR // // INDIRECT DAMAGES OR FOR ANY ECONOMIC CONSEQUENTIAL DAMAGES INCLUDING LOST PROFITS OR // // SAVINGS. // //---------------------------------------------------------------------------------------------// // Send a text message using an async line configuation and a cellular modem. // Novagem Ltd ctl-opt dftactgrp(*NO) option(*NOSHOWCPY:*NODEBUGIO); dcl-f SMSICF workstn infds(SMSICFinfDS) maxdev(*file) infsr(*pssr) usropn; /copy qsysinc/qrpglesrc,qdcrcfgs /copy qcpysrc,usec dcl-s #t int(10); dcl-s modemDta char(562); dcl-s sentESC ind; dcl-s msgChr char(140); dcl-s msgHex char(560); dcl-s qCmd char(2000); dcl-s readFailed ind; dcl-s retCod int(10); dcl-s retMsg char(132); dcl-s writeFailed ind; dcl-ds chrSet; *N char(122) inz('- @£$¥èéùìòÇØøÅå_ÆæßÉ !#¤%&''()*+,-./0123456789:;<=>?¡ABCDEFGHI- JKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà^{}\[~- ]"'); chrSetAr char(1) dim(122) pos(1); end-ds; dcl-ds hexSet; // Hex characters for the GSM 03.38 alphabet *N char(488) inz('- 00 01 02 03 04 05 06 07 08 09 0B 0C 0E 0F 11 - 1C 1D 1E 1F 20 21 23 24 25 26 27 28 29 2A 2B - 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A - 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 - 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 - 59 5A 5B 5C 4D 5E 5F 60 61 62 63 64 65 66 67 - 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 - 77 78 79 7A 7B 7C 7D 7E 7F 1B141B281B291B2F1B3C1B3D- 1B3E22 '); hexSetAr char(4) dim(122) pos(1); end-ds; dcl-ds SMSICFinfDS; icfstatus *status; icfroutine *routine; icfformat char(8) pos(38); icfmsg char(7) pos(46); icfpgmdev char(10) pos(273); icfreclen int(10) pos(372); icfmajmin char(4) pos(401); end-ds; dcl-pr QCMDEXC extpgm; *n char(250) options(*varsize) const; *n packed(15:5) const; end-pr; dcl-pr QDCRCFGS_ extpgm('QDCRCFGS'); *n char(32767) options(*varsize); *n int(10) const; *n char(8) const; *n char(10) const; *n char(10) const; *n char(32767) options(*varsize); end-pr; dcl-pi SENDSMSTXT; mobPhoneNo char(16) const; smsTextMsg char(140) const; returnCode int(10) options(*nopass); returnMessage char(132) options(*nopass); end-pi; // Get the status of the device clear QDCS0100; QDCRCFGS_(QDCS0100:%len(QDCS0100):'CFGS0100':'*DEVD':'SMSTXTDEV':qusec); if QUSEI=*BLANKS and QDCCST00<>'ACTIVE'; exsr ResetLine; // Reset the line if the status is not showing as active and only if the line is not in use endif; // ----------------------------------------------------- dow 0=0; // ----------------------------------------------------- // Delete any existing ICF file override and re-apply qCmd='DLTOVRDEVE PGMDEV(SMSMDM) LVL(*JOB)'; monitor; QCMDEXC(qCmd:%len(%trimr(qCmd))); on-error; endmon; qCmd='OVRICFDEVE PGMDEV(SMSMDM) RMTLOCNAME(SMSTXT01) CMNTYPE(*ASYNC) DEV(*LOC) SECURE(*YES) OVRSCOPE(*JOB)'; monitor; QCMDEXC(qCmd:%len(%trimr(qCmd))); open(e) SMSICF; acq(e) 'SMSMDM' SMSICF; on-error; retCod=-1; retMsg='Unable to acquire ICF file.'; leave; endmon; if icfmsg='CPF5332'; // Exit if line in use retCod=-1; retMsg='Configuration line already in use.'; leave; endif; // Put modem into Text mode. In Text mode maximum message length is 140. In PDU mode it is 160 (CMGF=0). TIMERLEN=11; sentESC=*OFF; modemDta='AT+CMGF=1'+x'0D'; exsr WriteICFDta; if writeFailed; leave; endif; // Read text mode "OK" reply from modem exsr ReadICFDta; if readFailed; exsr ResetMdm; if readFailed; leave; endif; endif; // Put modem into hex mode TIMERLEN=11; sentESC=*OFF; modemDta='AT+CSCS="HEX"'+x'0D'; exsr WriteICFDta; if writeFailed; leave; endif; // Read text mode "OK" reply from modem exsr ReadICFDta; if readFailed; leave; endif; // Send mobile phone number modemDta='AT+CMGS='+%trim(mobPhoneNo)+x'0D'; exsr WriteICFDta; if writeFailed; leave; endif; // Read ">" reply from modem that invites message input exsr ReadICFDta; if readFailed; leave; endif; // Send message to modem terminated with carriage return and control Z msgChr=smsTextMsg; // Maximum length of text message is 140 in Text mode. exsr CnvMsg2GSMHexChr; // Convert the message to the equivalent hex characters representing the GSM 03.38 alphabet modemDta=%trim(msgHex)+x'0D3F'; exsr WriteICFDta; if writeFailed; leave; endif; // Check receive buffer for continuation symbol ">" exsr ReadICFDta; if readFailed; leave; endif; // Close ICF file and exit program monitor; rel(e) 'SMSICF' SMSICF; close(e) SMSICF; on-error *file; leave; endmon; // Exit send loop leave; // --- enddo; // --- // Exit program *INLR=*ON; if %parms>2; returnCode=retCod; endif; if %parms>3; returnMessage=retMsg; endif; // *************** begsr WriteICFDta; // *************** monitor; OUTDTA#=%trim(modemDta); OUTDTALEN=%len(%trim(OUTDTA#)); write(e) OUTDTA; on-error *file; retCod=-1; retMsg='Failure writing to modem.'; writeFailed=*ON; endmon; // *** endsr; // *** // ************** begsr ReadICFDta; // ************** // ----- dow 0=0; // ----- monitor; write(e) timer; // Setup timer with the duration given by TIMERLEN write(e) invite; // Invoke timer to return control to the program after TIMERLEN read(e) SMSICF; // Read the reply from the modem post SMSICF; // Populate the ICF file information data structure if icfmajmin='0310'; readFailed=*ON; // Timeout then leave retCod=-1; retMsg='Timeout while reading from modem.'; leave; endif; on-error *file; retCod=-1; retMsg='Failure reading from modem.'; readFailed=*ON; // Error then leave leave; endmon; // If nothing has been received then go round again if icfreclen<=0; iter; endif; // Leave if doing modem initialize with ESC if sentESC; leave; endif; // If we have expected result then continue if %scan('>':INDTA#)>0 or %scan('OK':INDTA#)>0; leave; endif; // --- enddo; // --- // *** endsr; // *** // ******************** begsr CnvMsg2GSMHexChr; // ******************** for #t=1 to %len(%trimr(msgChr)); monitor; msgHex=%trim(msgHex)+hexSetAr(%lookup(%subst(msgChr:#t:1):chrSetAr)); on-error; endmon; endfor; // *** endsr; // *** // ************* begsr ResetLine; // ************* qCmd='VRYCFG CFGOBJ(SMSTXTLIN) CFGTYPE(*LIN) STATUS(*OFF) ASCVRYOFF(*YES)'; QCMDEXC(qCmd:%len(%trimr(qCmd))); qCmd='VRYCFG CFGOBJ(SMSTXTLIN) CFGTYPE(*LIN) STATUS(*ON)'; QCMDEXC(qCmd:%len(%trimr(qCmd))); qCmd='DLYJOB DLY(2)'; QCMDEXC(qCmd:%len(%trimr(qCmd))); // *** endsr; // *** // ************ begsr ResetMdm; // ************ // Clear modem with ESC TIMERLEN=5; readFailed=*OFF; sentESC=*ON; modemDta=x'1B0D0D'; exsr WriteICFDta; // Read buffer exsr ReadICFDta; // *** endsr; // *** // ********* begsr *pssr; // ********* *INLR=*ON; if %parms>2; returnCode=retCod; endif; if %parms>3; returnMessage=retMsg; endif; // *** endsr; // ***
Now we have all the necessary configuration objects and code in place to send an SMS text from the IBM i. Making sure that the command, ICF file and SQLRPGLE program that were all created above are in a library in your library list type SENDSMSTXT MOBPHONENO(NNNNNNNNNNN) SMSTEXTMSG(‘SMS Text Message’) where NNNNNNNNNNN is the receiving mobile phone number. If all is working ok then the text message should be sent to the mobile phone. Bear in mind the example SQLRPGLE program outlined above is using the text operating mode which is slightly slower than PDU mode and because we are using the HEX codepage the message length is limited to 140 instead of 160 characters.
The next part of this blog will outline how Argon2id will be used to enable the password hashing functionality.