Search the OSCAR Documentation
< All Topics
Print

SRFax Gateway

Preface

This is a basic installation of an encrypted secure delivery to the SR fax server for use with Oscar.

Document Version History

  • v1.0 – initial public release to oscarmanual.org with python – May 10, 2016
  • v1.1 – amended for Ubuntu 18.04 – Sept 28, 2019
  • v2.0 – greatly simplified curl based– Feb 27, 2020
  • v2.1 – script generalised – Mar 1, 2020
  • v2.2 – corrected crontab line, added note about files in /tmp – Oct 16,2020
  • v2.3 – script to match crontab; remove signature file – Jan 29,2022
  • v2.4 – notes added for Tomcat 9 – Feb 4, 2022
  • v2.5 – permissions fixed and direct cron script output to null – April 17, 2022
  • v2.6 – more Tomcat9 – August 29, 2024
  • v2.7 – updated to reflect class use – Feb 26, 2025

Copyright ©2016-2025 by Peter Hutten-Czapski MD under the Creative Commons Attribution-Share Alike 3.0 Unported License.

Preamble

OSCAR drops files to a directory identified in oscar.properties as fax_file_location= .  Failing that it uses the system property for java io tmp directory which usually resolves to the tmp directory to enable faxing.  The three types are a text file with the fax number(s), the corresponding pdf and an optional signature.jpg.  The .txt filename is one of the following patterns

  1. Prescription:
    prescription_<prescription_provider_no><serial>.txt
  2. eForm:
    EForm-<eform_data.fdid>.<serial>.txt
  3. Consult request form:
    CRF-<consultationRequests.requestId>.0.<serial>.txt

An Internet Fax Gateway is a commercial subscription service that allows for conversion of email to fax and vice versa, fax to email.  The main advantage of this service over using a method to fax locally is that the phone line and the modem are provided. The costs are the need for internet connectivity and the gateway subscription cost. 

Apply for an account at SRfax and then with those particulars proceed

A Bash Script for sending

Use your favorite text editor and load the following into /usr/share/oscar-emr/srfax2.sh

#!/bin/bash
#
# SRFax Gateway cron
# (c) 2022 Peter Hutten-Czapski released on GPL 2+
# version date Feb 4, 2022
#
# Picks up the files dropped by OSCAR
# and POSTs it to the SRFax API
# and it clears the files dropped by OSCAR
# Adjust the constants at the top of the page to match actual
#

ID=***** # this is your SRfax account number
PWD=****** # Escape any special characters with non quoted \
CID=******** # this is your SRfax fax number, just numbers
EMAIL=******** # use the same email registered with SRfax for access to their web interface
FROM=******* # this one is whatever you like
FAXLOG=fax2.log

SCRIPT_FILE=$(basename "$0")
LOCKDIR=/tmp/${SCRIPT_FILE}.lock
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
SCRIPT_PATH="${SCRIPT_DIR}/${SCRIPT_FILE}"
FAXLOG=${SCRIPT_DIR}/fax2.log
PROPFILE=$CATALINA_HOME/oscar.properties
echo "Calling  ${SCRIPT_FILE} on ${SCRIPT_PATH} with logging on ${FAXLOG}"

# --- select the running Tomcat or the highest installed version
TMP=$(grep fax_file_location $PROPFILE | sed -e "s/.*[=:][[:space:]]*//" -e "s/#.*//") 
#TOMCAT=$(ps aux | grep org.apache.catalina.startup.Bootstrap | grep -v grep | awk '{ print $1 }')
#if [ -z "$TOMCAT" ]; then
    #Tomcat is not running, find the highest installed version
    if [ -f /usr/share/tomcat9/bin/version.sh ] ; then
            TOMCAT=tomcat9
            if [ -z "$TMP" ]; then 
               TMP=$(find /tmp -type d -wholename "*tomcat*/tmp")  
            fi
        else
        if [ -f /usr/share/tomcat8/bin/version.sh ] ; then
            TOMCAT=tomcat8
            else
            if [ -f /usr/share/tomcat7/bin/version.sh ] ; then
                TOMCAT=tomcat7
            fi
        fi
    fi
#fi

