Oct 29

Integrating IBM i CGI programs into Linux Web Server

We have been working with a number of clients now who have CGI programs (mainly RPG) that have been used as part of web sites which were hosted on the IBM Apache Server. These programs build the page content using a write to StdOut process. They have now started the migration to PHP based web sites and need to keep the CGI capability until they can re-write the existing CGI application to PHP.

The clients are currently running the iAMP server (they could use the ZendServer as well) for their PHP content and will need to access the CGI programs from that server. We wanted to test the process would run regardless of the Apache server used (IBM i, Windows,Linux etc) so we decided to set up the test using our Linux Apache server. The original PHP Server on the IBM i used a process that involved the passing of requests to another server (ProxyPass) which is what we will use to allow the Linux Server to get the CGI content back to the originating request. If you want to know more about the Proxy Process you can find it here.

First of we set up the IBM Apache Server to run the CGI program which we need. The program is from the IBM knowledge center called SampleC which I hacked to just use the POST method (code to follow) which I complied into a library called WEBPGM. Here is the content of the httpd.conf for the apachedft server.


# General setup directives
Listen 192.168.200.61:8081
HotBackup Off
TimeOut 30000
KeepAlive Off
DocumentRoot /www/apachedft/htdocs
AddLanguage en .en
DefaultNetCCSID 819
Options +ExecCGI -Includes
CGIJobCCSID 37
CGIConvMode %%EBCDIC/MIXED%%
ScriptAliasMatch ^/cgi-bin/(.*).exe /QSYS.LIB/WEBPGM.LIB/$1.PGM

The Listen line states that the server is going to listen on port 8081. Options allows the execution of CGI progrmas (+ExecCGI). I have set the CGI CCSID and conversion mode and then set up the re-write of any request that has a url with ‘/cgi-bin/’ and has a extension of .exe to the library format required to call the CGI program.

The program is very simple, I used the C version of the program samples IBM provides and hacked the content down to the minimum I needed. I could have altered it even further to remove the write_data() function but it wasn’t important. Here is the code for the program which was compiled into the WEBPGM lib.


#include <stdio.h> /* C-stdio library. */
#include <string.h> /* string functions. */
#include <stdlib.h> /* stdlib functions. */
#include <errno.h> /* errno values. */
#define LINELEN 80 /* Max length of line. */

void writeData(char* ptrToData, int dataLen) {
div_t insertBreak;
int i;

for(i=1; i<= dataLen; i++) {
putchar(*ptrToData);
ptrToData++;
insertBreak = div(i, LINELEN);
if( insertBreak.rem == 0 )
printf("<br>");
}
return;
}

void main( int argc, char **argv) {
char *stdInData; /* Input buffer. */
char *queryString; /* Query String env variable */
char *requestMethod; /* Request method env variable */
char *serverSoftware; /* Server Software env variable*/
char *contentLenString; /* Character content length. */
int contentLength; /* int content length */
int bytesRead; /* number of bytes read. */
int queryStringLen; /* Length of QUERY_STRING */

printf("Content-type: text/html\n");
printf("\n");
printf("<html>\n");
printf("<head>\n");
printf("<title>\n");
printf("Sample AS/400 HTTP Server CGI program\n");
printf("</title>\n");
printf("</head>\n");
printf("<body>\n");
printf("<h1>Sample AS/400 ILE/C program.</h1>\n");
printf("<br>This is sample output writing in AS/400 ILE/C\n");
printf("<br>as a sample of CGI programming. This program reads\n");
printf("<br>the input data from Query_String environment\n");
printf("<br>variable when the Request_Method is GET and reads\n");
printf("<br>standard input when the Request_Method is POST.\n");
requestMethod = getenv("REQUEST_METHOD");
if ( requestMethod )
printf("<h4>REQUEST_METHOD:</h4>%s\n", requestMethod);
else
printf("Error extracting environment variable REQUEST_METHOD.\n");
contentLenString = getenv("CONTENT_LENGTH");
contentLength = atoi(contentLenString);
printf("<h4>CONTENT_LENGTH:</h4>%i<br><br>\n",contentLength);
if ( contentLength ) {
stdInData = malloc(contentLength);
if ( stdInData )
memset(stdInData, 0x00, contentLength);
else
printf("ERROR: Unable to allocate memory\n");
printf("<h4>Server standard input:</h4>\n");
bytesRead = fread((char*)stdInData, 1, contentLength, stdin);
if ( bytesRead == contentLength )
writeData(stdInData, bytesRead);
else
printf("<br>Error reading standard input\n");
free(stdInData);
}
else
printf("<br><br><b>There is no standard input data.</b>");
printf("<br><p>\n");
serverSoftware = getenv("SERVER_SOFTWARE");
if ( serverSoftware )
printf("<h4>SERVER_SOFTWARE:</h4>%s\n", serverSoftware);
else
printf("<h4>Server Software is NULL</h4>");
printf("</p>\n");
printf("</body>\n");
printf("</html>\n");
return;
}

