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.