sql-injection fix in demographics
[openemr.git] / library / direct_message_check.inc
blob55294ab963d93f3371db83b05849caa71298dc1e
1 <?php
2 /**
3  * Background receive function for phiMail Direct Messaging service.
4  *
5  * This script is called by the background service manager
6  * at /library/ajax/execute_background_services.php
7  *
8  * Copyright (C) 2013 EMR Direct <http://www.emrdirect.com/>
9  *
10  * LICENSE: This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  * You should have received a copy of the GNU General Public License
19  * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
20  *
21  * @package OpenEMR
22  * @author  EMR Direct <http://www.emrdirect.com/>
23  * @link    http://www.open-emr.org
24  */
26 require_once(dirname(__FILE__) . "/log.inc");
27 require_once(dirname(__FILE__) . "/sql.inc");
28 require_once(dirname(__FILE__) . "/pnotes.inc");
29 require_once(dirname(__FILE__) . "/../controllers/C_Document.class.php");
30 require_once(dirname(__FILE__) . "/documents.php");
31 require_once(dirname(__FILE__) . "/gprelations.inc.php");
32 require_once(dirname(__FILE__) . "/classes/class.phpmailer.php");
34 /**
35  * Connect to a phiMail Direct Messaging server 
36  */
38 function phimail_connect(&$phimail_error) {
40    if ($GLOBALS['phimail_enable'] == false) {
41       $phimail_error = 'C1';
42       return false; //for safety
43    }
45    $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
46    $phimail_username = $GLOBALS['phimail_username'];
47    $phimail_password = $GLOBALS['phimail_password'];
48    $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
49       . '/documents/phimail_server_pem/phimail_server.pem';
50    if (!file_exists($phimail_cafile)) $phimail_cafile='';
51    
52    $phimail_secure = true;
53    switch ($phimail_server['scheme']) {
54        case "tcp":
55        case "http": $server = "tcp://".$phimail_server['host'];
56                $phimail_secure = false;
57                break;
58        case "https": $server = "ssl://" . $phimail_server['host']
59                . ':' . $phimail_server['port'];
60                break;
61        case "ssl":
62        case "sslv3":
63        case "tls": $server = $GLOBALS['phimail_server_address'];
64                break;
65        default: $phimail_error = 'C2';
66                return false;
67    }
68    if ($phimail_secure) {
69       $context = stream_context_create();
70       if ($phimail_cafile != '' &&
71             (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
72              !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))) {
73          $phimail_error = 'C3';
74          return false;
75       }
76       $socket_tries = 0;
77       $fp = false;
78       while ($socket_tries < 3 && !$fp) {
79          $socket_tries++;
80          $fp = @stream_socket_client($server, $err1, $err2, 10, 
81             STREAM_CLIENT_CONNECT, $context);
82       }
83       if (!$fp) {
84          if ($err1 == '111') $err2 = xl('Server may be offline');
85          if ($err2 == '') $err2 = xl('Connection error');
86          $phimail_error = "C4 $err1 ($err2)";
87       }
88    } else {
89       $fp = @fsockopen($server,$phimail_server['port']);
90    }
91    return $fp;
94 /**
95  * Connect to a phiMail Direct Messaging server and check for any incoming status
96  * messages related to previously transmitted messages or any new messages received.
97  */
99 function phimail_check() {
100    $fp = phimail_connect($err);
101    if ($fp===false) {
102       phimail_logit(0,xl('could not connect to server').' '.$err);
103       return;
104    }
105    $phimail_username = $GLOBALS['phimail_username'];
106    $phimail_password = $GLOBALS['phimail_password'];
108    $ret = phimail_write_expect_OK($fp,"AUTH $phimail_username $phimail_password\n");
109    if($ret!==TRUE) return; //authentication error
111    if(!($notifyUsername = $GLOBALS['phimail_notify'])) $notifyUsername='admin'; //fallback
113    while (1) {
114       phimail_write($fp,"CHECK\n");
115       $ret=fgets($fp,512);
117       if($ret=="NONE\n") { //nothing to process
118           phimail_close($fp);
119           phimail_logit(1,"message check completed");
120           return;
121       }
122       else if(substr($ret,0,6)=="STATUS") {
123           //Format STATUS message-id status-code [additional-information]
124           $val=explode(" ",trim($ret),4);
125           $sql='SELECT * from direct_message_log WHERE msg_id = ?';
126           $res = sqlStatementNoLog($sql,array($val[1]));
127           if ($res===FALSE) { //database problem
128              phimail_close($fp);
129              phimail_logit(0,"database problem");
130              return;
131           }
132           if (($msg=sqlFetchArray($res))===FALSE) {
133              //no match, so log it and move on (should never happen)
134              phimail_logit(0,"NO MATCH: ".$ret);
135              $ret = phimail_write_expect_OK($fp,"OK\n"); 
136              if($ret!==TRUE) return; else continue; 
137           }
139           //if we get here, $msg contains the matching outgoing message record
140           if($val[2]=='failed') {
141              $success=0;
142              $status='F';
143           } else if ($val[2]=='dispatched') {
144              $success=1;
145              $status='D';
146           } else {
147              //unrecognized status, log it and move on (should never happen)
148              $ret = "UNKNOWN STATUS: ".$ret;
149              $success=0;
150              $status='U';
151           }
153           phimail_logit($success,$ret,$msg['patient_id']);
155           if (!isset($val[3])) $val[3]="";
156           $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
157           $res = sqlStatementNoLog($sql,array($status,$val[3],$val[1]));
158           if ($res===FALSE) { //database problem
159              phimail_close($fp);
160              phimail_logit(0,"database problem updating: ".$val[1]);
161              return;
162           }
164           if (!$success) {
165              //notify local user of failure
166              $sql = "SELECT username FROM users WHERE id = ?";
167              $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
168              $fail_user = ($res2 === FALSE || ($user_row = sqlFetchArray($res2)) === FALSE) ?
169                 xl('unknown (see log)') : $user_row['username'];
170              $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
171                 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
172              phimail_notify( xl('Direct Messaging Send Failure.'), $fail_notice);
173              $pnote_id = addPnote($msg['patient_id'], 
174                 xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
175                 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
176           }
178           //done with this status message
179           $ret = phimail_write_expect_OK($fp,"OK\n");
180           if($ret!==TRUE) {
181              phimail_close($fp);
182              return;
183           } 
185       }
187       else if(substr($ret,0,4)=="MAIL") {
189          $val = explode(" ",trim($ret),5); // MAIL recipient sender #attachments msg-id
190          $recipient=$val[1];
191          $sender=$val[2];
192          $att=(int)$val[3];
193          $msg_id=$val[4];
195          //request main message
196          $ret2 = phimail_write_expect_OK($fp,"SHOW 0\n");
197          if($ret2!==TRUE) {
198             phimail_close($fp);
199             return;
200          }
202          //get message headers
203          $hdrs="";
204          while (($next_hdr = fgets($fp,1024)) != "\n") 
205             $hdrs .= $next_hdr;
207          $mime_type=fgets($fp,512);
208          $mime_info=explode(";",$mime_type);
209          $mime_type_main=strtolower($mime_info[0]);
211          //get main message body
212          $body_len=fgets($fp,256);
213          $body=phimail_read_blob($fp,$body_len);
214          if ($body===FALSE) {
215            phimail_close($fp);
216            return;
217          }
219          $att2=fgets($fp,256);
220          if($att2!=$att) { //safety for mismatch on attachments
221             phimail_close($fp);
222             return;
223          }
225          //get attachment info
226          if($att>0) {
227             for ($attnum=0;$attnum<$att;$attnum++) {
228                 if(  ($attinfo[$attnum]['name']=fgets($fp,1024)) === FALSE
229                   || ($attinfo[$attnum]['mime']=fgets($fp,1024)) === FALSE
230                   || ($attinfo[$attnum]['desc']=fgets($fp,1024)) === FALSE) {
231                      phimail_close($fp);
232                      return;
233                 }
234              }
235           }
237           //main part gets stored as document if not plain text content
238           //(if plain text it will be the body of the final pnote)
239           $all_doc_ids = array();
240           $doc_id=0;
241           $att_detail="";
242           if ($mime_type_main != "text/plain") {
243              $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
244              $doc_id = phimail_store($name,$mime_type_main,$body);
245              if (!$doc_id) { 
246                 phimail_close($fp);
247                 return;
248              }
249              $idnum=$doc_id['doc_id']; 
250              $all_doc_ids[] = $idnum;
251              $url=$doc_id['url']; 
252              $url=substr($url,strrpos($url,"/")+1);
253              $att_detail = "\n" . xl ("Document") . " $idnum (\"$url\"; $mime_type_main; " . 
254                 filesize($body) . " bytes) Main message body";
255           }
257           //download and store attachments
258           for($attnum=0;$attnum<$att;$attnum++) {
259              $ret2 = phimail_write_expect_OK($fp,"SHOW " . ($attnum+1) . "\n");
260              if ($ret2!==TRUE) {
261                 phimail_close($fp);
262                 return;
263              }
265              //we can ignore next two lines (repeat of name and mime-type)
266              if ( ($a1=fgets($fp,512))===FALSE || ($a2=fgets($fp,512))===FALSE ) {
267                 phimail_close($fp);
268                 return;
269              }
271              $att_len = fgets($fp,256); //length of file
272              $attdata = phimail_read_blob($fp,$att_len);
273              if ($attdata===FALSE) {
274                 phimail_close($fp);
275                 return;
276              }
277              $attinfo[$attnum]['file']=$attdata;
279              $req_name = trim($attinfo[$attnum]['name']);
280              $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
281              $attinfo[$attnum]['mime'] = explode(";",trim($attinfo[$attnum]['mime']));
282              $attmime = strtolower($attinfo[$attnum]['mime'][0]);
283              $att_doc_id = phimail_store($req_name, $attmime, $attdata);
284              if (!$att_doc_id) { 
285                 phimail_close($fp);
286                 return;
287              }
288              $attinfo[$attnum]['doc_id']=$att_doc_id;
289              $idnum=$att_doc_id['doc_id']; 
290              $all_doc_ids[] = $idnum;
291              $url=$att_doc_id['url']; 
292              $url=substr($url,strrpos($url,"/")+1);
293              $att_detail = $att_detail . "\n" . xl ("Document") . " $idnum (\"$url\"; $attmime; " . 
294                  filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
295          }
297          if ($att_detail != "") 
298            $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
300          $ret2 = phimail_write_expect_OK($fp,"DONE\n"); //we'll check for failure after logging.
302          //logging only after succesful download, storage, and acknowledgement of message
303          $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
304             "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
305          $res = sqlStatementNoLog($sql,array($msg_id,$sender,$recipient,phimail_service_userID()));
307          phimail_logit(1,$ret);
309          //alert appointed user about new message
310          switch($mime_type_main) {
312            case "text/plain":
313              $body_text = @file_get_contents($body); //this was not uploaded as a document
314              unlink($body);
315              $pnote_id = addPnote(0, xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
316                0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
317              break;
319            default:
320              $note = xl("Direct Message Received.") . "\n$hdrs\n"
321                 . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
322              $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
323              break;
325          }
327          foreach ($all_doc_ids as $doc_id) setGpRelation(1, $doc_id, 6, $pnote_id);
329          if ($ret2!==TRUE) { 
330             phimail_close();
331             return; 
332          }
334       }
335       else { //unrecognized or FAIL response
336           phimail_close($fp);
337           return;
338       } 
339    }
343  * Helper functions
344  */
345 function phimail_write($fp,$text) {
346    fwrite($fp,$text);
347    fflush($fp);
350 function phimail_write_expect_OK($fp,$text) {
351    phimail_write($fp,$text);
352    $ret = fgets($fp,256);
353    if($ret!="OK\n") { //unexpected error
354       phimail_close($fp);
355       return $ret;
356    }
357    return TRUE;
360 function phimail_close($fp) {
361    fwrite($fp,"BYE\n");
362    fflush($fp);
363    fclose($fp);
366 function phimail_logit($success,$text,$pid=0,$event="direct-message-check") {
367    newEvent($event,"phimail-service",0,$success,$text,$pid);
371  * Read a blob of data into a local temporary file
372  * @param $len number of bytes to read
373  * @return the temp filename, or FALSE if failure
374  */
375 function phimail_read_blob($fp,$len) {
377    $fpath=$GLOBALS['temporary_files_dir'];
378    if(!@file_exists($fpath)) {
379      return FALSE;
380    }
381    $name = uniqid("direct-");
382    $fn = $fpath . "/" . $name . ".dat";
383    $dup = 1;
384    while(file_exists($fn)) {
385      $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
386    }
388    $ff = @fopen ($fn, "w");
389    if(!$ff) return FALSE;
391    $bytes_left=$len;
392    $chunk_size=1024;
393    while (!feof($fp) && $bytes_left>0) {
394       if ($bytes_left < $chunk_size ) $chunk_size = $bytes_left;
395       $chunk = fread($fp,$chunk_size);
396       if($chunk===FALSE || @fwrite($ff,$chunk)===FALSE) {
397          @fclose($ff);
398          @unlink($fn);
399          return FALSE;
400       }
401       $bytes_left -= strlen($chunk);
402    }
403    @fclose($ff);
404    return($fn);
408  * Return a suitable filename extension based on MIME-type
409  * (very limited, default is .dat)
410  */
411 function phimail_extension($mime) {
412   $m=explode("/",$mime);
413   switch($mime) {
414         case 'text/plain': 
415                 return (".txt");
416         default:
417   }
418   switch($m[1]) {
419         case 'html':
420         case 'xml':
421         case 'pdf':
422                 return (".".$m[1]);
423         default:
424                 return (".dat");
425   }
428 function phimail_service_userID($name='phimail-service') {
429    $sql = "SELECT id FROM users WHERE username=?";
430    if (($r = sqlStatementNoLog($sql,array($name))) === FALSE ||
431        ($u = sqlFetchArray($r)) === FALSE) {
432       $user=1; //default if we don't have a service user
433    } else {
434       $user = $u['id'];
435    }
436    return ($user);
441  * Registers an attachment or non-text message file using the existing Document structure
442  * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
443  */
444 function phimail_store($name,$mime_type,$fn) {
446     // Collect phimail user id
447     $user = phimail_service_userID();
449     // Import the document
450     $return = addNewDocument($name,$mime_type,$fn,0,filesize($fn),$user,'direct');
452     // Remove the temporary file
453     @unlink($fn);
455     // Return the result
456     return $return;
460  * Send an error notification or other alert to the notification address specified in globals.
461  * (notification email function modified from interface/drugs/dispense_drug.php)
462  * @return true if notification successfully sent, false otherwise
463  */
464 function phimail_notify($subj,$body) {
465   $recipient = $GLOBALS['practice_return_email_path'];
466   if (empty($recipient)) return false;
467   $mail = new PHPMailer();
468   $mail->SetLanguage("en", $GLOBALS['fileroot'] . "/library/" );
469   $mail->From = $recipient;
470   $mail->FromName = 'phiMail Gateway';
471   $mail->isMail();
472   $mail->Host = "localhost";
473   $mail->Mailer = "mail";
474   $mail->Body = $body;
475   $mail->Subject = $subject;
476   $mail->AddAddress($recipient);
477   return ($mail->Send());