Sorry about the formatting!

That is all we had to do on the IBM i server, we restarted the default apache instance and set to work on creating the content required for the Linux Server.
The Linux Server we use is running Proxmox, this allows us to build lots of OS instances (Windows,Linux etc) for testing. The Virtual Server is running a Debian Linux build with a standard Apache/PHP install. The Apache servers are also running Virtual hosts (we have 3 Virtual Linux servers running Apache), this allows us to run many websites from a single server/IP address. We created a new server called phptest (www.phptest.shield.local) running on port 80 some time ago for testing our PHP scripts so we decided to use this server for the CGI test. As the Server was already running PHP scripts all we had to do was change the configuration slightly to allow us to pass the CGI requests back to the IBM i Apache server.

The sample code provided by IBM which will run on the Linux Server is listed below.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Untitled Document</title>
</head>

<body>
<form method="POST" action="/cgi-bin/samplec.exe">
<input name="YourInput" size=42,2>
<br>
Enter input for the C sample and click <input type="SUBMIT" value="ENTER">
<p>The output will be a screen with the text,
"YourInput=" followed by the text you typed above.
The contents of environment variable SERVER_SOFTWARE is also displayed.
</form>
</body>
</html>

When the url is requested the following page is displayed.

sampleC

Sample C input page


The Server needs to know what to do with the request so we have to redirect the request from the Linux Server to the IBM i server using the ProxyPass capabilities. You will notice from the code above that we are using the POST method from the form submission and we are going to call ‘/cgi-bin/samplec.exe’. This will be converted on the target system to our program call. The following changes were made to the Linux Apache configs and the server was restarted.

ProxyPreserveHost On
ProxyPass /cgi-bin/ http://192.168.200.61:8081/cgi-bin/
ProxyPassReverse /cgi-bin/ http://192.168.200.61:8081/cgi-bin/

This allows the Linux Server to act as a gateway to the IBM i Apache server, the client will see the response as if it is from the Linux server.
When we add the information into the input field on the page and press submit the following is displayed on the page.
samplec-out

Output from CGI program

Note:
Those reading carefully will notice the above page shows a url of www1.phptst.shield.local not www.phpest.shield.local as shown in the output screen below. This is because we also tested the iAMP server running on another IBM i to link to the same IBM Apache Server used in the Linux test using exactly the same code.

This is a useful setup for being able to keep existing CGI programs which are being presented via the IBM Apache Server while you migrate to a new PHP based interface. I would rather have replaced the entire CGI application for the clients with a newer and better PHP based interface, but the clients just wated a simple and quick fix, maybe we will get the opportunity to replace it later?

Note:-
The current version of iAMP which available for download from the Aura website does not support mod_proxy so if this is something you need to implement let us know and we can supply a version which contains the mod_proxy modules. I hope Aura will update the download if sufficient people need the support which will cut us out of the loop.

If you need assistance creating PHP environments for IBM i let us know. We have a lot of experience now with setting up PHP for IBM i and using the Easycom toolkit for access IBM i data and objects.

Chris…

Jun 05

IBM i Mobile with IBM i Access for Web

We have been resistant to implement anything to do with the IBM HTTP server for a number of reasons, the main one being that we feel Linux is a better option for running any HTTP services on. However when we heard that IBM was now providing a mobile interface for the IBM i as part of the 7.2 release we felt we should take a closer look and see if it was something we could use. To our surprise we found the initial interaction very smooth and fast.

