fixes to prior commit
[openemr.git] / library / log.inc
blob2b36482569989adc2211f398aff4e673e2c81406
1 <?php
2 #require_once("{$GLOBALS['srcdir']}/sql.inc");
3 require_once(dirname(__FILE__). "/sql.inc");
4 require_once(dirname(__FILE__). "/formdata.inc.php");
6 function newEvent($event, $user, $groupname, $success, $comments="", $patient_id=null, $log_from = 'open-emr', $menu_item = 'dashboard', $ccda_doc_id = 0) {
7     $adodb = $GLOBALS['adodb']['db'];
8     $crt_user=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ?  $_SERVER['SSL_CLIENT_S_DN_CN'] : null;
10     // deal with comments encryption, if turned on
11     $encrypt_comment = 'No';
12     if (!empty($comments)) {
13         if ($GLOBALS["enable_auditlog_encryption"]) {
14             $comments =  aes256Encrypt($comments);
15             $encrypt_comment = 'Yes';
16         }
17     }
18     
19     if ($log_from == 'patient-portal') {
20         $sqlMenuItems = "SELECT * FROM patient_portal_menu";
22         $resMenuItems = sqlStatement($sqlMenuItems);
23         for($iter=0; $rowMenuItem=sqlFetchArray($resMenuItems); $iter++) {
24             $menuItems[$rowMenuItem['patient_portal_menu_id']] = $rowMenuItem['menu_name'];
25         }
27         $menuItemId = array_search($menu_item, $menuItems);
28         $sql = "insert into log ( date, event, user, patient_id, groupname, success, comments, 
29                 log_from, menu_item_id, crt_user, ccda_doc_id) values ( NOW(), ?, ?, ?, ?, ?, ?, ?, ?,?, ?)";
30         $ret = sqlStatementNoLog($sql, array($event, $user, $patient_id, $groupname, $success, $comments,$log_from, $menuItemId,$crt_user, $ccda_doc_id));
31     } else {    
32     /* More details added to the log */
33     $sql = "insert into log ( date, event, user, groupname, success, comments, crt_user, patient_id) " .
34             "values ( NOW(), " . $adodb->qstr($event) . "," . $adodb->qstr($user) .
35             "," . $adodb->qstr($groupname) . "," . $adodb->qstr($success) . "," .
36             $adodb->qstr($comments) ."," .
37             $adodb->qstr($crt_user) ."," . $adodb->qstr($patient_id). ")";
39     $ret = sqlInsertClean_audit($sql);
40     }
42     // Send item to log_comment_encrypt for comment encyption tracking
43     $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
44     $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
45                      " VALUES ( ".
46                      $adodb->qstr($last_log_id) . "," .
47                      $adodb->qstr($encrypt_comment) . "," .
48                      "'')";
49     sqlInsertClean_audit($encryptLogQry); 
51     if(($patient_id=="NULL") || ($patient_id==null))$patient_id=0;
53     send_atna_audit_msg($user, $groupname, $event, $patient_id, $success, $comments);
56 function getEventByDate($date, $user="", $cols="DISTINCT date, event, user, groupname, patient_id, success, comments, checksum")
58     $sql = "SELECT $cols FROM log WHERE date >= '$date 00:00:00' AND date <= '$date 23:59:59'";
59     if ($user) $sql .= " AND user LIKE '$user'";
60     $sql .= " ORDER BY date DESC LIMIT 5000";
61     $res = sqlStatement($sql);
62     for($iter=0; $row=sqlFetchArray($res); $iter++) {
63         $all[$iter] = $row;
64     }
65     return $all;
68 /******************
69  * Get records from the LOG and Extended_Log table
70  * using the optional parameters:
71  *   date : a specific date  (defaults to today)
72  *   user : a specific user  (defaults to none)
73  *   cols : gather specific columns  (defaults to date,event,user,groupname,comments)
74  *   sortby : sort the results by  (defaults to none)
75  * RETURNS:
76  *   array of results
77  ******************/
78 function getEvents($params) 
80     // parse the parameters
81     $cols = "DISTINCT date, event, user, groupname, patient_id, success, comments,checksum,crt_user, id ";
82     if (isset($params['cols']) && $params['cols'] != "") $cols = $params['cols'];
84     $date1 = date("Y-m-d H:i:s", time());
85     if (isset($params['sdate']) && $params['sdate'] != "") $date1= $params['sdate'];
86     
87     $date2 = date("Y-m-d H:i:s", time());
88     if (isset($params['edate']) && $params['edate'] != "") $date2= $params['edate'];
89     
90     $user = "";
91     if (isset($params['user']) && $params['user'] != "") $user= $params['user'];
93     //VicarePlus :: For Generating log with patient id.
94     $patient = "";
95     if (isset($params['patient']) && $params['patient'] != "") $patient= $params['patient'];
96     
97     $sortby = "";
98     if (isset($params['sortby']) && $params['sortby'] != "") $sortby = $params['sortby'];
99     
100     $levent = "";
101     if (isset($params['levent']) && $params['levent'] != "") $levent = $params['levent'];
102     
103      $tevent = "";
104     if (isset($params['tevent']) && $params['tevent'] != "") $tevent = $params['tevent'];
105     
106      $event = "";
107     if (isset($params['event']) && $params['event'] != "") $event = $params['event'];
108     if ($event!=""){
109     if ($sortby == "comments") $sortby = "description";
110     if ($sortby == "groupname") $sortby = ""; //VicarePlus :: since there is no groupname in extended_log
111     if ($sortby == "success") $sortby = "";   //VicarePlus :: since there is no success field in extended_log
112     if ($sortby == "checksum") $sortby = "";  //VicarePlus :: since there is no checksum field in extended_log
113     $columns = "DISTINCT date, event, user, recipient,patient_id,description";
114     $sql = "SELECT $columns FROM extended_log WHERE date >= '$date1' AND date <= '$date2'";
115     if ($user != "") $sql .= " AND user LIKE '$user'";
116     if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
117     if ($levent != "") $sql .= " AND event LIKE '$levent%'";
118     if ($sortby != "") $sql .= " ORDER BY ".$sortby." DESC "; // descending order
119     $sql .= " LIMIT 5000";
120     }
121     else
122     {
123     // do the query
124     $sql = "SELECT $cols FROM log WHERE date >= '$date1' AND date <= '$date2'";
125     if ($user != "") $sql .= " AND user LIKE '$user'";
126     if ($patient != "") $sql .= " AND patient_id LIKE '$patient'";
127     if ($levent != "") $sql .= " AND event LIKE '$levent%'";
128     if ($tevent != "") $sql .= " AND event LIKE '%$tevent'";
129     if ($sortby != "") $sql .= " ORDER BY ".$sortby." DESC "; // descending order
130     $sql .= " LIMIT 5000";
131     }
132     $res = sqlStatement($sql);
133     for($iter=0; $row=sqlFetchArray($res); $iter++) {
134         $all[$iter] = $row;
135     }
136     return $all;
139 /* Given an SQL insert/update that was just performeds:
140  * - Find the table and primary id of the row that was created/modified
141  * - Calculate the SHA1 checksum of that row (with all the
142  *   column values concatenated together).
143  * - Return the SHA1 checksum as a 40 char hex string.
144  * If this is not an insert/update query, return "".
145  * If multiple rows were modified, return "".
146  * If we're unable to determine the row modified, return "".
148  * TODO: May need to incorporate the binded stuff (still analyzing)
150  */
151 function sql_checksum_of_modified_row($statement)
153     $table = "";
154     $rid = "";
156     $tokens = preg_split("/[\s,(\'\"]+/", $statement);
157     /* Identifying the id for insert/replace statements for calculating the checksum */
158         if((strcasecmp($tokens[0],"INSERT")==0) || (strcasecmp($tokens[0],"REPLACE")==0)){
159         $table = $tokens[2];
160         $rid = generic_sql_insert_id();
161         /* For handling the table that doesn't have auto-increment column */
162         if ($rid === 0 || $rid === FALSE) {
163           if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map")
164            $id="acl_id";
165           else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map")
166           $id="group_id";
167           else
168            $id="id";
169           /* To handle insert statements */
170           if($tokens[3] == $id){
171              for($i=4;$i<count($tokens);$i++){
172                  if(strcasecmp($tokens[$i],"VALUES")==0){
173                   $rid=$tokens[$i+1];
174                      break;
175                 }// if close
176               }//for close
177             }//if close
178         /* To handle replace statements */
179           else if(strcasecmp($tokens[3],"SET")==0){
180                  if((strcasecmp($tokens[4],"ID")==0) || (strcasecmp($tokens[4],"`ID`")==0)){
181                   $rid=$tokens[6];
182            }// if close
183         }
185         else {          
186             return "";
187           }
188         }
189     }
190      /* Identifying the id for update statements for calculating the checksum */
191        else if(strcasecmp($tokens[0],"UPDATE")==0){
192         $table = $tokens[1];
194         $offset = 3;
195         $total = count($tokens);
197         /* Identifying the primary key column for the updated record */ 
198         if ($table == "form_physical_exam") {
199             $id = "forms_id";
200         }
201         else if ($table == "claims"){
202             $id = "patient_id";
203         }
204         else if ($table == "openemr_postcalendar_events") {
205             $id = "pc_eid";
206         }
207          else if ($table == "lang_languages"){
208             $id = "lang_id";
209          }
210          else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
211             $id = "pc_catid";
212          }
213          else if ($table == "openemr_postcalendar_limits"){
214             $id = "pc_limitid";
215          }
216          else if($table == "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
217            $id="acl_id";
218           }
219           else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
220           $id="group_id";
221           }
222            else {
223             $id = "id";
224            }
225         
226          /* Identifying the primary key value for the updated record */
227         while ($offset < $total) {
228             /* There are 4 possible ways that the id=123 can be parsed:
229              * ('id', '=', '123')
230              * ('id=', '123')
231              * ('id=123')
232              * ('id', '=123')
233              */
234             $rid = "";
235            /*id=', '123'*/
236             if (($tokens[$offset] == "$id=") && ($offset + 1 < $total)) {
237                 $rid = $tokens[$offset+1];
238                 break;
239             }
240            /* 'id', '=', '123' */
241             else if ($tokens[$offset] == "$id" && $tokens[$offset+1] == "=" && ($offset+2 < $total)) {
242                 $rid = $tokens[$offset+2];
243                 break;
244              }
245             /*id=123*/
246             else if (strpos($tokens[$offset], "$id=") === 0) {
247                 $tid = substr($tokens[$offset], strlen($id)+1);
248                 if(is_numeric($tid))
249                  $rid=$tid;
250                  break;
251              }
252            /*'id', '=123' */
253              else if($tokens[$offset] == "$id") {
254                 $tid = substr($tokens[$offset+1],1);
255                 if(is_numeric($tid))
256                  $rid=$tid;
257                 break;
258               } 
259             $offset += 1;
260         }//while ($offset < $total)
261     }// else if ($tokens[0] == 'update' || $tokens[0] == 'UPDATE' )
263     if ($table == "" || $rid == "") {
264         return "";
265     }
266    /* Framing sql statements for calculating checksum */ 
267    if ($table == "form_physical_exam") {
268         $sql = "select * from $table where forms_id = $rid";
269     }
270    else if ($table == "claims"){
271                 $sql = "select * from $table where patient_id = $rid";
272         }
273    else if ($table == "openemr_postcalendar_events") {
274             $sql = "select * from $table where pc_eid = $rid";
275         }
276     else if ($table == "lang_languages") {
277             $sql = "select * from $table where lang_id = $rid";
278     }
279     else if ($table == "openemr_postcalendar_categories" || $table == "openemr_postcalendar_topics"){
280             $sql = "select * from $table where pc_catid = $rid";
281          }
282     else if ($table == "openemr_postcalendar_limits"){
283            $sql = "select * from $table where pc_limitid = $rid";
284          }
285     else if ($table ==  "gacl_aco_map" || $table == "gacl_aro_groups_map" || $table == "gacl_aro_map" || $table == "gacl_axo_groups_map" || $table == "gacl_axo_map"){
286            $sql = "select * from $table where acl_id = $rid";
287          }
288      else if($table == "gacl_groups_aro_map" || $table == "gacl_groups_axo_map"){
289            $sql = "select * from $table where group_id = $rid";
290       }
291      else {
292         $sql = "select * from $table where id = $rid";
293     }
294     // When this function is working perfectly, can then shift to the
295     // sqlQueryNoLog() function.
296     $results = sqlQueryNoLogIgnoreError($sql);    
297     $column_values = "";
298    /* Concatenating the column values for the row inserted/updated */
299     if (is_array($results)) {
300         foreach ($results as $field_name => $field) {
301             $column_values .= $field;
302         }
303     }
304     // ViCarePlus: As per NIST standard, the encryption algorithm SHA1 is used
306     //error_log("COLUMN_VALUES: ".$column_values,0);
307     return sha1($column_values);
310 /* Create an XML audit record corresponding to RFC 3881.
311  * The parameters passed are the column values (from table 'log')
312  * for a single audit record.
313  */
314 function create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments)
317     /* Event action codes indicate whether the event is read/write.
318      * C = create, R = read, U = update, D = delete, E = execute
319      */
320     $eventActionCode = 'E';
321     if (substr($event, -7) == "-create") {
322         $eventActionCode = 'C';
323     }
324     else if (substr($event, -7) == "-insert") {
325         $eventActionCode = 'C';
326     }
327     else if (substr($event, -7) == "-select") {
328         $eventActionCode = 'R';
329     }
330     else if (substr($event, -7) == "-update") {
331         $eventActionCode = 'U';
332     }
333     else if (substr($event, -7) == "-delete") {
334         $eventActionCode = 'D';
335     }
337     $date_obj = new DateTime();
338     $eventDateTime = $date_obj->format(DATE_ATOM);
340     /* For EventOutcomeIndicator, 0 = success and 4 = minor error */
341     $eventOutcome = ($outcome === 1) ? 0 : 4;
343     /* The choice of event codes is up to OpenEMR.
344      * We're using the same event codes as
345      * https://iheprofiles.projects.openhealthtools.org/
346      */
347     $eventIDcodeSystemName = "DCM";
348     $eventIDcode = 0;
349     $eventIDdisplayName = $event;
351     if (strpos($event, 'patient-record') !== FALSE) {
352         $eventIDcode = 110110;
353         $eventIDdisplayName = 'Patient Record';
354     }
355     else if (strpos($event, 'view') !== FALSE) {
356         $eventIDCode = 110110;
357         $eventIDdisplayName = 'Patient Record';
358     }
359     else if (strpos($event, 'login') !== FALSE) {
360         $eventIDcode = 110122;
361         $eventIDdisplayName = 'Login';
362     }
363     else if (strpos($event, 'logout') !== FALSE) {
364         $eventIDcode = 110123;
365         $eventIDdisplayName = 'Logout';
366     }
367     else if (strpos($event, 'scheduling') !== FALSE) {
368         $eventIDcode = 110111;
369         $eventIDdisplayName = 'Patient Care Assignment';
370     }
371     else if (strpos($event, 'security-administration') !== FALSE) {
372         $eventIDcode = 110129;
373         $eventIDdisplayName = 'Security Administration';
374     }
377    
379     /* Variables used in ActiveParticipant section, which identifies
380      * the IP address and application of the source and destination.
381      */
382     $srcUserID = $_SERVER['SERVER_NAME'] . '|OpenEMR';
383     $srcNetwork = $_SERVER['SERVER_ADDR'];
384     $destUserID = $GLOBALS['atna_audit_host'];
385     $destNetwork = $GLOBALS['atna_audit_host'];
387     $userID = $user;
388     $userTypeCode = 1;
389     $userRole = 6;
390     $userCode = 11;
391     $userDisplayName = 'User Identifier';
393     $patientID = "";
394     $patientTypeCode = "";
395     $patientRole = "";
396     $patientCode = "";
397     $patientDisplayName = "";
399     if ($eventIDdisplayName == 'Patient Record') {
400         $patientID = $patient_id;
401         $pattientTypeCode = 1;
402         $patientRole = 1;
403         $patientCode = 2;
404         $patientDisplayName = 'Patient Number';
405     }
407     /* Construct the XML audit message, and save to $msg */
408     $msg =  '<?xml version="1.0" encoding="ASCII"?>';
409     $msg .= '<AuditMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ';
410     $msg .= 'xsi:noNamespaceSchemaLocation="healthcare-security-audit.xsd">';
412     /* Indicate the event code, text name, read/write type, and date/time */
413     $msg .= "<EventIdentification EventActionCode=\"$eventActionCode\" ";
414     $msg .= "EventDateTime=\"$eventDateTime\" ";
415     $msg .= "EventOutcomeIndicator=\"$eventOutcome\">";
416     $msg .= "<EventID code=\"eventIDcode\" displayName=\"$eventIDdisplayName\" ";
417     $msg .= "codeSystemName=\"DCM\" />";
418     $msg .= "</EventIdentification>";
420     /* Indicate the IP address and application of the source and destination */
421     $msg .= "<ActiveParticipant UserID=\"$srcUserID\" UserIsRequestor=\"true\" ";
422     $msg .= "NetworkAccessPointID=\"$srcNetwork\" NetworkAccessPointTypeCode=\"2\" >";
423     $msg .= "<RoleIDCode code=\"110153\" displayName=\"Source\" codeSystemName=\"DCM\" />";
424     $msg .= "</ActiveParticipant>";
425     $msg .= "<ActiveParticipant UserID=\"$destUserID\" UserIsRequestor=\"false\" ";
426     $msg .= "NetworkAccessPointID=\"$destNetwork\" NetworkAccessPointTypeCode=\"2\" >";
427     $msg .= "<RoleIDCode code=\"110152\" displayName=\"Destination\" codeSystemName=\"DCM\" />";
428     $msg .= "</ActiveParticipant>";
430     $msg .= "<AuditSourceIdentification AuditSourceID=\"$srcUserID\" />";
432     /* Indicate the username who generated this audit record */
433     $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$user\" ";
434     $msg .= "ParticipantObjectTypeCode=\"1\" ";
435     $msg .= "ParticipantObjectTypeCodeRole=\"6\" >";
436     $msg .= "<ParticipantObjectIDTypeCode code=\"11\" ";
437     $msg .= "displayName=\"User Identifier\" ";
438     $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
440     if ($eventIDdisplayName == 'Patient Record' && $patient_id != 0) {
441         $msg .= "<ParticipantObjectIdentification ParticipantObjectID=\"$patient_id\" ";
442         $msg .= "ParticipantObjectTypeCode=\"1\" ";
443         $msg .= "ParticipantObjectTypeCodeRole=\"1\" >";
444         $msg .= "<ParticipantObjectIDTypeCode code=\"2\" ";
445         $msg .= "displayName=\"Patient Number\" ";
446         $msg .= "codeSystemName=\"RFC-3881\" /></ParticipantObjectIdentification>";
447     }
448     $msg .= "</AuditMessage>";
450     /* Add the syslog header */
451     $date_obj = new DateTime($date);
452     $datestr= $date_obj->format(DATE_ATOM);
453     $msg = "<13> " . $datestr . " " . $_SERVER['SERVER_NAME'] . " " . $msg;
454     return $msg;
458 /* Create a TLS (SSLv3) connection to the given host/port.
459  * $localcert is the path to a PEM file with a client certificate and private key.
460  * $cafile is the path to the CA certificate file, for
461  *  authenticating the remote machine's certificate.
462  * If $cafile is "", the remote machine's certificate is not verified.
463  * If $localcert is "", we don't pass a client certificate in the connection.
465  * Return a stream resource that can be used with fwrite(), fread(), etc.
466  * Returns FALSE on error.
467  */
468 function create_tls_conn($host, $port, $localcert, $cafile) {
469     $sslopts = array();
470     if ($cafile !== null && $cafile != "") {
471         $sslopts['cafile'] = $cafile;
472         $sslopts['verify_peer'] = TRUE;
473         $sslopts['verify_depth'] = 10;
474     }
475     if ($localcert !== null && $localcert != "") {
476         $sslopts['local_cert'] = $localcert;
477     }
478     $opts = array('tls' => $sslopts, 'ssl' => $sslopts);
479     $ctx = stream_context_create($opts);
480     $timeout = 60;
481     $flags = STREAM_CLIENT_CONNECT;
483     $olderr = error_reporting(0);
484     $conn = stream_socket_client('tls://' . $host . ":" . $port, $errno, $errstr,
485                                  $timeout, $flags, $ctx);
486     error_reporting($olderr);
487     return $conn;
491 /* This function is used to send audit records to an Audit Repository Server,
492  * as described in the Audit Trail and Node Authentication (ATNA) standard.
493  * Given the fields in a single audit record:
494  * - Create an XML audit message according to RFC 3881, including the RFC5425 syslog header.
495  * - Create a TLS connection that performs bi-directions certificate authentication,
496  *   according to RFC 5425.
497  * - Send the XML message on the TLS connection.
498  */
499 function send_atna_audit_msg($user, $group, $event, $patient_id, $outcome, $comments)
501     /* If no ATNA repository server is configured, return */
502     if ($GLOBALS['atna_audit_host'] === null || $GLOBALS['atna_audit_host'] == "" || !($GLOBALS['enable_atna_audit'])) {
503         return;
504     }
505     $host = $GLOBALS['atna_audit_host'];
506     $port = $GLOBALS['atna_audit_port'];
507     $localcert = $GLOBALS['atna_audit_localcert'];
508     $cacert = $GLOBALS['atna_audit_cacert'];
509     $conn = create_tls_conn($host, $port, $localcert, $cacert);
510     if ($conn !== FALSE) {
511         $msg = create_rfc3881_msg($user, $group, $event, $patient_id, $outcome, $comments);
512         $len = strlen($msg);
513         fwrite($conn, $msg);
514         fclose($conn);
515     }
519 /* Add an entry into the audit log table, indicating that an
520  * SQL query was performed. $outcome is true if the statement
521  * successfully completed.  Determine the event type based on
522  * the tables present in the SQL query.
523  */
524 function auditSQLEvent($statement, $outcome, $binds=NULL)
527     $user =  isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
528         /* Don't log anything if the audit logging is not enabled. Exception for "emergency" users */
529    if (!isset($GLOBALS['enable_auditlog']) || !($GLOBALS['enable_auditlog']))
530    {
531             if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
532             return;
533    }
536    $statement = trim($statement);
538     /* Don't audit SQL statements done to the audit log,
539      * or we'll have an infinite loop.
540      */
541     if ((stripos($statement, "insert into log") !== FALSE) ||
542         (stripos($statement, "FROM log ") !== FALSE) ) {
543         return;
544     }
546     $group = isset($_SESSION['authGroup']) ?  $_SESSION['authGroup'] : "";
547     $comments = $statement;
549     $processed_binds = "";
550     if (is_array($binds)) {
551         // Need to include the binded variable elements in the logging
552         $first_loop=true;
553         foreach ($binds as $value_bind) {
554             if ($first_loop) {
555                 //no comma
556                 $processed_binds .= "'" . add_escape_custom($value_bind) . "'";
557                 $first_loop=false;
558             }
559             else {
560                 //add a comma
561                 $processed_binds .= ",'" . add_escape_custom($value_bind) . "'";
562             }
563         }
564         if (!empty($processed_binds)) {
565             $processed_binds = "(" . $processed_binds . ")";
566             $comments .= " " . $processed_binds;
567         }
568     }
570     $success = 1;
571     $checksum = "";
572     if ($outcome === FALSE) {
573         $success = 0;
574     }
575     if ($outcome !== FALSE) {
576         // Should use the $statement rather than the processed
577         // variables, which includes the binded stuff. If do
578         // indeed need the binded values, then will need
579         // to include this as a separate array.
581         //error_log("STATEMENT: ".$statement,0);
582         //error_log("BINDS: ".$processed_binds,0);
583         $checksum = sql_checksum_of_modified_row($statement);
584         //error_log("CHECKSUM: ".$checksum,0);
585     }
586     /* Determine the query type (select, update, insert, delete) */
587     $querytype = "select";
588     $querytypes = array("select", "update", "insert", "delete","replace");
589     foreach ($querytypes as $qtype) {
590         if (stripos($statement, $qtype) === 0) {
591             $querytype = $qtype;
592         }
593     }
595     /* Determine the audit event based on the database tables */
596     $event = "other";
597     $tables = array("billing" => "patient-record",
598                     "claims" => "patient-record",
599                     "employer_data" => "patient-record",
600                     "forms" => "patient-record",
601                     "form_encounter" => "patient-record",
602                     "form_dictation" => "patient-record",
603                     "form_misc_billing_options" => "patient-record",
604                     "form_reviewofs" => "patient-record",
605                     "form_ros" => "patient-record",
606                     "form_soap" => "patient-record",
607                     "form_vitals" => "patient-record",
608                     "history_data" => "patient-record",
609                     "immunizations" => "patient-record",
610                     "insurance_data" => "patient-record",
611                     "issue_encounter" => "patient-record",
612                     "lists" => "patient-record",
613                     "patient_data" => "patient-record",
614                     "payments" => "patient-record",
615                     "pnotes" => "patient-record",
616                     "onotes" => "patient-record",
617                     "prescriptions" => "order",
618                     "transactions" => "patient-record",
619                                         "amendments" => "patient-record",
620                                         "amendments_history" => "patient-record",
621                     "facility" => "security-administration",
622                     "pharmacies" => "security-administration",
623                     "addresses" => "security-administration",
624                     "phone_numbers" => "security-administration",
625                     "x12_partners" => "security-administration",
626                     "insurance_companies" => "security-administration",
627                     "codes" => "security-administration",
628                     "registry" => "security-administration", 
629                     "users" => "security-administration",
630                     "groups" => "security-administration",
631                     "openemr_postcalendar_events" => "scheduling",
632                                 "openemr_postcalendar_categories" => "security-administration",
633                                 "openemr_postcalendar_limits" => "security-administration",
634                                 "openemr_postcalendar_topics" => "security-administration",
635                                 "gacl_acl" => "security-administration",
636                                 "gacl_acl_sections" => "security-administration",
637                                 "gacl_acl_seq" => "security-administration",
638                                 "gacl_aco" => "security-administration",
639                                 "gacl_aco_map" => "security-administration",
640                                 "gacl_aco_sections" => "security-administration",
641                                 "gacl_aco_sections_seq" => "security-administration",
642                                 "gacl_aco_seq" => "security-administration",
643                                 "gacl_aro" => "security-administration",
644                                 "gacl_aro_groups" => "security-administration",                                 
645                                 "gacl_aro_groups_id_seq" => "security-administration",
646                                 "gacl_aro_groups_map" => "security-administration",
647                                 "gacl_aro_map" => "security-administration",
648                                 "gacl_aro_sections" => "security-administration",
649                                 "gacl_aro_sections_seq" => "security-administration",
650                                 "gacl_aro_seq" => "security-administration",
651                                 "gacl_axo" => "security-administration",
652                                 "gacl_axo_groups" => "security-administration",
653                                 "gacl_axo_groups_map" => "security-administration",
654                                 "gacl_axo_map" => "security-administration",
655                                 "gacl_axo_sections" => "security-administration",
656                                 "gacl_groups_aro_map" => "security-administration",
657                                 "gacl_groups_axo_map" => "security-administration",
658                                 "gacl_phpgacl" => "security-administration"                             
659                   );
661     /* When searching for table names, truncate the SQL statement,
662      * removing any WHERE, SET, or VALUE clauses.
663      */
664         $truncated_sql = $statement;
665         $truncated_sql = str_replace("\n", " ", $truncated_sql);
666         if ($querytype == "select") {
667         $startwhere = stripos($truncated_sql, " where ");
668         if ($startwhere > 0) {
669         $truncated_sql = substr($truncated_sql, 0, $startwhere);
670     }
672         else {
673      $startparen = stripos($truncated_sql, "(" );
674      $startset = stripos($truncated_sql, " set ");
675      $startvalues = stripos($truncated_sql, " values ");
677         if ($startparen > 0) {
678             $truncated_sql = substr($truncated_sql, 0, $startparen);
679         }
680         if ($startvalues > 0) {
681             $truncated_sql = substr($truncated_sql, 0, $startvalues);
682         }
683         if ($startset > 0) {
684             $truncated_sql = substr($truncated_sql, 0, $startset);
685         }
686     }
687     foreach ($tables as $table => $value) {
688         if (strpos($truncated_sql, $table) !== FALSE) {
689             $event = $value;
690              break;
691         }
692       else if (strpos($truncated_sql, "form_") !== FALSE) {
693             $event = "patient-record";
694              break;
695         }
696     }
698     /* Avoid filling the audit log with trivial SELECT statements.
699      * Skip SELECTs from unknown tables.  
700      * Skip SELECT count() statements.
701      * Skip the SELECT made by the authCheckSession() function.
702      */
703     if ($querytype == "select") {
704         if ($event == "other")
705             return;
706         if (stripos($statement, "SELECT count(" ) === 0)
707             return;
708         if (stripos($statement, "select username, password from users") === 0)
709             return;
710     }
713     /* If the event is a patient-record, then note the patient id */
714     $pid = 0;
715     if ($event == "patient-record") {
716         if (array_key_exists('pid', $_SESSION) && $_SESSION['pid'] != '') {
717             $pid = $_SESSION['pid'];
718         }
719     }
721     /* If query events are not enabled, don't log them */
722     if (($querytype == "select") && !($GLOBALS['audit_events_query']))
723     { 
724        if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
725        return;
726     }
728     if (!($GLOBALS["audit_events_${event}"])) 
729     {
730         if ((soundex($user) != soundex("emergency")) && (soundex($user) != soundex("breakglass")))
731         return;
732     }
733            
735     $event = $event . "-" . $querytype;
737     $adodb = $GLOBALS['adodb']['db'];
738    
739     // ViSolve : Don't log sequences - to avoid the affect due to GenID calls
740     if (strpos($comments, "sequences") !== FALSE) return;
741         
742         $encrypt_comment = 'No';
743         //July 1, 2014: Ensoftek: Check and encrypt audit logging
744         if ($GLOBALS["enable_auditlog_encryption"]) {
745                 $comments =  aes256Encrypt($comments);
746                 $encrypt_comment = 'Yes';
747         }
748         
749         $current_datetime = date("Y-m-d H:i:s");
750     $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
751     $sql = "insert into log (date, event, user, groupname, comments, patient_id, success, checksum,crt_user) " .
752          "values ( ".
753                  $adodb->qstr($current_datetime). ", ".
754          $adodb->qstr($event) . ", " .
755          $adodb->qstr($user) . "," . 
756          $adodb->qstr($group) . "," .
757          $adodb->qstr($comments) . "," .
758          $adodb->qstr($pid) . "," .
759          $adodb->qstr($success) . "," . 
760          $adodb->qstr($checksum) . "," .
761          $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
762         sqlInsertClean_audit($sql);
763         
764         $last_log_id = $GLOBALS['adodb']['db']->Insert_ID();
765         $checksumGenerate = '';
766         //July 1, 2014: Ensoftek: Record the encryption checksum in a secondary table(log_comment_encrypt)
767         if ($querytype == 'update') {
768                 $concatLogColumns = $current_datetime.$event.$user.$group.$comments.$pid.$success.$checksum.$SSL_CLIENT_S_DN_CN;
769                 $checksumGenerate = sha1($concatLogColumns);
770         }
771         $encryptLogQry = "INSERT INTO log_comment_encrypt (log_id, encrypt, checksum) ".
772                                          " VALUES ( ".
773                                           $adodb->qstr($last_log_id) . "," .
774                                           $adodb->qstr($encrypt_comment) . "," .
775                                           $adodb->qstr($checksumGenerate) .")"; 
776         sqlInsertClean_audit($encryptLogQry);
777         
778     send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
779     //return $ret;
782 // May-29-2014: Ensoftek: For Auditable events and tamper-resistance (MU2)
783 // Insert Audit Logging Status into the LOG table.
784 function auditSQLAuditTamper($enable)
786     $user =  isset($_SESSION['authUser']) ? $_SESSION['authUser'] : "";
787     $group = isset($_SESSION['authGroup']) ?  $_SESSION['authGroup'] : "";
788     $pid = 0;
789     $checksum = "";
790     $success = 1;
791         $event = "security-administration" . "-" . "insert";
794     $adodb = $GLOBALS['adodb']['db'];
795    
796         if ($enable == "1")
797         {
798                 $comments = "Audit Logging Enabled.";
799         }
800         else
801         {
802                 $comments = "Audit Logging Disabled.";
803         }
804            
805     $SSL_CLIENT_S_DN_CN=isset($_SERVER['SSL_CLIENT_S_DN_CN']) ? $_SERVER['SSL_CLIENT_S_DN_CN'] : '';
806     $sql = "insert into log (date, event, user, groupname, comments, patient_id, success, checksum,crt_user) " .
807          "values ( NOW(), " . 
808          $adodb->qstr($event) . ", " .
809          $adodb->qstr($user) . "," . 
810          $adodb->qstr($group) . "," .
811          $adodb->qstr($comments) . "," .
812          $adodb->qstr($pid) . "," .
813          $adodb->qstr($success) . "," . 
814          $adodb->qstr($checksum) . "," .
815          $adodb->qstr($SSL_CLIENT_S_DN_CN) .")";
817     sqlInsertClean_audit($sql);
818     send_atna_audit_msg($user, $group, $event, $pid, $success, $comments);
822  * Record the patient disclosures.
823  * @param $dates    - The date when the disclosures are sent to the thrid party.
824  * @param $event    - The type of the disclosure.
825  * @param $pid      - The id of the patient for whom the disclosures are recorded.
826  * @param $comment  - The recipient name and description of the disclosure.
827  * @uname           - The username who is recording the disclosure.
828  */
829 function recordDisclosure($dates,$event,$pid,$recipient,$description,$user)
831         $adodb = $GLOBALS['adodb']['db'];
832         $crt_user= $_SERVER['SSL_CLIENT_S_DN_CN'];
833         $groupname=$_SESSION['authProvider'];
834         $success=1;
835         $sql = "insert into extended_log ( date, event, user, recipient, patient_id, description) " .
836             "values (" . $adodb->qstr($dates) . "," . $adodb->qstr($event) . "," . $adodb->qstr($user) .
837             "," . $adodb->qstr($recipient) . ",".
838             $adodb->qstr($pid) ."," .
839             $adodb->qstr($description) .")";
840         $ret = sqlInsertClean_audit($sql);
843  * Edit the disclosures that is recorded.
844  * @param $dates  - The date when the disclosures are sent to the thrid party.
845  * @param $event  - The type of the disclosure.
846  * param $comment - The recipient and the description of the disclosure are appended.
847  * $logeventid    - The id of the record which is to be edited.
848  */
849 function updateRecordedDisclosure($dates,$event,$recipient,$description,$disclosure_id)
851          $adodb = $GLOBALS['adodb']['db'];
852          $sql="update extended_log set
853                 event=" . $adodb->qstr($event) . ",
854                 date=" .  $adodb->qstr($dates) . ",
855                 recipient=" . $adodb->qstr($recipient) . ",
856                 description=" . $adodb->qstr($description) . "
857                 where id=" . $adodb->qstr($disclosure_id) . "";
858           $ret = sqlInsertClean_audit($sql);
861  * Delete the disclosures that is recorded.
862  * $deleteid - The id of the record which is to be deleted.
863  */
864 function deleteDisclosure($deletelid)
866         $sql="delete from extended_log where id='" . add_escape_custom($deletelid) . "'";
867         $ret = sqlInsertClean_audit($sql);
870 //July 1, 2014: Ensoftek: Function to AES256 encrypt a given string
871 function aes256Encrypt($sValue){
872         $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
873     return rtrim(
874         base64_encode(
875             mcrypt_encrypt(
876                 MCRYPT_RIJNDAEL_256,
877                 $sSecretKey, $sValue, 
878                 MCRYPT_MODE_ECB, 
879                 mcrypt_create_iv(
880                     mcrypt_get_iv_size(
881                         MCRYPT_RIJNDAEL_256, 
882                         MCRYPT_MODE_ECB
883                     ), 
884                     MCRYPT_RAND)
885                 )
886             ), "\0"
887         );
890 //July 1, 2014: Ensoftek: Function to AES256 decrypt a given string
891 function aes256Decrypt($sValue){
892         $sSecretKey = pack('H*', "bcb04b7e103a0cd8b54763051cef08bc55abe029fdebae5e1d417e2ffb2a00a3");
893     return rtrim(
894         mcrypt_decrypt(
895             MCRYPT_RIJNDAEL_256, 
896             $sSecretKey, 
897             base64_decode($sValue), 
898             MCRYPT_MODE_ECB,
899             mcrypt_create_iv(
900                 mcrypt_get_iv_size(
901                     MCRYPT_RIJNDAEL_256,
902                     MCRYPT_MODE_ECB
903                 ), 
904                 MCRYPT_RAND
905             )
906         ), "\0"
907     );
910 //July 1, 2014: Ensoftek: Utility function to get data from table(log_comment_encrypt)
911 function logCommentEncryptData($log_id){
912         $encryptRow = array();
913         $logRes = sqlStatement("SELECT * FROM log_comment_encrypt WHERE log_id=?", array($log_id));
914         while($logRow = sqlFetchArray($logRes)){
915                 $encryptRow['encrypt'] = $logRow['encrypt'];
916                 $encryptRow['checksum'] = $logRow['checksum'];
917         }
918         return $encryptRow;