if [ -z "$TMP" ]; then 
  TMP=/tmp/${TOMCAT}-${TOMCAT}-tmp 
fi
echo "Searching ${TMP} for faxes...."
if test -n "$(find ${TMP} -maxdepth 1 -name '*.txt' -print -quit)"; then
        echo "Faxes found to be sent"
        for f in `ls $TMP/*.txt`; do
                faxto=`sed s"/ *//g" $f|tr -d "\n"`
                # allow for a second or so for the pdf to be generated
                sleep 4
                bfile=`echo $f | sed s"/txt/pdf/"`
                openssl base64 -e -A -in $bfile -out temp.b64
                echo -n `date` "fax to 1$faxto " >> $FAXLOG
                json=$(curl \
                -F "action=Queue_Fax" \
                -F "access_id=$ID" \
                -F "access_pwd=$PWD" \
                -F "sCallerID=$CID" \
                -F "sSenderEmail=$EMAIL" \
                -F "sFaxFromHeader=$FROM" \
                -F "sFaxType=SINGLE" \
                -F "sToFaxNumber=1$faxto" \
                -F "sCPSubject=Oscar Fax" \
                -F "sRetries=3" \
                -F "sFileName_x=$bfile" \
                -F "sFileContent_x=<temp.b64" \
https://www.srfax.com/SRF_SecWebSvc.php)
                if [ -n "$json" ]; then
                    success=$(echo "${json}" | grep -Po "Success")
                    if [ -z "$success" ]; then
                        echo "#### ERROR: ${json} for ${f} " >> $FAXLOG
                        echo "exiting but renaming source file"
                        cp $f $f.fail
                    else
                        echo "${json} for" `basename ${f}`  >> $FAXLOG
                        rm $bfile;
                    fi
                else
                        echo "### ERROR: $json is empty for ${f} " >> $FAXLOG
                        cp $f $f.fail
                fi
                rm $f;
                rm -f `echo $f | sed s"/prescription_/signature_/;s/.txt/.jpg/"`
                rm temp.b64
        done
else
        echo "No faxes found"
fi

Of course you will need to alter the lines at the top of the script with your id, password, caller id, and account email.

Note: 1) You may need to add a ‘;’ before the ‘then’, depending on your version of bash. 2) The script deletes the .txt and .pdf files in the /tmp folder, but the signature.jpg will remain. These get deleted when the server is restarted.

Now make it executable

chmod 710 srfax2.sh

Test this script manually with sudo ./srfax.sh and alter as needed to get it working.

Now a cron job

And set it up as a cron job (you will need to run it as root or the tomcat user to open the files that are dropped by Oscar into the tmp directory.)  The following example has the root user sending the files

sudo crontab -e

And add an entry like the following that executes every 2 minutes

*/2 * * * * /usr/share/oscar-emr/srfax2.sh> /dev/null

Logs will be in the root home directory with one line per fax that should look something like

Sat Feb 29 07:05:06 EST 2020 fax to <fax no> {"Status":"Success","Result":<SRfaxserial>} for prescription_<user><OSCAR serial>.txt

Using the API to Download your Faxes

This is a preferred method that accesses the SRfax api directly.  Lets set up some php code into a file and call it srfax2o.php saving it to /usr/share/oscar-emr/php-oscar-srfax/srfax2o.php.  Note the dependency of srfax.php which is a php class from SRFax that is reproduced here