Installation was fairly simple other than the usual I don’t need to read the manuals part! We had installed 7.2 last week with the intention of reviewing the mobile access, unfortunately we did not realize that there were already Cum PTF’s and PTF Groups available. Our first try at the install stopped short when we thought Websphere was a requirement, as it turns out it can be used but is not a prerequisite. Thanks to a LinkedIn thread we saw and responded to our misconception was rectified and we set about trying to set up the product again. We followed all of the instructions (other than making sure the HTTP PTF Group was installed :-() and it just kept giving us a 403 Forbidden message for /iamobile. Took a lot of rummaging through the IFS directories to find out that when the CFGACCWEB command run it logged the fact that a lot of directories were missing (even though the message sent when it completed stated it completed successfully, maybe IBM should look at that?) so we reviewed all of the information again. It turns out the Mobile support is delivered in the PTF Group so after downloading and installing the latest CUM plus all of the PTF Groups we found the interface now works.

As I mentioned at the beginning I am surprised at just how snappy it is, we don’t have hundreds of users but our experience of the Systems Director software for IBM i made us very wary about actually using anything to do with the IBM i HTTP servers so we had no high expectations of this interface. We saw no lag at all in the page requests and the layout is very acceptable. When the time came to enter information the screen automatically zoomed into the entry fields (I like that as my eye sight is not what it used to be). We looked at a number of the screens but have not gone through every one. I really like the ability to drill down into the IFS and view a file (no edit capability) which will be very useful for viewing logs in the IFS.

Here are a few of the screen shots we took, the first set is from an iPod the second is from the iPad, we were going to try the iPhone but the iPod is the same size output so jsut stuck with testing from the iPod (yes we like Apple products, we would get off our Microsoft systems if IBM would release the much rumored RDi for the MAC). I think IBM did a good job in the page layouts and content.

iPod Display of file in IFS.

iPod Display of file in IFS.

iPod display of messages

iPod display of messages

iPod SQL output

iPod SQL output

iPod sign on screen shield7

iPod sign on screen shield7

iPod 5250 session

iPod 5250 session

iPod initial screen

iPod initial screen

The iPad screens.

iPad Display of messages on Shield7

iPad Display of messages on Shield7

iPad 5250 session, note how it is connected to another system (shield6)

iPad 5250 session, note how it is connected to another system (shield6)

iPad SQL output

iPad SQL output

iPad List of installed Licensed Programs

iPad List of installed Licensed Programs

iPad initial page

iPad initial page

Clicking on the images will bring up a larger one so if like me you are a bit blind you can see the content. Also take notice of the 5250 connection to the Shield6 system, Shield6 is not running the mobile access or the HTTP server so we were surprised when we could start a session to the Shield6 system using the mobile access from the Shield7 system. I definitely think this is a big improvement on anything else we have seen in terms of speed using the IBM HTTP server.

If you don’t have the Mobile support installed do it now! the fact that it is PTF’d all the way back to V6R1 is a big benefit. We will certainly be adopting this as our preferred access method from our mobile devices especially to provide support from our mobile devices while we are away from the office.

Chris…

Aug 23

Sending emails with attachments from the IBM i

OK I have to admit I did not think of this first, I found it when I checked the latest Blog postings on iPlanet! You can find the original here. I just searched on the web to find the IBM documentation which is located here.

The reason I was really interested was due to a client issue where the iAMP server does not have any built in email function (mail()), so I was looking at how to build my own email function.

The functions I built were based on the code we produced for our HA4i product which has an inbuilt email manager for its notification process, these are written in C and use the low level socket functions to send the email directly to a SMTP server. Nothing fancy but it does work and as we are not email guru’s we thought keeping it simple was out best option. All went well until we though about adding attachments to the email, the HA4i code has no ability to add attachments because it does not need it. After a lot of reading and combing through RFC’s and Wiki pages we found the solution we needed, multipart mime was needed so we had to structure the code to allow the attachments to be correctly embedded into the email body.

After some trial and error we did get the process to work and we now have correctly formatted emails with attachments being sent from the IBM i. But we wanted to see if there are other options (we like options :-)) which is how we came across the above blog post. Running the command in a CL program etc was not what we needed, we wanted to provide a PHP version. Thankfully the i5_toolkit provides the answer, we just needed to call the command via the i5_command() function! Here is the sample code we used to test it with.

The page which is called connects to the IBM i and then uses the following to call the function

send_email_cmd($conn,"chrish@shieldadvanced.ca","This is a test message with IBM Command","/home/CHRISH/mail-1.2.0.tar");

This if the code for the function

function send_email_cmd(&$conn,$recipient,$subject,$file) {
$command = "SNDSMTPEMM RCP((" .$recipient .")) SUBJECT('" .$subject ."') NOTE('

This is the body of the email

I can enter things using HTML and format things in a most pretty way

cool') ATTACH(('" .$file ."' *OCTET *BIN)) CONTENT(*HTML)";
if(!i5_command($command,$conn)) {
echo("Failed to submit command " .$command);
}
else {
echo("Successfully sent email");
}
}

That was all there was to it! You could add a lot more code to verify the attachment types etc etc etc but our test proved the functionality does work.
Thanks to Nick for pointing out the command.

Chris…

Apr 24

FTP Guard4i Log Viewer

As promised we have now developed the log viewer which shows the events which have been logged by the FTP processes. The log view has a number of columns each of which is sortable but the default sort is done by the Date and Time with the latest entry at the top. Here is sample view of the log on our test server.

FTP Guard4i log view

A sample of the events logged by FTP Guard4i.

