dev ldap fixes (#3914)
[openemr.git] / library / direct_message_check.inc
blobea2c1fd46cd6152917d731e528dccaa77f31db3f
1 <?php
3 /**
4  * Background receive function for phiMail Direct Messaging service.
5  *
6  * This script is called by the background service manager
7  * at /library/ajax/execute_background_services.php
8  *
9  * Copyright (C) 2013 EMR Direct <http://www.emrdirect.com/>
10  *
11  * LICENSE: This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://opensource.org/licenses/gpl-license.php>;.
21  *
22  * @package OpenEMR
23  * @author  EMR Direct <http://www.emrdirect.com/>
24  * @link    http://www.open-emr.org
25  */
27 require_once(dirname(__FILE__) . "/pnotes.inc");
28 require_once(dirname(__FILE__) . "/documents.php");
29 require_once(dirname(__FILE__) . "/gprelations.inc.php");
31 use OpenEMR\Common\Crypto\CryptoGen;
32 use OpenEMR\Common\Logging\EventAuditLogger;
33 use PHPMailer\PHPMailer\PHPMailer;
35 /**
36  * Connect to a phiMail Direct Messaging server
37  */
39 function phimail_connect(&$phimail_error)
42     if ($GLOBALS['phimail_enable'] == false) {
43         $phimail_error = 'C1';
44         return false; //for safety
45     }
47     $phimail_server = @parse_url($GLOBALS['phimail_server_address']);
48     $phimail_username = $GLOBALS['phimail_username'];
49     $cryptoGen = new CryptoGen();
50     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
51     $phimail_cafile = dirname(__FILE__) . '/../sites/' . $_SESSION['site_id']
52       . '/documents/phimail_server_pem/phimail_server.pem';
53     if (!file_exists($phimail_cafile)) {
54         $phimail_cafile = '';
55     }
57     $phimail_secure = true;
58     switch ($phimail_server['scheme']) {
59         case "tcp":
60         case "http":
61             $server = "tcp://" . $phimail_server['host'];
62               $phimail_secure = false;
63             break;
64         case "https":
65             $server = "ssl://" . $phimail_server['host']
66                . ':' . $phimail_server['port'];
67             break;
68         case "ssl":
69         case "sslv3":
70         case "tls":
71             $server = $GLOBALS['phimail_server_address'];
72             break;
73         default:
74             $phimail_error = 'C2';
75             return false;
76     }
78     if ($phimail_secure) {
79         $context = stream_context_create();
80         if (
81             $phimail_cafile != '' &&
82             (!stream_context_set_option($context, 'ssl', 'verify_peer', true) ||
83              !stream_context_set_option($context, 'ssl', 'cafile', $phimail_cafile))
84         ) {
85             $phimail_error = 'C3';
86             return false;
87         }
89         $socket_tries = 0;
90         $fp = false;
91         while ($socket_tries < 3 && !$fp) {
92             $socket_tries++;
93             $fp = @stream_socket_client(
94                 $server,
95                 $err1,
96                 $err2,
97                 10,
98                 STREAM_CLIENT_CONNECT,
99                 $context
100             );
101         }
103         if (!$fp) {
104             if ($err1 == '111') {
105                 $err2 = xl('Server may be offline');
106             }
108             if ($err2 == '') {
109                 $err2 = xl('Connection error');
110             }
112             $phimail_error = "C4 $err1 ($err2)";
113         }
114     } else {
115         $fp = @fsockopen($server, $phimail_server['port']);
116     }
118     return $fp;
122  * Connect to a phiMail Direct Messaging server and check for any incoming status
123  * messages related to previously transmitted messages or any new messages received.
124  */
126 function phimail_check()
128     $fp = phimail_connect($err);
129     if ($fp === false) {
130         phimail_logit(0, xl('could not connect to server') . ' ' . $err);
131         return;
132     }
134     $phimail_username = $GLOBALS['phimail_username'];
135     $cryptoGen = new CryptoGen();
136     $phimail_password = $cryptoGen->decryptStandard($GLOBALS['phimail_password']);
138     $ret = phimail_write_expect_OK($fp, "AUTH $phimail_username $phimail_password\n");
139     if ($ret !== true) {
140         phimail_logit(0, "authentication error " . $ret);
141         return;
142     }
144     if (!($notifyUsername = $GLOBALS['phimail_notify'])) {
145         $notifyUsername = 'admin'; //fallback
146     }
148     while (1) {
149         phimail_write($fp, "CHECK\n");
150         $ret = fgets($fp, 512);
152         if ($ret == "NONE\n") { //nothing to process
153             phimail_close($fp);
154             phimail_logit(1, "message check completed");
155             return;
156         } elseif (substr($ret, 0, 6) == "STATUS") {
157          //Format STATUS message-id status-code [additional-information]
158             $val = explode(" ", trim($ret), 4);
159             $sql = 'SELECT * from direct_message_log WHERE msg_id = ?';
160             $res = sqlStatementNoLog($sql, array($val[1]));
161             if ($res === false) { //database problem
162                 phimail_close($fp);
163                 phimail_logit(0, "database problem");
164                 return;
165             }
167             if (($msg = sqlFetchArray($res)) === false) {
168                 //no match, so log it and move on (should never happen)
169                 phimail_logit(0, "NO MATCH: " . $ret);
170                 $ret = phimail_write_expect_OK($fp, "OK\n");
171                 if ($ret !== true) {
172                     return;
173                 } else {
174                     continue;
175                 }
176             }
178             //if we get here, $msg contains the matching outgoing message record
179             if ($val[2] == 'failed') {
180                 $success = 0;
181                 $status = 'F';
182             } elseif ($val[2] == 'dispatched') {
183                 $success = 1;
184                 $status = 'D';
185             } else {
186             //unrecognized status, log it and move on (should never happen)
187                 $ret = "UNKNOWN STATUS: " . $ret;
188                 $success = 0;
189                 $status = 'U';
190             }
192             phimail_logit($success, $ret, $msg['patient_id']);
194             if (!isset($val[3])) {
195                 $val[3] = "";
196             }
198             $sql = "UPDATE direct_message_log SET status=?, status_ts=NOW(), status_info=? WHERE msg_type='S' AND msg_id=?";
199             $res = sqlStatementNoLog($sql, array($status,$val[3],$val[1]));
200             if ($res === false) { //database problem
201                 phimail_close($fp);
202                 phimail_logit(0, "database problem updating: " . $val[1]);
203                 return;
204             }
206             if (!$success) {
207                    //notify local user of failure
208                    $sql = "SELECT username FROM users WHERE id = ?";
209                    $res2 = sqlStatementNoLog($sql, array($msg['user_id']));
210                    $fail_user = ($res2 === false || ($user_row = sqlFetchArray($res2)) === false) ?
211                 xl('unknown (see log)') : $user_row['username'];
212                    $fail_notice = xl('Sent by:') . ' ' . $fail_user . '(' . $msg['user_id'] . ') ' . xl('on') . ' ' . $msg['create_ts']
213                 . "\n" . xl('Sent to:') . ' ' . $msg['recipient'] . "\n" . xl('Server message:') . ' ' . $ret;
214                 phimail_notify(xl('Direct Messaging Send Failure.'), $fail_notice);
215                    $pnote_id = addPnote(
216                        $msg['patient_id'],
217                        xl("FAILURE NOTICE: Direct Message Send Failed.") . "\n\n$fail_notice\n",
218                        0,
219                        1,
220                        "Unassigned",
221                        $notifyUsername,
222                        "",
223                        "New",
224                        "phimail-service"
225                    );
226             }
228          //done with this status message
229             $ret = phimail_write_expect_OK($fp, "OK\n");
230             if ($ret !== true) {
231                    phimail_close($fp);
232                 return;
233             }
234         } elseif (substr($ret, 0, 4) == "MAIL") {
235             $val = explode(" ", trim($ret), 5); // MAIL recipient sender #attachments msg-id
236             $recipient = $val[1];
237             $sender = $val[2];
238             $att = (int)$val[3];
239             $msg_id = $val[4];
241             //request main message
242             $ret2 = phimail_write_expect_OK($fp, "SHOW 0\n");
243             if ($ret2 !== true) {
244                 phimail_close($fp);
245                 return;
246             }
248         //get message headers
249             $hdrs = "";
250             while (($next_hdr = fgets($fp, 1024)) != "\n") {
251                 $hdrs .= $next_hdr;
252             }
254             $mime_type = fgets($fp, 512);
255             $mime_info = explode(";", $mime_type);
256             $mime_type_main = strtolower($mime_info[0]);
258         //get main message body
259             $body_len = fgets($fp, 256);
260             $body = phimail_read_blob($fp, $body_len);
261             if ($body === false) {
262                    phimail_close($fp);
263                    return;
264             }
266             $att2 = fgets($fp, 256);
267             if ($att2 != $att) { //safety for mismatch on attachments
268                    phimail_close($fp);
269                    return;
270             }
272         //get attachment info
273             if ($att > 0) {
274                 for ($attnum = 0; $attnum < $att; $attnum++) {
275                     if (
276                         ($attinfo[$attnum]['name'] = fgets($fp, 1024)) === false
277                         || ($attinfo[$attnum]['mime'] = fgets($fp, 1024)) === false
278                         || ($attinfo[$attnum]['desc'] = fgets($fp, 1024)) === false
279                     ) {
280                              phimail_close($fp);
281                              return;
282                     }
283                 }
284             }
286         //main part gets stored as document if not plain text content
287         //(if plain text it will be the body of the final pnote)
288             $all_doc_ids = array();
289             $doc_id = 0;
290             $att_detail = "";
291             if ($mime_type_main != "text/plain") {
292                    $name = uniqid("dm-message-") . phimail_extension($mime_type_main);
293                    $doc_id = phimail_store($name, $mime_type_main, $body);
294                 if (!$doc_id) {
295                     phimail_close($fp);
296                     return;
297                 }
299                    $idnum = $doc_id['doc_id'];
300                    $all_doc_ids[] = $idnum;
301                    $url = $doc_id['url'];
302                    $url = substr($url, strrpos($url, "/") + 1);
303                    $att_detail = "\n" . xl("Document") . " $idnum (\"$url\"; $mime_type_main; " .
304                   filesize($body) . " bytes) Main message body";
305             }
307         //download and store attachments
308             for ($attnum = 0; $attnum < $att; $attnum++) {
309                 $ret2 = phimail_write_expect_OK($fp, "SHOW " . ($attnum + 1) . "\n");
310                 if ($ret2 !== true) {
311                     phimail_close($fp);
312                     return;
313                 }
315                //we can ignore next two lines (repeat of name and mime-type)
316                 if (($a1 = fgets($fp, 512)) === false || ($a2 = fgets($fp, 512)) === false) {
317                     phimail_close($fp);
318                     return;
319                 }
321                 $att_len = fgets($fp, 256); //length of file
322                 $attdata = phimail_read_blob($fp, $att_len);
323                 if ($attdata === false) {
324                     phimail_close($fp);
325                     return;
326                 }
328                 $attinfo[$attnum]['file'] = $attdata;
330                 $req_name = trim($attinfo[$attnum]['name']);
331                 $req_name = (empty($req_name) ? $attdata : "dm-") . $req_name;
332                 $attinfo[$attnum]['mime'] = explode(";", trim($attinfo[$attnum]['mime']));
333                 $attmime = strtolower($attinfo[$attnum]['mime'][0]);
334                 $att_doc_id = phimail_store($req_name, $attmime, $attdata);
335                 if (!$att_doc_id) {
336                     phimail_close($fp);
337                     return;
338                 }
340                 $attinfo[$attnum]['doc_id'] = $att_doc_id;
341                 $idnum = $att_doc_id['doc_id'];
342                 $all_doc_ids[] = $idnum;
343                 $url = $att_doc_id['url'];
344                 $url = substr($url, strrpos($url, "/") + 1);
345                 $att_detail = $att_detail . "\n" . xl("Document") . " $idnum (\"$url\"; $attmime; " .
346                 filesize($attdata) . " bytes) " . trim($attinfo[$attnum]['desc']);
347             }
349             if ($att_detail != "") {
350                 $att_detail = "\n\n" . xl("The following documents were attached to this Direct message:") . $att_detail;
351             }
353             $ret2 = phimail_write_expect_OK($fp, "DONE\n"); //we'll check for failure after logging.
355         //logging only after succesful download, storage, and acknowledgement of message
356             $sql = "INSERT INTO direct_message_log (msg_type,msg_id,sender,recipient,status,status_ts,user_id) " .
357             "VALUES ('R', ?, ?, ?, 'R', NOW(), ?)";
358             $res = sqlStatementNoLog($sql, array($msg_id,$sender,$recipient,phimail_service_userID()));
360             phimail_logit(1, $ret);
362         //alert appointed user about new message
363             switch ($mime_type_main) {
364                 case "text/plain":
365                     $body_text = @file_get_contents($body); //this was not uploaded as a document
366                     unlink($body);
367                     $pnote_id = addPnote(
368                         0,
369                         xl("Direct Message Received.") . "\n$hdrs\n$body_text$att_detail",
370                         0,
371                         1,
372                         "Unassigned",
373                         $notifyUsername,
374                         "",
375                         "New",
376                         "phimail-service"
377                     );
378                     break;
380                 default:
381                     $note = xl("Direct Message Received.") . "\n$hdrs\n"
382                     . xl("Message content is not plain text so it has been stored as a document.") . $att_detail;
383                     $pnote_id = addPnote(0, $note, 0, 1, "Unassigned", $notifyUsername, "", "New", "phimail-service");
384                     break;
385             }
387             foreach ($all_doc_ids as $doc_id) {
388                 setGpRelation(1, $doc_id, 6, $pnote_id);
389             }
391             if ($ret2 !== true) {
392                    phimail_close();
393                    return;
394             }
395         } else { //unrecognized or FAIL response
396             phimail_logit(0, "problem checking messages " . $ret);
397             phimail_close($fp);
398             return;
399         }
400     }
404  * Helper functions
405  */
406 function phimail_write($fp, $text)
408     fwrite($fp, $text);
409     fflush($fp);
412 function phimail_write_expect_OK($fp, $text)
414     phimail_write($fp, $text);
415     $ret = fgets($fp, 256);
416     if ($ret != "OK\n") { //unexpected error
417         phimail_close($fp);
418         return $ret;
419     }
421     return true;
424 function phimail_close($fp)
426     fwrite($fp, "BYE\n");
427     fflush($fp);
428     fclose($fp);
431 function phimail_logit($success, $text, $pid = 0, $event = "direct-message-check")
433     EventAuditLogger::instance()->newEvent($event, "phimail-service", 0, $success, $text, $pid);
437  * Read a blob of data into a local temporary file
438  * @param $len number of bytes to read
439  * @return the temp filename, or FALSE if failure
440  */
441 function phimail_read_blob($fp, $len)
444     $fpath = $GLOBALS['temporary_files_dir'];
445     if (!@file_exists($fpath)) {
446         return false;
447     }
449     $name = uniqid("direct-");
450     $fn = $fpath . "/" . $name . ".dat";
451     $dup = 1;
452     while (file_exists($fn)) {
453         $fn = $fpath . "/" . $name . "." . $dup++ . ".dat";
454     }
456     $ff = @fopen($fn, "w");
457     if (!$ff) {
458         return false;
459     }
461     $bytes_left = $len;
462     $chunk_size = 1024;
463     while (!feof($fp) && $bytes_left > 0) {
464         if ($bytes_left < $chunk_size) {
465             $chunk_size = $bytes_left;
466         }
468         $chunk = fread($fp, $chunk_size);
469         if ($chunk === false || @fwrite($ff, $chunk) === false) {
470             @fclose($ff);
471             @unlink($fn);
472             return false;
473         }
475         $bytes_left -= strlen($chunk);
476     }
478     @fclose($ff);
479     return($fn);
483  * Return a suitable filename extension based on MIME-type
484  * (very limited, default is .dat)
485  */
486 function phimail_extension($mime)
488     $m = explode("/", $mime);
489     switch ($mime) {
490         case 'text/plain':
491             return (".txt");
492         default:
493     }
495     switch ($m[1]) {
496         case 'html':
497         case 'xml':
498         case 'pdf':
499             return ("." . $m[1]);
500         default:
501             return (".dat");
502     }
505 function phimail_service_userID($name = 'phimail-service')
507     $sql = "SELECT id FROM users WHERE username=?";
508     if (
509         ($r = sqlStatementNoLog($sql, array($name))) === false ||
510         ($u = sqlFetchArray($r)) === false
511     ) {
512         $user = 1; //default if we don't have a service user
513     } else {
514         $user = $u['id'];
515     }
517     return ($user);
522  * Registers an attachment or non-text message file using the existing Document structure
523  * @return Array(doc_id,URL) of the file as stored in documents table, false = failure
524  */
525 function phimail_store($name, $mime_type, $fn)
528     // Collect phimail user id
529     $user = phimail_service_userID();
531     // Import the document
532     $return = addNewDocument($name, $mime_type, $fn, 0, filesize($fn), $user, 'direct');
534     // Remove the temporary file
535     @unlink($fn);
537     // Return the result
538     return $return;
542  * Send an error notification or other alert to the notification address specified in globals.
543  * (notification email function modified from interface/drugs/dispense_drug.php)
544  * @return true if notification successfully sent, false otherwise
545  */
546 function phimail_notify($subj, $body)
548     $recipient = $GLOBALS['practice_return_email_path'];
549     if (empty($recipient)) {
550         return false;
551     }
553     $mail = new PHPMailer();
554     $mail->From = $recipient;
555     $mail->FromName = 'phiMail Gateway';
556     $mail->isMail();
557     $mail->Host = "localhost";
558     $mail->Mailer = "mail";
559     $mail->Body = $body;
560     $mail->Subject = $subj;
561     $mail->AddAddress($recipient);
562     return ($mail->Send());