<?php /* Class srfax - Copyright (C) 2015 KISS IT Consulting, LLC. All rights reserved. * * Simple class that provides an interface to the SRFax web API: * https://www.srfax.com/online-fax-features/internet-fax-api/. * Usage requires a valid account. */ class srfax { private $api_user; private $api_pass; private $api_url; private $sender_fax; private $sender_email; /* * Class constructor * $api_user: SRFax API user (customer number). Required. * $api_user: SRFax API password. Required. * $options: Array of optional parameters possible for the given call * return: Array to be used as the base parameters to the call() method */ function __construct($api_user, $api_pass, $api_url = "", $sender_fax = null, $sender_email = null){ $this->api_user = $api_user;
        $this->api_pass = $api_pass;
        if(!empty($api_url)) {
            $this->api_url = $api_url;
        } else {
            $this->api_url = 'https://www.srfax.com/SRF_SecWebSvc.php';
        }
        $this->sender_fax = $sender_fax;
        $this->sender_email = $sender_email;
    }

    /*
	 * Method to set the options array where applicable
     * $function: Name of the SRFax API function to be called.  Required.
     * $options: Array of optional parameters possible for the given call
     * return: Array to be used as the base parameters to the call() method
     */
    private function _set_options($function, $options = array()) {
        $params = array();
        if(is_array($options) && !empty($options)) {
            $params = $options;
        }
        $params['action'] = $function;
        return $params;
    }

    /*
	 * Method to make an API call to SRFax using cURL.  
     * $params: array of parameters, including the 'action' which is the SRFax API function to call
     * $return_response: True to get the raw response object, false to just get the success response or exception on failure
     * return: JSON decoded object on success or throws an exception on cURL error.
     */
    private function _call($params, $return_response = false) {
        $return = null;
        if(is_array($params) && !empty($params)) {
            // Setup our payload, note we force JSON regardless of what you may pass.
            $params['access_id'] = $this->api_user;
            $params['access_pwd'] = $this->api_pass;
            $params['sResponseFormat'] = 'JSON';
            $payload = http_build_query($params);

            // Setup our cURL call and run it
            $options = array (
                CURLOPT_POST => 1,
                CURLOPT_HEADER => 0,
                CURLOPT_URL => $this->api_url,
                CURLOPT_FRESH_CONNECT => 1,
                CURLOPT_RETURNTRANSFER => 1,
                // Not the greatest choice but it gets the job done.
                CURLOPT_SSL_VERIFYPEER => false,
                CURLOPT_SSL_VERIFYHOST => 2,
                CURLOPT_FORBID_REUSE => 1,
                CURLOPT_TIMEOUT => 60,
                CURLOPT_SSLVERSION => 6,
                CURLOPT_POSTFIELDS => $payload);

            $curl = curl_init();
            curl_setopt_array($curl, $options);
            $response = curl_exec($curl);

            // setup our return based on our status and close the handle
            if (curl_errno($curl) > 0) {
                throw new Exception ('cURL error: '. curl_error($curl));
            } else {
                if(!empty($response)) {
                    $response = json_decode($response);
                    if($response !== null) {
                        if($return_response) {
                            // Return our response as is
                            $return = $response;
                        } else {
                            // Check our response
                            if($response->Status == 'Success') {
                                $return = $response->Result;
                            } else {
                                throw new Exception("API Call returned error: " . $response->Result);
                            }
                        }
                    } else {
                        throw new Exception("Failed to parse response as JSON.");
                    }
                } else {
                    throw new Exception("Empty response returned from SRFax API.");
                }
            }
            curl_close($curl);
        }
        return $return;
    }

    /*
     * Method to queue a fax via the SRFax API function Queue_Fax
     * $to: The recipient fax number.  11 digit number or up to 50 x 11 digit numbers pipe separated.  Required.
     * $options: Array of optional parameters possible for the SRFax API function Queue_Fax.  Note the file name(s)/data to send must
     *      be passed in here as per the API and that the file content must be base64_encoded on the calling side.
     * $sender_fax: Sender fax number, 10 digits.  Optional if set via constructor.
     * $sender_email: Sender email address.  Optional if set via constructor.
     * $fax_type: Fax type either 'SINGLE' or 'BROADCAST.  Optional, defaults to 'SINGLE'
     * Returns queued fax ID (FaxDetailsID) on success, throws an exception on failure.
     */
    public function Queue_Fax($to, $options = array(), $sender_fax = null, $sender_email = null, $fax_type = 'SINGLE') {
        // Get our base params
        $params = $this->_set_options('Queue_Fax', $options);

        // Validate the to #.  First remove all the special chars to turn it into a plain string of numbers.
        $to = (string)$to;
        $to = preg_replace("/\D/", "", $to);
        if(strlen($to) != 11) {
            // Try to check for a valid number that is missing a 1 at the beginning and beat it with a hammer
            if(strlen($to) == 10) {
                $to = "1{$to}";
            } else {
                throw new Exception("Invalid recipient fax number.");
            }
        }
        $params['sToFaxNumber'] = $to;

        // Validate sender fax #
        if(empty($sender_fax) || !is_numeric($sender_fax) || strlen($sender_fax) != 10) {
            if(empty($this->sender_fax) || !is_numeric($this->sender_fax) || strlen($this->sender_fax) != 10) {
                throw new Exception("Invalid sender fax number.  Must be 10 digits");
            }
            $sender_fax = $this->sender_fax;
        }
        $params['sCallerID'] = $sender_fax;

        // Validate sender email
        if(empty($sender_email)) {
            if(empty($this->sender_email)) {
                throw new Exception("Invalid sender email address.");
            }
            $sender_email = $this->sender_email;
        }
        $params['sSenderEmail'] = $sender_email;

        // Validate fax type
        $fax_type = strtoupper($fax_type);
        if($fax_type != 'SINGLE' && $fax_type != 'BROADCAST') {
            throw new Exception("Invalid fax type.  Must be 'SINGLE' or 'BROADCAST'.");
        }
        $params['sFaxType'] = $fax_type;
        
        // Make our API Call
        return $this->_call($params);
    }

    /*
     * Method to check the status of a previously queue'd fax(es)
     * $fax_details_id: The FaxDetailsID to check the status of.  
     *      Can also be a pipe separated list of IDs that will in turn be used to call Get_MultiFaxStatus Required.
     * Returns an array of one or more fax properties objects on success, throws an exception on failure.
     */
    public function Get_FaxStatus($fax_details_id) {
        // Figure out which API call we need to use
        if(strpos($fax_details_id, '|') === false) {
            $method = 'Get_FaxStatus';
        } else {
            $method = 'Get_MultiFaxStatus';
        }

        // Get our base params
        $params = $this->_set_options($method);
        $params['sFaxDetailsID'] = $fax_details_id;

        // Make our API Call
        return $this->_call($params);
    }
    
    /*
     * Method to get the fax usage of the account.
     * $options: Array of optional parameters possible for the SRFax API function Get_Fax_Usage
     * Returns array of fax usage properties on success, throws an exception on failure.
     */
    public function Get_Fax_Usage($options = array()) {
        // Get our base params
        $params = $this->_set_options('Get_Fax_Usage', $options);
        
        // Make our API Call
        return $this->_call($params);
    }
    
    /*
     * Method to get the fax inbox.
     * $options: Array of optional parameters possible for the SRFax API function Get_Fax_Inbox
     * Returns array of faxes on success, throws an exception on failure.
     */
    public function Get_Fax_Inbox($options = array()) {
        // Get our base params
        $params = $this->_set_options('Get_Fax_Inbox', $options);
        
        // Make our API Call
        return $this->_call($params);
    }

    /*
     * Method to get the fax outbox.
     * $options: Array of optional parameters possible for the SRFax API function Get_Fax_Outbox
     * Returns array of faxes on success, throws an exception on failure.
     */
    public function Get_Fax_Outbox($options = array()) {
        // Get our base params
        $params = $this->_set_options('Get_Fax_Outbox', $options);
        
        // Make our API Call
        return $this->_call($params);
    }

    /*
     * Method to retrieve a fax, either incoming or outgoing based on parameters
     * $direction: Type of fax, "IN" for inbound, "OUT" for outbound.  Required.
     * $fax_details_id: The fax details id (required if not passing $fax_filename)
     * $fax_filename: The fax file name (required if not passing $fax_details_id)
     * $options: Array of optional parameters possible for the SRFax API function Retrieve_Fax
     * Returns file data from the retrieved fax on success, throws an exception on failure.
     */
    public function Retrieve_Fax($direction, $fax_details_id = null, $fax_filename = null, $options = array()) {
        // Get our base params
        $params = $this->_set_options('Retrieve_Fax', $options);

        // Validate the direction
        $direction = strtoupper($direction);
        if($direction != 'IN' && $direction != 'OUT') {
            throw new Exception("Invalid direction, must be IN or OUT.");
        }
        $params['sDirection'] = $direction;

        // Validate that we have fax information to be retrieved
        if(!empty($fax_details_id)) {
            $params['sFaxDetailsID'] = $fax_details_id;
        } elseif(!empty($fax_filename)) {
            $params['sFaxFileName'] = $fax_filename;
        } else {
            throw new Exception("You must pass either fax_details_id or fax_filename.");
        }

        // Make our API Call and return the results decoded if valid
        return base64_decode($this->_call($params));
    }

    /*
     * Method to update the viewed status of a fax, either incoming or outgoing based on parameters
     * $direction: Type of fax, "IN" for inbound, "OUT" for outbound.  Required.
     * $viewed: Mark the fax as read ('Y') or unread ('N').  Required.
     * $fax_details_id: The fax details id (required if not passing $fax_filename)
     * $fax_filename: The fax file name (required if not passing $fax_details_id)
     * $options: Array of optional parameters possible for the SRFax API function Retrieve_Fax
     * Returns empty string on success, throws an exception on failure.
     */
    public function Update_Viewed_Status($direction, $viewed, $fax_details_id = null, $fax_filename = null, $options = array()) {
        // Get our base params
        $params = $this->_set_options('Update_Viewed_Status', $options);

        // Validate the direction
        $direction = strtoupper($direction);
        if($direction != 'IN' && $direction != 'OUT') {
            throw new Exception("Invalid direction, must be IN or OUT.");
        }
        $params['sDirection'] = $direction;

        // Validate the viewed state
        $viewed = strtoupper($viewed);
        if($viewed != 'Y' && $viewed != 'N') {
            throw new Exception("Invalid viewed option, must be Y or N.");
        }
        $params['sMarkasViewed'] = $viewed;

        // Validate that we have fax information to be retrieved
        if(!empty($fax_details_id)) {
            $params['sFaxDetailsID'] = $fax_details_id;
        } elseif(!empty($fax_filename)) {
            $params['sFaxFileName'] = $fax_filename;
        } else {
            throw new Exception("You must pass either fax_details_id or fax_filename.");
        }

        // Make our API Call and return the results decoded if valid
        return $this->_call($params);
    }

    /*
     * Method to delete a fax, either incoming or outgoing based on parameters
     * $direction: Type of fax, "IN" for inbound, "OUT" for outbound.  Required.
     * $fax_details_id: The fax details id (required if not passing $fax_filename)
     * $fax_filename: The fax file name (required if not passing $fax_details_id)
     * $options: Array of optional parameters possible for the SRFax API function Retrieve_Fax
     * Returns an empty string on success, throws an exception on failure.
     */
    public function Delete_Fax($direction, $fax_details_id = null, $fax_filename = null, $options = array()) {
        // Get our base params
        $params = $this->_set_options('Delete_Fax', $options);

        // Validate the direction
        $direction = strtoupper($direction);
        if($direction != 'IN' && $direction != 'OUT') {
            throw new Exception("Invalid direction, must be IN or OUT.");
        }
        $params['sDirection'] = $direction;

        // Validate that we have fax information to be retrieved
        if(!empty($fax_details_id)) {
            $params['sFaxDetailsID'] = $fax_details_id;
        } elseif(!empty($fax_filename)) {
            $params['sFaxFileName'] = $fax_filename;
        } else {
            throw new Exception("You must pass either fax_details_id or fax_filename.");
        }

        // Make our API Call and return the results decoded if valid
        return $this->_call($params);
    }

    /*
     * Method to delete a fax from the queue.  Note that depending on where it is in processing this may or may not work fully or at all
     * $fax_details_id: The FaxDetailsID to stop 
     * Returns the full json response as per the documentation on success to provide meaningful status messages, throws an exception on failure.
         Status = Success:
            Result:
             "Fax cancelled but partially sent"  fax was successfully cancelled but
            whatever was in the fax buffer will have been sent.
             "Fax Cancelled"  the fax was successfully cancelled without any pages being
            sent.
         Status = Failed:
            Result:
             "Fax transmission completed"  the fax has been sent and the transaction is
            complete
             "Unable to Cancel Fax"  Fax in the process of conversion and cannot be
            cancelled at this time  you can try again in 10 seconds.
     */
    public function Stop_Fax($fax_details_id) {
        // Get our base params
        $params = $this->_set_options('Stop_Fax');
        $params['sFaxDetailsID'] = $fax_details_id;

        // Make our API Call and get the full response back
        return $this->_call($params, true);
    }
}

