BMLT Root Server
contact.php
Go to the documentation of this file.
1 <?php
2 /***********************************************************************/
3 /** \file contact.php
4 
5  \brief This file is a very simple interface for contacts related to meetings.
6  Only 4 inputs are provided: The meeting ID (an integer), the Service body ID (an integer),
7  the from address (a string), and the message (a string).
8  This comes via GET, not POST.
9 
10  There is never any writing to the database (security). The database is only checked for the contact info.
11 
12  This file makes sure that email contacts are allowed, then does some basic
13  spam-checking. It will send an email to whatever contact is associated
14  with a meeting.
15 
16  In order to mitigate spam use, we look for:
17  meeting_id=<INTEGER> The ID of the meeting being referenced.
18  service_body_id=<INTEGER> The ID of the Service body associated with the meeting.
19  from_address=<STRING> A validly-formatted email address to be used as the "FROM:" line.
20  message=<STRING> The message text.
21 
22  All of these must be supplied, and the Service body ID needs to jive with the one associated with the meeting ID.
23  That's not something that spammers will be easily able to determine; especially when you consider how worthless the recipient will be to them.
24 
25  The contacts are tiered in this manner:
26  - If a contact is provided for the meeting itself (email_contact field, or contact_email_1), then that contact is used.
27  - If there are multiple contacts using the default contact structure (contact_email_1, contact_email_2), then we will send to both of them.
28  - If no individual contacts are provided for a meeting, then we will use the email contact for the Service body for that meeting.
29  - If no Service body contact is provided, then the email will be sent to the Server Administrator.
30  - If no email contacts are provided anywhere, the email will not be sent.
31 
32  A simple integer response is returned. 1, if the email was successfully sent, 0 if email contacts are disallowed, -1, if no email contacts are available for this meeting, -2, if the from email address is invalid, -3 if the email was flagged as spam, and -4 if there was some error encountered while sending.
33 
34  If the meeting ID is 0 (or there is no input), then the message text and from are ignored, and this is considered a test to see if email is supported. A response of 1 is yes, 0, otherwise.
35 
36  This won't work if the $g_enable_email_contact = TRUE; line is not in the auto-config.inc.php file.
37 
38  If you want to include all the Service body contacts (multiple recipients are possible), then set $include_service_body_admin_on_emails = TRUE; in the config file.
39 
40  This file is part of the Basic Meeting List Toolbox (BMLT).
41 
42  Find out more at: https://bmlt.app
43 
44  BMLT is free software: you can redistribute it and/or modify
45  it under the terms of the MIT License.
46 
47  BMLT is distributed in the hope that it will be useful,
48  but WITHOUT ANY WARRANTY; without even the implied warranty of
49  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
50  MIT License for more details.
51 
52  You should have received a copy of the MIT License along with this code.
53  If not, see <https://opensource.org/licenses/MIT>.
54 */
55 
57 
58 $g_mail_debug = false; ///< Set this to TRUE to output the email as an echo, instead of sending it.
59 
60 if (preg_match("/localhost/", $_SERVER['SERVER_NAME'])) {
61  $g_mail_debug = true; // We always debug for localhost testing.
62 }
63 
64 /***********************************************************************/
65 /** \brief This analyzes an input string for obvious spam signatures.
66  This looks for attempts to insert headers into the From: line.
67 
68  \returns a Boolean. TRUE if the message appears to be spam.
69 */
70 function analyzeFromLine( $inFrom ///< The message from line as a text string.
71  )
72 {
73  $inFrom = strtolower($inFrom);
74 
75  $ret = !((false == strpos($inFrom, "\r")) && (false == strpos($inFrom, "\n")) && (false == strpos($inFrom, ";")) && (false == strpos($inFrom, "to:")) && (false == strpos($inFrom, "cc:")) && (false == strpos($inFrom, "bc:")));
76 
77  return $ret;
78 }
79 
80 /***********************************************************************/
81 /** \brief This analyzes an input string for obvious spam signatures (mostly checking for URLs).
82  This is VERY basic, but it will catch 99% of the usual spam types.
83  Cribbed from here: http://wcetdesigns.com/tutorials/2011/11/30/detect-url-in-string.html
84 
85  \returns a Boolean. TRUE if the message appears to be spam.
86 */
87 function analyzeMessageContent( $inMessage ///< The message as a text string.
88  )
89 {
90  $ret = false;
91  $count = 0;
92 
93  $p = '#^(http(s)?|ftp)://([a-z0-9_-]+.)+([a-z]{2,}){1}((:|/)(.*))?$#';
94 
95  $w = preg_split("/\s+/", $inMessage, -1, PREG_SPLIT_NO_EMPTY);
96 
97  foreach ($w as $s) {
98  if (preg_match($p, $s)) {
99  if (1 < $count++) { // More than 2 is spam.
100  $ret = true;
101  break;
102  }
103  }
104  }
105 
106  return $ret;
107 }
108 
109 /***********************************************************************/
110 /** \brief This analyzes email address (or a list of them), and returns TRUE if they are OK (as formatted).
111  \returns a Boolean. TRUE if the emails are OK.
112 */
113 function isValidEmailAddress( $in_test_address ///< The email address (or a list or array) to be checked.
114  )
115 {
116  $valid = false;
117  if (isset($in_test_address)) {
118  if (!is_array($in_test_address)) {
119  $in_test_address = explode(",", $in_test_address); // See if we have a list.
120  }
121 
122  // Start off optimistic.
123  $valid = true;
124 
125  // If we have more than one address, we iterate through each one.
126  foreach ($in_test_address as $addr_elem) {
127  // This splits any name/address pair (ex: "Jack Schidt" <jsh@spaz.com>)
128  $addr_temp = preg_split("/ </", strtolower($addr_elem));
129 
130  if (count($addr_temp) > 1) { // We also want to trim off address brackets.
131  $addr_elem = trim($addr_temp[1], "<>");
132  } else {
133  $addr_elem = trim($addr_temp[0], "<>");
134  }
135 
136  // Test for valid email address.
137  $regexp = "/^([a-z0-9\_\.\-]+?)@([a-z0-9\-]+)(\.[a-z0-9\-]+)*(\.[a-z]{2,6})$/";
138 
139  if (!preg_match($regexp, strtolower($addr_elem))) {
140  $valid = false;
141  break;
142  }
143  }
144  }
145 
146  return $valid;
147 }
148 
149 /***********************************************************************/
150 /** \brief This simplifies one single email address, by stripping away cruft.
151 
152  \returns a "cleaned" email address.
153 */
154 function simplifyEmailAddress($in_orig_address)
155 {
156  $addr_temp = preg_split("/ </", $in_orig_address);
157 
158  if (count($addr_temp) > 1) { // We also want to trim off address brackets.
159  $addr_elem = trim($addr_temp[1], " <>");
160  } else {
161  $addr_elem = trim($addr_temp[0], " <>");
162  }
163 
164  if (isValidEmailAddress($addr_elem)) {
165  return $addr_elem;
166  }
167 
168  return "";
169 }
170 
171 /***********************************************************************/
172 /** \brief This actually sends the email.
173 
174  \returns a Boolean. TRUE if successful.
175 */
176 function sendEMail(
177  $in_to_address,
178  $in_from_address,
179  $in_subject = "<No Subject>",
180  $in_body = "<No Body Text>"
181 ) {
182  $success = false;
183 
184  $addlParam = $in_from_address;
185 
186  if ($addlParam) {
187  $addlParam = "-f $addlParam";
188  }
189 
190  // The body is not sent in the plain text portion of the
191  // mail() function. Instead, it is put in the headers.
192 
193  $headers = "";
194  $headers .= "From: $in_from_address\n";
195  // Make sure our endlines are correct, and unescape any escaped quotes.
196  $in_body = preg_replace("/\r\n/", "\n", $in_body);
197  $in_body = stripslashes(preg_replace("/\r/", "\n", $in_body));
198  $in_body = stripslashes(preg_replace("/\n+/", "\n", $in_body));
199  $subject = stripslashes($in_subject);
200 
201  // Headers precede the body.
202  $headers .= $in_body;
203 
204  global $g_mail_debug;
205 
206  if ($g_mail_debug) {
207  $disp = "To: ".htmlspecialchars($in_to_address)."\n";
208  $disp .= "Subject: ".htmlspecialchars($subject)."\n";
209  $disp .= htmlspecialchars($headers);
210  echo "<pre>$disp</pre>";
211  $success = true;
212  } else {
213  // The "Message" parameter is blank, because we are using
214  // the headers to send the body. Bit more technical, but
215  // more effective.
216  $success = mail($in_to_address, $subject, "", $headers, $addlParam);
217  }
218 
219  return $success;
220 }
221 
222 /***********************************************************************/
223 /* MAIN CONTEXT */
224 /***********************************************************************/
225 
226 $ret = 0; // We start off assuming that email contact is disabled.
228 
229 if (isset($_GET['meeting_id'])) {
230  $meeting_id = intval($_GET['meeting_id']);
231 }
232 
233 // If this is just a test, we respond with the capability.
234 if (0 == $meeting_id) {
235  if (file_exists(dirname(dirname(dirname(__FILE__))).'/auto-config.inc.php')) {
236  define('BMLT_EXEC', 1);
237  // We check to make sure that we are supporting the capability.
238  require_once(dirname(dirname(dirname(__FILE__))).'/auto-config.inc.php');
239  $ret = $g_enable_email_contact ? 1 : 0;
240  }
241 } else {
242  if (isset($_GET['service_body_id'])) {
243  $service_body_id = intval($_GET['service_body_id']);
244  if (!$service_body_id) {
245  $service_body_id = intval($_GET['service_body_bigint']);
246  }
247  }
248 
249  if (isset($_GET['message'])) {
250  $message_text = $_GET['message'];
251  }
252 
253  if (isset($_GET['from_address'])) {
254  $from_address = $_GET['from_address'];
255  }
256 
257  $isspam = false;
258 
259  foreach ($_GET as $key => $value) {
260  $key = strtolower(strval($key));
261 
262  // Any attempt to sneak in extra fields automatically marks this as spam.
263  if (($key != 'meeting_id') && ($key != 'service_body_id') && ($key != 'service_body_bigint') && ($key != 'from_address') && ($key != 'message')) {
264  if ($g_mail_debug) {
265  echo ( "$key is invalid<br />" );
266  }
267 
268  $isspam = true;
269  break;
270  }
271  }
272 
273  if (!$isspam) {
274  $isspam = isset($from_address) ? analyzeFromLine($from_address) : false;
275 
276  if (!$isspam) {
277  if (isset($from_address) ? isValidEmailAddress($from_address) : true) {
278  $isspam = isset($message_text) ? analyzeMessageContent($message_text) : false;
279 
280  if (!$isspam) {
281  if (file_exists(dirname(dirname(dirname(__FILE__))).'/auto-config.inc.php')) {
282  define('BMLT_EXEC', 1);
283 
284  // We check to make sure that we are supporting the capability.
285  require_once(dirname(dirname(dirname(__FILE__))).'/auto-config.inc.php');
286 
287  if ($g_enable_email_contact && $meeting_id) {
288  require_once(dirname(dirname(__FILE__)).'/server/c_comdef_server.class.php');
290 
291  if ($server instanceof c_comdef_server) {
292  $email_contacts = array(); // This will contain our meeting email contact list.
293 
294  $meeting_object = c_comdef_server::GetOneMeeting($meeting_id);
295 
296  if ($meeting_object instanceof c_comdef_meeting) { // We must have a valid meeting.
297  // This is a pretty good spamtrap. The submission must have both the meeting ID and the valid Service body ID.
298  if (isset($service_body_id) && $service_body_id && ($service_body_id == $meeting_object->GetServiceBodyID())) {
299  if ($meeting_object->GetEmailContact()) { // The direct contact is placed first in the queue.
300  $email = simplifyEmailAddress($meeting_object->GetEmailContact());
301 
302  if ($email) {
303  $email_contacts[] = $email;
304  }
305  }
306 
307  // We now walk up the hierarchy, and add contacts as we find them. We use the emails set in the Service body admin, not individual accounts.
308 
309  $service_body = $meeting_object->GetServiceBodyObj();
310 
311  do {
312  if ($service_body && $service_body->GetContactEmail()) {
313  $email = simplifyEmailAddress($service_body->GetContactEmail());
314 
315  if ($email && !in_array($email, $email_contacts)) { // Make sure we don't already have it.
316  $email_contacts[] = $email;
317  }
318  }
319 
320  // We don't recurse if we aren't supposed to
321  $service_body = isset($include_every_admin_on_emails) && $include_every_admin_on_emails ? $service_body->GetOwnerIDObject() : null;
322  } while ($service_body);
323 
324  // The one exception is the Server Administrator, and we get that email from the individual account.
325 
326  if (isset($include_every_admin_on_emails) && $include_every_admin_on_emails) { // The Server admin is not involved unless we are cascading.
327  $server_admin_user = c_comdef_server::GetUserByIDObj(1);
328 
329  if ($server_admin_user && $server_admin_user->GetEmailAddress()) {
330  $email = simplifyEmailAddress($server_admin_user->GetEmailAddress());
331 
332  if ($email) {
333  $email_contacts[] = $email;
334  }
335  }
336  }
337 
338  // At this point, we have one or more email addresses in our $email_contacts array. It's possible that the Server Admin may be the only contact.
339 
340  if (count($email_contacts)) { // Make sure that we have something.
341  $to_line = null;
342 
343  if ((1 < count($email_contacts)) && $include_service_body_admin_on_emails) { // See if we are including anyone else.
344  $to_line = implode(",", $email_contacts);
345  } else {
346  $to_line = $email_contacts[0]; // Otherwise, just the primary contact.
347  }
348 
349  if ($to_line) { // Assuming all went well, we have a nice to line, here.
350  if (isValidEmailAddress($to_line)) { // Make sure our email addresses are valid.
352 
353  $subject = sprintf($local_strings['email_contact_strings']['meeting_contact_form_subject_format'], $meeting_object->GetLocalName());
354  $dirn = dirname(( dirname($_SERVER['PHP_SELF'])));
355  if ('/' == $dirn) {
356  $dirn = '';
357  }
358  $start_time = explode(':', $meeting_object->GetMeetingDataValue('start_time'));
359  unset($start_time[2]);
360  $start_time = implode(':', $start_time);
361  $root_dirname = 'http://'.$_SERVER['SERVER_NAME'].(($_SERVER['SERVER_PORT'] != 80) ? ':'.$_SERVER['SERVER_PORT'] : '').$dirn;
362  $url1 = $root_dirname.'/?single_meeting_id='.$meeting_id;
363  $url2 = $root_dirname.'/?edit_meeting='.$meeting_id;
364  $body = sprintf(
365  $local_strings['email_contact_strings']['meeting_contact_message_format'],
366  $message_text,
367  $meeting_object->GetLocalName(),
368  $start_time,
369  $local_strings['weekdays'][$meeting_object->GetMeetingDataValue('weekday_tinyint')],
370  $url1,
371  $url2
372  );
373  if (sendEMail($to_line, $from_address, $subject, $body)) {
374  $ret = 1;
375  } else {
376  $ret = -4;
377  }
378  } else {
379  $ret = -1;
380  }
381  } else {
382  $ret = -1; // Should never happen.
383  }
384  } else {
385  $ret = -1;
386  }
387  } else {
388  if ($g_mail_debug) {
389  die("Content Considered Spam (Service body check failed)");
390  }
391 
392  $ret = -3;
393  }
394  }
395  }
396  }
397 
398  // If this is just a test, we respond with the capability.
399  if (0 == $meeting_id) {
400  $ret = $g_enable_email_contact ? 1 : 0;
401  }
402  } else {
403  die("SERVER NOT INITIALIZED");
404  }
405  } else {
406  if ($g_mail_debug) {
407  die("Content Considered Spam");
408  }
409 
410  $ret = -3;
411  }
412  } else {
413  if ($g_mail_debug) {
414  die("From Address Invalid");
415  }
416 
417  $ret = -2;
418  }
419  } else {
420  if ($g_mail_debug) {
421  die("From Address Considered Spam");
422  }
423 
424  $ret = -3;
425  }
426  } else {
427  if ($g_mail_debug) {
428  die("Extra parameters (considered spam)");
429  }
430 
431  $ret = -3;
432  }
433 }
434 
435 echo intval($ret);
static GetLocalStrings($in_lang_enum=null)
This gets the appropriate language files, and puts all the the strings into an associative array...
isValidEmailAddress($in_test_address)
This analyzes email address (or a list of them), and returns TRUE if they are OK (as formatted)...
Definition: contact.php:113
global $g_mail_debug
Set this to TRUE to output the email as an echo, instead of sending it.
Definition: contact.php:56
A class to hold a single meeting object.
$local_strings
Definition: index.php:6
if(preg_match("/localhost/", $_SERVER['SERVER_NAME'])) analyzeFromLine($inFrom)
This analyzes an input string for obvious spam signatures. This looks for attempts to insert headers ...
Definition: contact.php:70
analyzeMessageContent($inMessage)
This analyzes an input string for obvious spam signatures (mostly checking for URLs). This is VERY basic, but it will catch 99% of the usual spam types. Cribbed from here: http://wcetdesigns.com/tutorials/2011/11/30/detect-url-in-string.html.
Definition: contact.php:87
$ret
Definition: contact.php:226
function sprintf()
Definition: installer.js:873
$meeting_id
Definition: contact.php:227
sendEMail($in_to_address, $in_from_address, $in_subject="<No Subject>", $in_body="<No Body Text>")
This actually sends the email.
Definition: contact.php:176
simplifyEmailAddress($in_orig_address)
This simplifies one single email address, by stripping away cruft.
Definition: contact.php:154
static GetUserByIDObj($in_user_id_bigint)
Get the object for a single user, given an ID.
$server
Definition: GetLangs.php:25
static MakeServer()
This is the factory for the server instantiation. It makes sure that only one instance exists...
This class is the main server class. It instantiates a PDO database object, and is the starting point...
static GetOneMeeting($in_id_bigint, $test_only=false)
Given an ID for a meeting, it returns one instance.
$_GET['switcher']