This post is going to add a level of security to that process, it is probably too little for an application that is accessed from outside your own network (particularly as the password we send in will be text based) but it is a starting point which we can work with. The method we have chosen to add this level of security requires the job adopts the profile of the user requesting access which is achieved by using the Profile Handle API’s. Each time a user requests access we will extract a link to that profile and set the job up to use it, once the user signs off we will set the profile back to the original profile (the one the job was loaded with). If the user send in incorrect data the process is stopped and the connection closed, the only way the user can communicate again is by starting a new connection request.
We are going to create a new Service Program which will be utilized by the WORKER program, its a good fit for this because we could have lots of WORKER programs running to respond to outside requests. When the user needs to sign on we will have to communicate with the client to pull in the profile and password to be used, this will be our first function in the Service Program.
Add a new member to OSLIB/H called SRVFUNC, this will be the header we will include for all of the Server Functions we are going to build. The following code is to be added to the member.
#ifndef SRVFUNC_h #define SRVFUNC_h #include <H/COMMON> // common header #include <H/MSGFUNC> // message functions #include <qsyphandle.h> // profile handles #include <netdb.h> // Socket DB #include <sys/socket.h> // Sockets #include <netinet/in.h> // Socket net #include <arpa/inet.h> // ARPA structs #include <errno.h> // Error return header #include <netdb.h> // net DB header #include <sys/errno.h> // sys error header #include <arpa/inet.h> // inet header #include <signal.h> // signal funcs #include <except.h> // exception funcs #include <iconv.h> // conversion header #include <qtqiconv.h> // iconv header int Handle_SO(int,char *,iconv_t,iconv_t); #endifA lot of the includes have been removed from the WORKER program code with a few additions, we have also defined the call requirements for the Handle_SO function we are going to build for the Service Program..
Next we are going to create the function which will communicate with the client and get the profile and password information. if all works well it will change the job to use the profile handle the API returns.
#include <H/SRVFUNC> // Server Functions Header // Function Handle_SO() // purpose: handle a sign on request // @parms // socket // Prf Handle // returns 1 on success int Handle_SO(int accept_sd, char *Usr_hdl, iconv_t a_e_ccsid, iconv_t e_a_ccsid) { int rc = 0; // return code int len = 0; // counter int ret = 0; // return code size_t insz; // input len size_t outsz; // output size char recv_buf[_32K]; // recv buffer char msg_dta[_MAX_MSG]; // message buffer char *out_ptr; // buffer ptr char *in_ptr; // buffer ptr char Profile[10] = {' '}; // Profile char Pwd[128] = {' '}; // password char convBuf[132]; // conversion buffer Os_EC_t Error_Code = {0}; // error struct Error_Code.EC.Bytes_Provided = _ERR_REC; // first we need to retieve the profile sprintf(msg_dta,"Please enter your Profile name : "); len = strlen(msg_dta); in_ptr = msg_dta; out_ptr = convBuf; insz = len; outsz = 132; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convBuf,len,0); // make sure the data was sent if(rc != len) { return -1; } // make sure not too long. Our test server sends a '0A' at the end of the input so // it needs to be dropped. In a future implementation we should consider alternatives rc = recv(accept_sd,recv_buf,_32K,0); // copy to the Profile holder which is initialized with all blanks after conversion in_ptr = recv_buf; out_ptr = convBuf; insz = rc; outsz = 132; ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); // strip off the 'A0' byte. len = rc -1; if(len > 10) { sprintf(msg_dta,"Profile too long Sign On aborted"); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); return -1; } memcpy(Profile,convBuf,len); // now get the password sprintf(msg_dta,"Please enter your Password : "); len = strlen(msg_dta); in_ptr = msg_dta; out_ptr = convBuf; insz = len; outsz = 132; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convBuf,len,0); if(rc != len) { return -1; } // recv the password and convert rc = recv(accept_sd,recv_buf,_32K,0); in_ptr = recv_buf; out_ptr = convBuf; insz = rc; outsz = 132; ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); // strip off the 'A0' byte. len = rc -1; if(len > 128) { sprintf(msg_dta,"Password too long"); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); return -1; } memcpy(Pwd,convBuf,len); // now check the password against the profile, CCSID of 0 means use the job ccsid QsyGetProfileHandle(Usr_hdl, Profile, Pwd, rc, 0, &Error_Code); if(Error_Code.EC.Bytes_Available) { snd_error_msg(Error_Code); return -1; } return 1; }We are going to convert the text used for the communication so we need the conversion tables from the WORKER program and the socket that is to be used. We will pass a holder for the Profile handle which is created by the API as these are finite resources in the system, its important that we release any profile handles as soon as they are finished with. As this function will be doing some conversions between ASCII and EBCDIC as part of the communication stream we pass in the tables already created by the WORKER program.
The socket has been accepted so we first send off a request to the client to enter the Profile name they wish to use, this has to be converted to ASCII before being sent. The data we received is terminated by a ‘0A’ combination, we need to ensure we do not recognize that as part of the Profile so we simply decrement the counter which states how much data was received ( this works in our case but may be different dependent on the client platform test application) . We also make sure the data is not longer than we can accept (10 characters for a Profile). Next we send off a request for the password which follows the same process and is stored ready to be passed into the API we are going to use to verify the input and get the profile handle. You will see we have set the maximum length of the password to 128 which is the maximum length on the IBM i. However you could adjust that based on the relevant system settings. the Profile and Password buffers are passed into the API, if the profile exists and the password matches a 12 byte handle is returned which can be used to set the jobs profile.
We had to make a number of changes to the WORKER program to allow this to work, we have implemented a switch process that will work against a converted 4 byte number (passed as a character array). We have to copy this to a temp holder which is NULL terminated for the atoi() function to convert to an integer, we will use this integer to decide what request the user is sending in. Here is the code we will use, later we will explain some of the changes.
/** * * Revision log.: * Date Author Revision * 2018 Chris H 1.0 * * Copyright (C) <2018> <Chris Hird> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * with this program; if not, write to the Free Software Foundation, * Inc.,51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * You can contact the author via Email chrish@shieldadvanced.com */ #include <H/SRVFUNC> // Server functions #include <qsyphandle.h> // profile handles #define _QSIZE 256 #define _CPYRGHT "Copyright @ Shield Advanced Solutions Ltd 1997-2018" #pragma comment(copyright,_CPYRGHT) // Signal catcher void catcher(int sig) { // you can do some messaging etc in this function, it is called // when the kill signal is received from the TESTSVR program exit(0); } int main(int argc, char **argv) { int listen_sd, accept_sd = 0; // file descriptors int rc = 0; // return codes int ret = 0; // return vals int Stop = 0; // stop flag int i = 0; // counter int j = 0; // counter int offset = 0; // offset marker int total_bytes = 0; // counter int bytes_processed = 0; // counter int valid_user = 0; // valid user flag int req = 0; // request flag size_t insz; // input len size_t outsz = _32K; // outbuf size char recv_buf[_32K]; // recv buffer char convbuf[_32K]; // conversion buffer char msg_dta[_MAX_MSG]; // message buffer char Usr_hdl[12]; // Usr profile handle char Cur_hdl[12]; // Current profile handle char key[5] = {'\0'}; // request key char *out_ptr; // buffer ptr char *in_ptr; // buffer ptr struct sockaddr_in addr; // socket struct struct sigaction sigact; // Signal Action Struct QtqCode_T jobCode = {0,0,0,0,0,0}; // (Job) CCSID to struct QtqCode_T asciiCode = {819,0,0,0,0,0}; // (ASCII) CCSID from struct iconv_t a_e_ccsid; // convert table struct iconv_t e_a_ccsid; // convert table struct Os_EC_t Error_Code = {0}; // error struct Error_Code.EC.Bytes_Provided = _ERR_REC; // Set up the signal handler sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigact.sa_handler = catcher; sigaction(SIGTERM, &sigact, NULL); // create the conversion tables // ASCII to EBCDIC a_e_ccsid = QtqIconvOpen(&jobCode,&asciiCode); if(a_e_ccsid.return_value == -1) { sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); exit(-1); } // EBCDIC to ASCII e_a_ccsid = QtqIconvOpen(&asciiCode,&jobCode); if(e_a_ccsid.return_value == -1) { iconv_close(a_e_ccsid); sprintf(msg_dta,"QtqIconvOpen Failed %s",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); exit(-1); } // get the current profile handle for the job QsyGetProfileHandleNoPwd(Cur_hdl, "*CURRENT ", "*NOPWDCHK ", &Error_Code); if(Error_Code.EC.Bytes_Available) { snd_error_msg(Error_Code); exit(-1); } // connect to the socket passed as argv[1] listen_sd = atoi(argv[1]); do { // only accept new connection if not already set if(valid_user == 0) { accept_sd = accept(listen_sd, NULL, NULL); setsockopt(accept_sd,SOL_SOCKET,SO_RCVBUF,CHAR_32K,sizeof(CHAR_32K)); if(accept_sd < 0) { sprintf(msg_dta,"accept() failed",strerror(errno)); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); close(listen_sd); exit(-1); } } memset(recv_buf,'\0',_32K); rc = recv(accept_sd, recv_buf, _32K, 0); // at this point we need to get the key information and switch to sort // the first 4 bytes will be the key and it must be sent separately the enter key is also passed if(rc == 5) { in_ptr = recv_buf; out_ptr = convbuf; insz = rc; outsz = _32K; ret = (iconv(a_e_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); memcpy(key,convbuf,4); sprintf(msg_dta,"Key : %s",key); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); req = atoi(key); switch(req) { case 0 : // signon request if(Handle_SO(accept_sd,Usr_hdl,a_e_ccsid,e_a_ccsid) != 1) { sprintf(msg_dta,"Sign On failed : "); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); in_ptr = msg_dta; out_ptr = convbuf; insz = strlen(msg_dta); outsz = _32K; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convbuf,strlen(msg_dta),0); close(accept_sd); } valid_user = 1; break; case 1 : // sign off request if(valid_user == 1) { QsySetToProfileHandle(Cur_hdl, &Error_Code); if(Error_Code.EC.Bytes_Available > 0) { snd_error_msg(Error_Code); } // release the profile handle for the user QsyReleaseProfileHandle(Usr_hdl, &Error_Code); // send closing message sprintf(msg_dta,"OK signing off and closing socket : "); in_ptr = msg_dta; out_ptr = convbuf; insz = strlen(msg_dta); outsz = _32K; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convbuf,strlen(msg_dta),0); // close the socket close(accept_sd); valid_user = 0; } break; default : sprintf(msg_dta,"Unknown Request : "); snd_msg("GEN0001",msg_dta,strlen(msg_dta)); in_ptr = msg_dta; out_ptr = convbuf; insz = strlen(msg_dta); outsz = _32K; ret = (iconv(e_a_ccsid,(char **)&(in_ptr),&insz,(char **)&(out_ptr),&outsz)); rc = send(accept_sd,convbuf,strlen(msg_dta),0); break; } // switch } // key request if(valid_user == 0) { close(accept_sd); } } while(Stop == 0); }As this program will be assuming the attributes of any number of profiles it is important that we can revert it back to the original profile once the conversation has ended between the client and it. To do this we will retrieve a profile handle for the *CURRENT user at program start (this is the profile the job was loaded with) and store it ready to use later when we need to switch back.
We need to determine when the existing conversation has ended, we do this by setting a variable called valid_user. If the valid_user variable is not set (== 1) we will wait for a connection request on the socket we have been passed. Once the connection has been made we will receive the initial request which will be used to determine the action we need to take, this will be a 4 byte character array which is converted to an integer to be used in the switch process. If the user does not follow the required process or it sends in an invalid request we will drop into the default : process which is to send a message stating its an invalid request. If it converts the input characters to 0 we know its a sign on request and we will call the Service Program function to collect the data, a positive return means it worked and so we set valid_user to 1. The only other action we will accept from there is a 0001 request which will switch the profile back to the original, destroy the profile handle, send a message, close the connection and set valid_user to 0 before going back to the accept state. It will continue to go through that loop until the TESTSVR program sends the kill signal.
There are still a number of changes we can add to improve the security and clean up on the kill signal being received, but for now this is enough. To create the programs we are going to need to bring the SRVFUNC Service Program into the mix. This requires a couple of commands to be run that will allow it to be created and seen by any programs that need it exported functions.
First we will create the Module from the source.
CRTCMOD MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QCSRC) SRCMBR(SRVFUNC) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)Next we need to update the export source file we use to add the new Service Program exports and add the Service program to the binding directory.
RTVBNDSRC MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QSRVSRC)The Service Program can now be created ready for use.
ADDBNDDIRE BNDDIR(OSLIB/OS) OBJ((SRVFUNC *SRVPGM *DEFER))
CRTSRVPGM SRVPGM(OSPGM/SRVFUNC) MODULE(OSLIB/SRVFUNC) SRCFILE(OSLIB/QSRVSRC) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)We have made a number of changes to the WORKER program plus it now relies on the SRVFUNC Service program so we will recreate the program.
CRTCMOD MODULE(OSLIB/WORKER) SRCFILE(OSLIB/QCSRC) SRCMBR(WORKER) DBGVIEW(*SOURCE) REPLACE(*YES) TGTRLS(V7R1M0)Go to the SVRMAIN menu with OSPGM in your library list and restart the jobs. Once they are up and running you can test the process using your test program in our case its NetCat running under Linux. Here is a sample of the communication from a terminal session.
CRTPGM PGM(OSPGM/WORKER) MODULE(OSLIB/WORKER) BNDDIR(OSLIB/OS) TGTRLS(V7R1M0)
chrish@webserver:~$ nc sas4.shield.local 12345 0000 Please enter your Profile name : CHIRD Please enter your Password : ******* 0003 Unknown Request : 0001 OK signing off and closing socket :One important point here is the IBM i does not like the Profile to be sent in as anything but upper case, the password does not matter but sending in ‘chird’ results in the Profile not being found.
This is only one of many ways to handle a communication process with the IBM i via TCP/IP and ASCII clients, it is limited by the fact that is holds any further connections to this program until the current conversation ends. We will look at how to improve the scalability in a future post but for now this is where we will stop.
In this post we have looked at how we can add security to the comminications using some of the API’s available, we can take this further by adding some encryption particularly for the password but that will require a client application that is specific for this server. Next post will look at how we can take additional actions using the profile security we have added.
If you have any questions etc please feel free to contact us. If you would like help in setting up your own in house server we provide services for that.
Chris…