Ensure to set the Sender settings at the top of the script with your credentials for your SRfax account.

<?php
/* START: Example configuration */
$API_URL = 'https://www.srfax.com/SRF_SecWebSvc.php';

// Set these options with your credentials
// Sender settings
$API_USER = ''; // put your number ID of your main SRFAX account, ie, 123456
$API_PASS = ''; // your master account password
$SENDER_FAX = ''; // your fax number 5555551212 etc 
$SENDER_EMAIL = ''; // email associated with srfax account

/* END: Example configuration */

$script_dir = "/usr/share/oscar-emr/php-oscar-srfax";
require_once "$script_dir/srfax.php";
//require_once "srfax.php";

$short_options = 'h';
$long_options = array(  'help' => 'Show this help screen',
                        'func:' => 'Name of func to run (required)',
						'id::' => 'FaxDetailsID to use for func (if applicable)',
                        'file::' => 'File to use for input to send a fax (if applicable)',
                        'viewed::' => 'Viewed state to set for fax - Y or N (if applicable)',
                        'dir::' => 'Direction to use for func - IN or OUT (if applicable)');
						
// Get our command line options & validate required items
$options = getopt($short_options, array_keys($long_options));
if(isset($options['h']) || isset($options['help']) || !isset($options['func'])) {
	echo("\n");
	echo("This tool is used to run examples of the SRFax API lib.  Options are as follows\n ");
	foreach($long_options as $name => $message) {
		$name = str_replace(':', '', $name);
		echo( "\t--$name=$message\n" );
	}
	echo("\n");
	exit();
}
$func = $options['func'];
$id = isset($options['id']) ? $options['id'] : null;
$file = isset($options['file']) ? $options['file'] : null;
$viewed = isset($options['viewed']) ? $options['viewed'] : null;
$dir = isset($options['dir']) ? $options['dir'] : null;
$out = '';
$oscar_fax_dir = '/usr/share/oscar-emr/OscarDocument/oscar/incomingdocs/1/Fax';