A couple of interesting things came about while generating the log, you will see that we deleted a file ‘/home/CHRISH/??_???????`%??>?>????????’, one of the issues we all come across from time to time is where a file in the IFS has a strange name, deleting the file using the normal IFS commands is not possible as it will always return ‘File not found’ errors. Using FTP (actually we used FileZilla) you can see that we successfully deleted the file in question. The log also shows a ‘Send File’ operation, that was actually a get operation from the FTP client but the event gets logged as a ‘Server Send File’ operation..

The PHP interface is now pretty much complete but we need to do some more work on the UIM interface to align the data store with the actual output to the UIM Manager. Once that is finished and we have done some more testing FTP Guard4i will be available for download.

Chris…

Apr 23

FTP Guard 4i Take 2

We had been discussing the FTP Guard 4i with a prospect and they mentioned that they would like to be able to monitor the FTP Server and SFTP Server from the FTP interface. So we have added a couple of new features to the status screen that allow the user to administer the FTP Server and the SSHD server which is used for the SFTP connections.

Here is the new status screen

New FTP Guard 4i status screen

FTP Guard 4i take 2

One of the things we did notice when we added the new features and checked they functioned was the SFTP connection takes on the QSECOFR profile in the job and drops the original user profile. We need to take a look at this to see exactly what effect this has? We don’t allow the QSECOFR profile to connect via FTP or SFTP so the security we have set for the user as far as FTP is concerned still applied.

Let us know if you are interested in this kind of solution and what if any additional features you would like to see. The Log viewer is coming along and will be the subject of our next post.

Chris…

Nov 23

A Bit more fun with the File display and update web pages.

OK so we thought we would have a bit more fun with the previous programs which allowed you to move around and update file records. This is very basic code so don’t expect too much, but as we always say it’s free and you can do what you want with it (or not!). We were thinking about what we could add to make it a bit more of a complete project, so we decided to add a few more pages and functions which would allow you to drill down to the data you were interested in updating. REMEMBER this code took us about 1 hour to create so it is not anything like production ready, but it does show some of the power you can use to add new interfaces over your existing programs and products using PHP and the i5_toolkit().

The first thing we did was moved the process back a few steps so we could let the user sign on and then pick the library they were interested in and then displaying the files it contained.

This is the index.php file which is the start of the process. Very simple and all it does is force you to pass in some credentials (sign on info) which it uses to get a list of the libraries on the target system. This is the code which generates the first page.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

*/
// start the session to allow session variables to be stored and addressed
session_start();
require_once("scripts/functions.php");
// get the previous return URL
$ret_url = $_SESSION['ret_url'];
// store this page as return URL
$_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
if(!isset($_SESSION['server'])) {
load_config();
}
?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Display File Content</title>
<!-- add the main CSS -->

</head>

<body>
<?php
// if valid user is set connect using Private connection
if(isset($_SESSION['valid_usr'])) {
$conn = 0;
if(!connect($conn)) {
if(isset($_SESSION['Err_Msg'])) {
echo($_SESSION['Err_Msg']);
$_SESSION['Err_Msg'] = "";
}
echo("Failed to connect");
}
else {
Dsp_Lib_Obj($conn);
}
}
else {?>
<form name=login method="post" action="scripts/login.php">
<table width="20%" align="center" border="1" cellpadding="1">
<tr><td><label>User ID :</label></td><td><input type="text" name="usr" /></td></tr>
<tr><td>Password:</td><td><input type="password" name="pwd" /></td></tr>
<tr><td colspan="2"><?php if(isset($_SESSION['Err_Msg'])) { echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = ''; } ?></td></tr>
<tr><td><label>Connection type</label></td><td><select id="conn_type" name="conn_type"><option value="decrypt" selected="selected">Decrypted</option><option value="encrypt">Encrypted</option></select></td></tr>
<tr><td colspan="2" align=center><input type="submit" value="Log in" /></td></tr><?php
if(isset($_SESSION['Pwd_Err'])) {
if($_SESSION['Pwd_Err'] == 1) { ?>
<tr><td colspan="2" align=center>Sorry the credentials were rejected by the <?php echo($_SESSION['sys']); ?> System</td></tr><?php
$_SESSION['Pwd_Err'] = 0;
}
} ?>
</table>
</form><?php
} ?>
</body>
</html>

The config file is loaded at the beginning of the process which contains the target server to use. The function which does that is contained in the functions.php file which is shown next. this also contains all of the functions used to connect to the server and display the content for other pages. (I hope I cut and pasted everything we used). I did not copy the config file but you should be able to work out the layout and content based on the load_config() function.


<?php
/*
Copyright © 2010, Shield Advanced Solutions Ltd
All rights reserved.

http://www.shieldadvanced.ca/

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.

- Neither the name of the Shield Advanced Solutions, nor the names of its
contributors may be used to endorse or promote products
derived from this software without specific prior written
permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/

function connect(&$conn) {
// reset the the ErrMsg variable
unset($_SESSION['ErrMsg']);
// connect to the i5
$conId = 0;
if (isset($_SESSION['ConnectionID'])) {
$conId = $_SESSION['ConnectionID'];
}
$server = $_SESSION['server'];
$addlibl = array($_SESSION['install_lib']);
// options array for the private connection
$options = array(
I5_OPTIONS_PRIVATE_CONNECTION => $conId,
I5_OPTIONS_IDLE_TIMEOUT => $_SESSION['timeout'],
I5_OPTIONS_JOBNAME => 'PHPTSTSVR');
// connect to the system
if($_SESSION['conn_type'] == 'encrypt')
$conn = i5_pconnect($server,$_SESSION['usr'],d_pwd($_SESSION['pwd']),$options);
else
$conn = i5_pconnect($server,$_SESSION['usr'],$_SESSION['pwd'],$options);
// if connect failed
if(is_bool($conn) && $conn == FALSE) {
$errorTab = i5_error();
if ($errorTab['cat'] == 9 && $errorTab['num'] == 285){
$_SESSION['ConnectionID'] = 0;
$_SESSION['Err_Msg'] = "Failed to connect";
return -1;
}
else {
//set the error message
$_SESSION['Err_Msg'] = "Connection Failed " .i5_errormsg();
// send back to the sign on screen
$_SESSION['ConnectionID'] = 0;
return -1;
}
}
else {
// add the library list
add_to_librarylist($addlibl,$conn);
$ret = i5_get_property(I5_PRIVATE_CONNECTION, $conn);
if(is_bool($ret) && $ret == FALSE) {
$_SESSION['Err_Msg'] = "Failed to retrieve connection ID " .i5_errormsg();
$_SESSION['ConnectionID'] = 0;
return -1;
}
else {
$_SESSION['ConnectionID'] = $ret;
}
}
return 1;
}

/*
* Function to add the install library to the library list
* @parms
* libraries to add to the library list
* connection to use
*/

function add_to_librarylist($libraries,&$conn) {
$curlibl = "";
// get current librarylist
if (!i5_command("rtvjoba",array(),array("usrlibl"=>"curlibl"),$conn))
die("Could not retrieve current librarylist:" . i5_errormsg($conn));
// add our libraries to the librarylist
$curlibl .= " " .implode(" ",$libraries);
// if library list already set a CPF2184 message will be generated, treat as warning and by pass.
if(!i5_command("chglibl",array("libl"=>$curlibl),array(),$conn)) {
if(i5_errormsg($conn) != 'CPF2184')
echo("Could not change current librarylist:" . i5_errormsg($conn));
}
return 1;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Recs($conn,$file,$flib) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data $_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("
// the assoc array contains the field names so we can use those as the headers
foreach($rec as $key => $value) {
echo(" }
echo(" // now just write out the data again and get the next record to the end
do {
echo(" foreach($rec as $key => $value) {
echo(" }
echo(" } while($rec = i5_fetch_assoc($result));
echo(" i5_free_query($result);
return;
}

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) = '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table><form name=updent method=post action=scripts/update_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$rec['RRN'] ." >");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=" .$key ." id=" .$key ." value='" .$value ."'");
if($key == "RRN")
echo(" readonly=readonly");
echo(" /></td></tr>");
$i++;
}
echo("<tr><td colspan=2><input type=submit value=Update /></td</tr></form></table>");
// button to get the next record
echo("<table><tr><td>");
if($id > 0) {
$id--;
echo("<input type=button value=PREV OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td><td>");
$id++;
}
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' /></td></tr></table>");
echo("<input type=button value='Log Out' OnClick=location='scripts/logout.php' ?>");
// break the reference to the last element as per the manual
unset($value);
i5_free_query($result);
return;
}

/*
* function load_config()
* @parms
* *NONE
*/

function load_config() {
$config_file = "scripts/config.conf";
$comment = "#";
// open the config file
$fp = fopen($config_file, "r");
if(!$fp) {
echo("Failed to open file");
return 0;
}
// loop through the file lines and pull out variables
while(!feof($fp)) {
$line = trim(fgets($fp));
if ($line && $line[0] != $comment) {
$pieces = explode("=", $line);
$option = trim($pieces[0]);
$value = trim($pieces[1]);
$config_values[$option] = $value;
}
}
fclose($fp);
$_SESSION['server'] = $config_values['server'];
$_SESSION['install_lib'] = $config_values['install_lib'];
$_SESSION['timeout'] = $config_values['timeout'];
$_SESSION['refresh'] = $config_values['refresh'];
$_SESSION['logo'] = $config_values['company_logo'];
$_SESSION['dft_logo'] = $config_values['default_logo'];
$_SESSION['co_name'] = $config_values['company_name'];
if(isset($config_value['valid_usr'])) {
if($config_values['valid_usr'] != '') {
if(($config_values['usr'] != '') && ($config_values['pwd'] != '')) {
$_SESSION['valid_usr'] = $config_values['valid_usr'];
$_SESSION['usr'] = $config_values['usr'];
$_SESSION['pwd'] = $config_values['pwd'];
$_SESSION['auto_signon'] = 1;
}
}
}
return 1;
}

/*
* function to retrieve a list of libraries
*/

function Dsp_Lib_Obj(&$conn) {
$HdlList = i5_objects_list('QSYS', '*ALL', '*LIB',$conn);
if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
echo("

    while($list = i5_objects_list_read($HdlList)){
    echo(" if($list['DESCRIP'])
    echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
    else
    echo("-");
    echo(" echo(" }
    echo(" $ret = i5_objects_list_close($HdlList);
    if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
    return;
    }

    /*
    * function to retrieve a list of Files in a Library
    */

    function Dsp_Pf_Obj(&$conn,$lib) {
    $HdlList = i5_objects_list($lib, '*ALL', '*FILE',$conn);
    if (is_bool($HdlList)) trigger_error('i5_objects_list error : '.i5_errormsg(), E_USER_ERROR);
    echo("

      while($list = i5_objects_list_read($HdlList)){
      // check if a PF-DTA
      if($list['EXT_ATTR'] == "PF") {
      echo(" if($list['DESCRIP'])
      echo(str_pad($list['DESCRIP'],50," ",STR_PAD_RIGHT));
      else
      echo("-");
      echo(" echo(" }
      }
      $ret = i5_objects_list_close($HdlList);
      if (!$ret) trigger_error('i5_object_list_close error : '.i5_errormsg(), E_USER_ERROR);
      return;
      }
      ?>

      The next page called provides a list of the *FILE objects in the library which have a EXT_ATTR of "PF", this allow you to select a file and display all of the records from that file. Again all we do is use the i5_toolkit function i5_list_objects() to provide the list of objects in the library and then filter based on the extended attribute.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>List Files in Library</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pf_Obj($conn,$_REQUEST['lib']); ?>
      </body>
      </html>

      From this list you will display all of the records in the file, we only used a small file to test so the amount of data coming back was not important, if this was production and you had files with millions of records in them it could blow up the page as it would be too big! If you want to add something consider paging the file in sets of records or passing in a range of records at the start so only a limited number of records are retrieved and displayed. We have written plenty of posts and forums entries about how to achieve this, Google should bring up enough information to allow you to do it. This is the code for the page to display all of the records, it simply displays them and has a button which allows you to edit specific records. We just left the display for this as it was so once you start to edit you will have the ability to move around the records as previously.


      <?php
      /*
      Copyright © 2010, Shield Advanced Solutions Ltd
      All rights reserved.

      http://www.shieldadvanced.ca/

      Redistribution and use in source and binary forms, with or without
      modification, are permitted provided that the following conditions
      are met:

      - Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

      - Neither the name of the Shield Advanced Solutions, nor the names of its
      contributors may be used to endorse or promote products
      derived from this software without specific prior written
      permission.

      THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
      "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
      LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
      FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
      COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
      INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES INCLUDING,
      BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
      CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
      LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
      ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
      POSSIBILITY OF SUCH DAMAGE.

      */
      // start the session to allow session variables to be stored and addressed
      session_start();
      require_once("scripts/functions.php");
      // if not signed in just force to sign in page
      if(!isset($_SESSION['valid_usr'])) {
      header("Location: /index.php");
      exit(0);
      }
      // get the previous return URL
      $ret_url = $_SESSION['ret_url'];
      // store this page as return URL
      $_SESSION['ret_url'] = $_SERVER['PHP_SELF'];
      ?>

      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <title>Edit File Content</title>
      <!-- add the main CSS -->
      <link rel="stylesheet" href="css/tst.css" type="text/css">
      </head>

      <body>
      <?php
      $conn = 0;
      if(!connect($conn)) {
      if(isset($_SESSION['Err_Msg'])) {
      echo($_SESSION['Err_Msg']); $_SESSION['Err_Msg'] = "";
      }
      }
      Dsp_Pfm_2($conn,$_REQUEST['file'],$_REQUEST['flib'],$_REQUEST['id']); ?>
      </body>
      </html>

      An that is it, took us about 1 hour to complete the set of functions and pages (lots of cut and paste). I thought I would add a few pictures to show you just what it produces. Remember this uses the Aura tollkit functions so they must be available for it to work. Again we did not add any fancy page sizing CSS etc so the images are very basic.

      This is the inital page which requires the user to pass in valid credentials to connect to the IBMi.

      Uses the sign in information to access the IBMi

      Initial signon screen

      Once you have signed in you should see a list of the libraries available on the system. You could provide filters or use the IBMi security to only allow the user to display the available libraries.

      List of Libraries

      The list of libraries from the system

      When you select the library you will be provided with a list of Physical Files.

      List of files in library

      A list of files in the library for selection

      Select the option to display the records and the following page is displayed.

      Records contained in the file

      A list of the records contained in a file select to edit

      Select a record to start editing from and the following page is displayed we moved the record up one to show the movement buttons.

      Edit a file

      Editing records in a file

      That is it, plenty of improvements I can think of just as I write this post but you get the idea. IBMi is the perfect hub for delivering data via web pages, we prefer to keep the web page generation off the IBMi but you have the option to use the iAMP server or Zend Server etc. to provide the same functionality. Stop thinking about how to move off the platform and start thinking about how you can harvest all of the good business logic and data you have built into the system.

      Hope you find this useful, for us its just a bit of fun and provides us with the ability to show what we can do with PHP. You should take up the challenge and start your investment in PHP now. Call us if you need more information or help.

      Happy PHP'ing..

      Chris...

Nov 22

Walking through a DB2 PF data and allow update.

I was thinking about some of the tools I would like to see on the IBMi and written in PHP that allow you to carry out the same functionality as the PHPMyAdmin tool on MySQL. Not that I wanted to write the entire product but at times I would like the ability to just review and/or update the data in a particular file. Sometime ago we started to publish sample programs in PHP that show the very effective capabilities of running PHP against IBMi data an objects, one such project was being able to display file data and the file column names in a table see this post. So we decided to take that one step further and add the ability to output the data to a web interface and then allow you to update that data or move to the next record.

The final solution is very simple in terms of code required to generate the interface (we did not make it pretty just functional) but it is a good stepping stone to take it to the next stage. This first iteration simply retrieves the file data and displays the column heading along-side the actual data in the field. We have provided a button to move to the next entry but not added the move back or update buttons although these will be very simple to add. The process works by having a sign in screen (index.php) fronting the process, if a request is made that has no valid credentials a sign on screen is displayed. If Valid credentials are supplied it will automatically pass in a file name and start point to the called page which in-turn calls the function shown below.

/*
* function Dsp_Pfm()
* display the contents of a file
* @parms
* File
* File Library
* Starting record
* returns the number of errors.
*/

function Dsp_Pfm_2($conn,$file,$flib,$id) {

// query to get the data frm the file
$query = "SELECT rrn(a) as RRN, a.* FROM " .rtrim($flib) ."/" .rtrim($file) ." a WHERE RRN(a) > '" .$id ."' FETCH FIRST ROW ONLY";
$result = i5_query($query,$conn);
if(!$result){
echo("Failed to get the data<br>" .$query);
$_SESSION['ErrMsg'] = "Error code: " .i5_errno($result) ." Error message: " .i5_errormsg($result);
}
$rec = i5_fetch_assoc($result);
echo("<table>");
// the assoc array contains the field names so we can use those as the headers
$i = 0;
foreach($rec as $key => $value) {
echo("<tr><td>" .$key ."</td><td><input type=text name=data" .$i ." id=data" .$i ." value='" .$value ."' /></td></tr>");
$i++;
}
echo("</table>");
// button to get the next record
$id++;
echo("<input type=button value=NEXT OnClick=location='dsp_pfm.php?file=" .$file ."&flib=" .$flib ."&id=" .$id ."' />");
i5_free_query($result);
return;
}

That code will produce something similar to the following.

Sample output of edit panel

Output from the function

As I have said we have not added the ability to edit and update the data in the file, but that would be a fairly simple option to add.

I would encourage those developers out there who have not experimented with PHP yet to start! The power provided by the ability to call SQL and provide interfaces which are not limited by 10 character column names and 80 column displays has to be something you want to use.. Our HA4i High Availability product is using some of the technology we have demonstrated in the various Blog posts to it best advantage.

Happy PHP’ing..

Chris…

Nov 15

iAMP server looping due to ending TCP/IP services

One of our clients just called because the iAMP server which runs on their IBMi had taken over the CPU! The problem was due to their ending of the TCP/IP subsystems, this caused the iAMP jobs to go into a loop with the message “[error] (42)Protocol driver not attached: apr_socket_accept: (client socket)” being logged into the error_log constantly. We looked around on the net for any similar issues and found that a similar problem had been logged by IBM when the SSHD server had been configured to run in its own subsystem and the TCP/IP services had been ended and a save started. The IBM solution was to restart the subsystem once the TCP/IP services had been restarted. There are also a number of Apache related problems which still seem to be outstanding but they are more about the graceful ending of the server.

So if you are running ANY TCP/IP based programs that could fall into this category make sure you end them before ending the TCP/IP services. This might be overkill because we also notice that some other TCP/IP programs which are running sockets did not display the same effects but its probably a better option to end all of them than trying to find out exactly which programs are effected.

Chris…

Jun 15

Adding CGI capabilities to iAMP

We have been working with a client who wanted to be able to install a new website on their IBMi. The new website was developed on another OS using cake PHP by a development company that had no idea about how to set up a web server on an IBMi so we were asked to help them implement the new site.

The companies old site did in fact run on the IBMi but was not written in PHP and was mainly composed of static pages with some parts that needed CGI access to run. The initial request stated that these CGI generated pages were no longer needed so we did not have to worry about setting them up. They had looked at Zend Server in the past and rejected using it due to its complexity so we had the opportunity to start fresh and just install the iAMP server.

At first we set up test sites just to make sure the server ran and we had no problems setting up VirtualHosting. We did this on a new IP address so it did not impact the current site which was being run from the same saver. We could have used the same IP address and a different port but that is just plain ugly! We also needed a MySQL instance because the content management used the MySQL database to store the page data, an admin feature allowed the content to be changed via an editor which updated in the MySQL database when it was finished.

Everything went OK and we managed to import the MySQL database which contained all of the web page content so far with a few issues which we soon worked around. On starting the web server everything looked OK but we found the links in the navigation objects were all screwed up, a quick fix from the developers soon fixed that and the site appeared to be running just fine.

At this point the client said OK lets go live! They wanted the new site up and running in the wild straight away. Although this was quite a leap of faith all we had to do at this point was change the new configurations to the run on the original servers IP address, stop the existing server and bring the new iAMP server online. This went through without any hitches and the new site was up and running in the wild.

We thought everything was OK but when we tried to use the Admin functions (which are important to the client because it allowed them to control the content of their webpages through a user friendly interface) we hit our first problem. In the code they had a function which uses sockets to talk back to the HTTP server, this request hung every time it was called. The messages in the logs did not help much in finding out exactly what the problem was so it was Google to the rescue. We found a post to a forum about a different problem but the description of the resolution gave us the idea of where to look. The problem was the socket tried to connect to the external interface of the HTTP server, the firewall (like all good firewalls) stopped the connection because it determined the request was coming from the LAN to the WAN and back to the same LAN. We did not want to change the firewall rules to allow this connectivity so we had to come up with another option. The solution was pretty simple, we added a host table entry to the IBMi that pointed all requests for the website address to the internal IP address, this meant any request from the HTTP server to the company website would go to the internal IP address of the IBMi not the external one defined in the DNS. After this the admin function worked and we move on.

The next challenge came when we found out that despite being told otherwise, one of the functions in the website required a call to a CGI script. We thought we could just configure the iAMP server to run the request by configuring the CGI module etc. That did not work. The output said it was a permissions error which lead us down a long and fruitless path of looking at the permission on each of the objects etc. but as we discovered the problem was because PASE programs cannot call a Native IBMi program.

This meant we needed a copy of the IBM HTTP server running which we could use to serve the CGI generated content. Setting up the IBM HTTP server was pretty simple as we had just removed the old site from the configs and another internal site was still running on it. We just added a new configuration and ran it against a separate port. The version of the iAMP Server we had installed did not have proxy support so we had to come up with an alternative while Aura could create and test the additional modules required for proxy support. The solution we eventually came up with had one draw back in that we had to open the port the new website was using in the firewall, this allowed the request to be routed from the iAMP server back to the correct port on the IBM HTTP server but we did not like having to open more ports in the firewall. Once we had everything configured the CGI processes all worked and the client was again happy with their solution.

When we received the new code from Aura for iAMP with Proxy support we upgraded the server and set up the link to the IBMi server using the ProxyPass and ProxyReverse statements in just the same way you had to configure ZendCore. We also added the new modules into the base config and after some false starts we had the site back up and running with the proxy support and we could remove the additional open port from the firewall.

If you need to have an AMP stack on the IBMi you now have the option of using the iAMP Server or Zend Server, iAMP is a free download from the Aura Equipments website. Support for iAMP is provided free of charge via the Aura forums but you can also request full support for the product from Aura for a very low fee. As an incentive Aura are also offering to inlcude the support costs for iAMP with Easycom so you will have one support structure or all your AMP stack plus the Easycom i5_toolkit for one very low fee. The same setup for Zend requires you to enter into a support contract with Zend for their Server, a support contract with another company for ZendDBi (MySQL) plus a non supported open source solution for the i5_toolkit server. I know which one I think is the better option.

If you have any questions about running iAMP or would like to know more about Easycom for PHP i5_Toolkit gives us a call, you may surprised just how cost effective our solution for running AMP on the IBMi is. Even if you are running an AMP stack on Linux or Windows, Easycom will allow the same i5_toolkit functions to run on those stacks in exactly the same way they do on the IBMi, don’t just integrate, innovate…

Chris…

May 15

Zend moves support for ZendDBi to alternative supplier.


A recent article in IT Jungle just announced that Zend is going to be moving support and future development for Zend DBi off to another supplier. I did a quick review of the website for Percona and the pricing for their support for MySQL and various add-ons they have created and found the following price page. This means you should now expect to pay for support for Zend DBi separate to any support contract you decide to take with Zend.

This is not a bad move for Aura Equipments and their iAMP server solution, with iAMP you get the MySQL engine built in and support via the forums is still going to be free. You can purchase support for iAMP which will cover all of the installed products (Apache, MySQL and PHP) and have that support in one place. Add to that the ability to include the Easycom product as part of that support contract and it be comes very much a better option than the Zend stack which now requires the support with Percona and Zend plus the no fee based support for the open source XMLSERVICE to provide the same solution.

I am not sure why Zend took the move or how the IBMi community will respond. I do know this is not going to be a bad thing for Aura, who can capitalize on the fact that they provide a single point of contact for the total solution and at a price which is far below that of the total package required to run the various elements for Zend, Percona and the Open Source XMLSERVICE.

You can download and install the iAMP server from the Aura Equipments website.

Chris…