// Initialize srfax
$srfax = new srfax($API_USER, $API_PASS, $API_URL, $SENDER_FAX, $SENDER_EMAIL);

// Get the fax inbox
if($func == 'oscar-inbox') {
		$test = $srfax->Get_Fax_Inbox();
		foreach($test as $message) {
			foreach($message as $key => $value) {
				if ($key == "FileName") {
					$targetFile = $value;
				}
				if (($key == "ViewedStatus") && ($value == "N")) {
					$splitTargetFile = explode ("|", $targetFile);
					$targetID = $splitTargetFile[1];

					echo "downloading $targetFile - ID $targetID\n";

    					try {
        					$action = $srfax->Retrieve_Fax("IN", $targetID);
        					if(!empty($action)) {
            						file_put_contents("$oscar_fax_dir/$targetID.pdf", $action);
							echo "Successfully retrieved file as $oscar_fax_dir/$targetID.pdf";

							 //set viewed
                                        		try {
                                                		$action = $srfax->Update_Viewed_Status("IN", "Y", $targetID);
                                                		echo "Fax set to viewed\n";
                                        		} catch (Exception $e) {
                                                		$out = "Error: " . $e->getMessage();
                                        		}
        				}
    					} catch (Exception $e) {
        					$out = "Error: " . $e->getMessage();
					}

				}
			}
		}
	}

echo "$out\n";
exit(0);

?>         

You will need a wrapper to call the php script. Lets call it faxwrap.sh

 

#! /bin/bash 
php /usr/share/oscar-emr/php-oscar-srfax/srfax2o.php --func=oscar-inbox >> /usr/share/oscar-emr/php-oscar-srfax/php-oscar-srfax.log 2>&1 
chown -R tomcat9:tomcat9 /usr/share/oscar-emr/OscarDocument/oscar/incomingdocs/1/Fax/

And set it up as a cron job (you will need to run it as root or the tomcat user to open the files that are dropped by Oscar into the tmp directory.)  The following example has the root user sending the files

sudo crontab -e

And add an entry like the following that executes every 2 minutes

*/2 * * * * /usr/share/oscar-emr/php-oscar-srfax/faxwrap.sh 

Table of Contents