/[drupal]/contributions/modules/gotcha/gotcha.module
ViewVC logotype

Contents of /contributions/modules/gotcha/gotcha.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.14 - (show annotations) (download) (as text)
Tue Sep 29 09:03:32 2009 UTC (8 weeks ago) by hutch
Branch: MAIN
CVS Tags: HEAD
Changes since 1.13: +51 -67 lines
File MIME type: text/x-php
rolling back HEAD to 5.x
1 <?php
2 // $Id: gotcha.module,v 1.11 2009/06/08 16:15:56 hutch Exp $
3
4 /**
5 * @file
6 * Attempts to prevent spam bots from using the contact form.
7 */
8
9 /**
10 * TODO:
11 * - build an IP address blacklist?
12 * - spam_delete_contact?
13 * - check IP address against current list (and Spam's list)?
14 * - make 'contact_mail_user' work
15 */
16
17 // ******************************
18 // ***** *****
19 // ***** "Overhead" Section *****
20 // ***** *****
21 // ******************************
22
23 /**
24 * Implementation of hook_help().
25 * The first case adds a little help text.
26 * The second case adds some submission guidelines on the create content page.
27 */
28 function gotcha_help($path, $args=NULL) {
29 switch ($path) {
30 case 'admin/help#gotcha':
31 return '<p>'. t('The gotcha module adds a field to the contact form which is hidden by the CSS. Spam bots will see the field and may fill it in, thus tipping us off to a spam bot.') .'</p>';
32
33 case 'admin/logs/gotcha':
34 return '<p>'. t('This is a list of all the Contact messages that have been logged. You are currently logging %logged messages. Settings for this list can be found on the <a href="@link">Contact Form settings page</a>.',
35 array('%logged' => variable_get('gotcha_log_email', 0) ? 'all' : 'only suspect',
36 '@link' => url('admin/build/contact/settings'),
37 )) .'</p>';
38 }
39 }
40
41 /**
42 * Implementation of hook_menu().
43 * The only menu items for this module is to list the emails that were intercepted.
44 * It will be under the admin >> logs section of the Administer menu.
45 */
46 function gotcha_menu($may_cache) {
47 $items = array();
48
49 if ($may_cache) {
50 $items[] = array(
51 'path' => 'admin/logs/gotcha',
52 'title' => t('Gotcha list'),
53 'description' => t('Show emails intercepted by the Gotcha module.'),
54 'callback' => 'gotcha_list',
55 'access' => user_access('access administration pages'),
56 );
57 }
58 else {
59 drupal_add_css(drupal_get_path('module', 'gotcha') .'/gotcha.css');
60 $items[] = array(
61 'path' => 'gotcha/view',
62 'title' => t('Gotcha view'),
63 'description' => t('Show an individual intercepted by the Gotcha module.'),
64 'callback' => 'gotcha_view',
65 'access' => user_access('access administration pages'),
66 'type' => MENU_CALLBACK,
67 );
68 }
69 return $items;
70 }
71
72 /**
73 * Implementation of hook_enable().
74 * This function logs a message saying the module is enabled and gives the user id.
75 */
76 function gotcha_enable() {
77 global $user;
78 watchdog('Gotcha', t('Gotcha module enabled by ') . theme('username', $user), WATCHDOG_NOTICE, NULL);
79 $go_away = db_result(db_query_range("SELECT nid FROM {node} WHERE title='Gotcha: Go Away!'", 0, 1));
80 if (! $go_away) {
81 // Define the default "Go Away" message page.
82 // http://drupal.org/node/183635, I'm setting the status to "published."
83 $go_page = array(
84 'status' => 1,
85 'sticky' => 0,
86 'promote' => 0,
87 'comment' => 0,
88 'uid' => $user->uid,
89 'type' => 'page',
90 'title' => 'Gotcha: Go Away!',
91 'body' => '<div id="spam_alert"><h1>SPAM ALERT!</h1><h2>This message was flagged as probable spam. It will not be sent.</h2><h2>Your identifying information has been logged and will be reported.</h2><h2>Get a life!</h2><h2>Do something productive and leave me alone!</h2></div>',
92 'format' => 2, /* Full HTML */
93 );
94 $go_away_page = (object)$go_page;
95 $go_away_node = node_save($go_away_page);
96 // Now we have to get the new page's nid.
97 $go_away = db_result(db_query_range("SELECT nid FROM {node} WHERE title='Gotcha: Go Away!'", 0, 1));
98 }
99 variable_set('gotcha_goaway_page', 'node/'. $go_away);
100 drupal_set_message(t('Gotcha "Go Away" page set to !goaway. The settings can be adjusted <a href="@settings">here</a>.', array('!goaway' => $go_away, '@settings' => url('admin/build/contact/settings'))), 'notice');
101 }
102
103 /**
104 * Implementation of hook_disable().
105 * This function logs a message saying the module is disabled and gives the user id.
106 */
107 function gotcha_disable() {
108 global $user;
109 watchdog('Gotcha', t('Gotcha module disabled by ') . theme('username', $user), WATCHDOG_NOTICE, NULL);
110 }
111
112 // *****************************
113 // ***** *****
114 // ***** "Working" Section *****
115 // ***** *****
116 // *****************************
117
118 /**
119 * Implementation of hook_form_alter().
120 * This function:
121 * 1) Adds a field to the top of the contact form and sets it to use a CSS style that makes it hidden.
122 * It then intercepts the submit button to check the field and bypass sending the message if it looks like a spam bot.
123 * 2) Adds a field to the Contact settings form to enable email logging.
124 */
125 function gotcha_form_alter($form_id, &$form) {
126 switch ($form_id) {
127 case 'contact_mail_page':
128 case 'contact_mail_user':
129 // We'll call our hidden field "Subject" so it sounds enticing.
130 // We provide a message for browsers that don't hide the field.
131
132 $fieldname = variable_get('gotcha_form_field', 'Private_Message');
133 $form[$fieldname] = array(
134 '#type' => 'textfield',
135 '#title' => variable_get('gotcha_form_field_label', t('Subject')),
136 '#weight' => variable_get('gotcha_form_weight', "-5"), /* Place it high on the form. */
137 '#prefix' => '<div class="gotcha_covered">',
138 '#suffix' => '</div>',
139 '#description' => t('This field is for computer-generated email only and should not be visible. If you can see it, please ignore it.'),
140 );
141
142 // set it up to catch the "send email" (submit) button.
143 $form['#submit'] = array('gotcha_contact_submit' => array());
144 break;
145
146 // add to the conact admin form
147 case 'contact_admin_settings':
148 $form['gotcha'] = array(
149 '#type' => 'fieldset',
150 '#title' => t('Gotcha settings'),
151 '#weight' => -5,
152 '#collapsible' => TRUE,
153 '#collapsed' => FALSE,
154 '#description' => t('Set up the gotcha spambot trap'),
155 );
156
157 $form['gotcha']['gotcha_log_email'] = array(
158 '#type' => 'checkbox',
159 '#title' => t('Log all email'),
160 '#default_value' => variable_get('gotcha_log_email', FALSE),
161 '#description' => t('If this box is checked, all site-wide Contact email will be logged in the <a href="!log">Gotcha table</a>. If it is not checked, only suspect emails will be logged.', array('!log' => url('admin/logs/gotcha'))),
162 );
163
164 $form['gotcha']['gotcha_goaway_page'] = array(
165 '#type' => 'textfield',
166 '#title' => t('"Go Away" page'),
167 '#default_value' => variable_get('gotcha_goaway_page', 'node'),
168 '#description' => t('This is the path to the page to be displayed when the message has been identified as spam. That message should be tactful, but forceful. Set this field to "node" to disable the message.'),
169 '#size' => 30,
170 );
171
172 $form['gotcha']['gotcha_default_log_age'] = array(
173 '#type' => 'select',
174 '#title' => t('Default Log age'),
175 '#required' => FALSE,
176 '#default_value' => variable_get('gotcha_default_log_age', 30),
177 '#description' => t('How long to keep gotcha logs for'),
178 '#options' => array(
179 '3' => t('three days'),
180 '7' => t('One week'),
181 '14' => 'Two weeks',
182 '30' => 'One month',
183 '91' => 'Three months',
184 '182' => 'Six months',
185 '365' => 'One year'),
186 );
187
188 // logging options
189 $form['gotcha']['log'] = array(
190 '#type' => 'fieldset',
191 '#title' => t('Log display settings'),
192 '#weight' => 1,
193 '#collapsible' => TRUE,
194 '#collapsed' => TRUE,
195 );
196
197 $form['gotcha']['log']['gotcha_max_body'] = array(
198 '#type' => 'textfield',
199 '#title' => t('Maximum body'),
200 '#default_value' => variable_get('gotcha_max_body', 100),
201 '#description' => t("The maximum size of the message's body to show in the logging list."),
202 '#size' => 7,
203 '#maxlength' => 5,
204 );
205
206 $form['gotcha']['log']['gotcha_max_subject'] = array(
207 '#type' => 'textfield',
208 '#title' => t('Maximum subject'),
209 '#default_value' => variable_get('gotcha_max_subject', 15),
210 '#description' => t("The maximum size of the message's subject to show in the logging list."),
211 '#size' => 7,
212 '#maxlength' => 5,
213 );
214
215 $form['gotcha']['log']['gotcha_rows_per_page'] = array(
216 '#type' => 'textfield',
217 '#title' => t('Rows per Page'),
218 '#default_value' => variable_get('gotcha_rows_per_page', 20),
219 '#description' => t("The number of rows to show per page in the logging list."),
220 '#size' => 7,
221 '#maxlength' => 5,
222 );
223
224 $form['gotcha']['log']['gotcha_show_site'] = array(
225 '#type' => 'checkbox',
226 '#title' => t('Show Site Name'),
227 '#default_value' => variable_get('gotcha_show_site', FALSE),
228 '#description' => t("If set, the site's name will show in the logging list. This is useful in a multisite environment with a shared 'Gotcha' log."),
229 );
230
231 // advanced settings
232 $form['gotcha']['advanced'] = array(
233 '#type' => 'fieldset',
234 '#title' => t('Advanced gotcha settings'),
235 '#weight' => 2,
236 '#collapsible' => TRUE,
237 '#collapsed' => TRUE,
238 '#description' => t('Only change these settings if you think a spambot has found a way around.'),
239 );
240
241 $form['gotcha']['advanced']['gotcha_form_field'] = array(
242 '#type' => 'textfield',
243 '#title' => t('Form field name'),
244 '#default_value' => variable_get('gotcha_form_field', 'Private_Message'),
245 '#size' => 15,
246 '#maxlength' => 15,
247 '#required' => TRUE,
248 '#description' => t('Change the name of the hidden form field. Please use with care.'),
249 );
250 $form['gotcha']['advanced']['gotcha_form_field_label'] = array(
251 '#type' => 'textfield',
252 '#title' => t('Form field label'),
253 '#default_value' => variable_get('gotcha_form_field_label', t('Subject')),
254 '#size' => 15,
255 '#maxlength' => 15,
256 '#required' => TRUE,
257 '#description' => t('The label of the hidden form field'),
258 );
259
260 $form['gotcha']['advanced']['gotcha_form_weight'] = array(
261 '#type' => 'select',
262 '#title' => t('Form Weight'),
263 '#required' => FALSE,
264 '#default_value' => variable_get('gotcha_form_weight', '-5'),
265 '#options' => array(
266 '-10' => '-10', '-9' => '-9', '-8' => '-8', '-7' => '-7',
267 '-6' => '-6', '-5' => '-5', '-4' => '-4', '-3' => '-3',
268 '-2' => '-2', '-1' => '-1', '0' => '0', '1' => '1',
269 '2' => '2', '3' => '3', '4' => '4', '5' => '5',
270 '6' => '6', '7' => '7', '8' => '8', '9' => '9',
271 '10' => '10',
272 ),
273 '#description' => t('The weight of the hidden form field'),
274 );
275
276
277 break;
278 }
279 }
280
281 /**
282 * This is the intercept form_submit function.
283 * Here we check the field and bypass sending the message if it looks like a spam bot.
284 * Just in case, we'll log the message and the info.
285 */
286 function gotcha_contact_submit($form_id, $form_values) {
287 global $user;
288
289 $log_all = variable_get('gotcha_log_email', FALSE);
290
291 // Get the entered information.
292 $site_name = variable_get('site_name', 'Drupal');
293 switch ($form_id) {
294 case 'contact_mail_page':
295 $type = 'site';
296 $contact = db_fetch_object(db_query("SELECT * FROM {contact} WHERE cid = %d", $form_values['cid']));
297 $recipients = $contact->recipients;
298 // Apply filter?
299 $sendername = check_plain($form_values['name']);
300 $sendermail = $form_values['mail'];
301 break;
302
303 case 'contact_mail_user':
304 $type = 'user';
305 $account = user_load(array('uid' => arg(1), 'status' => 1));
306
307 // Prepare all fields:
308 $recipients = $account->mail;
309 $sendername = $user->name;
310 $sendermail = $user->mail;
311 break;
312 } // End switch.
313
314 $subject = check_plain($form_values['subject']);
315 $body = $form_values['message'];
316
317 // Get the session information.
318 $userip = $_SERVER['REMOTE_ADDR'];
319 $datestamp = str_replace(' ', 'T', date('Y-m-d H:i:s')); // ISO format without zone.
320
321 // If there's anything in our hidden field, it is most likely the result of a spam bot.
322 $suspect = !empty($form_values[variable_get('gotcha_form_field', 'Private_Message')]) ? 2 : 0;
323
324 // Log all the data.
325 if ($log_all || $suspect) {
326 // Write the data to our database table. (We allow datesent to default.)
327 db_query("INSERT INTO {gotcha} (datestamp, suspect, recipients, subject, body, sendername, sendermail, userip, sitename, type) VALUES ('%s', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", $datestamp, $suspect, $recipients, $subject, $body, $sendername, $sendermail, $userip, $site_name, $type);
328 }
329
330 if ($suspect) {
331 watchdog('gotcha', t("Suspect contact attempt intercepted from IP address !ip", array('!ip' => $userip)), WATCHDOG-WARNING, l(t('View log'), 'admin/logs/gotcha'));
332
333 // Finish by redirecting to the "go away" page or page_not_found.
334 $dest = variable_get('gotcha_goaway_page', variable_get('site_404', 'node'));
335 drupal_goto($dest);
336 }
337 else {
338 // Looks okay, so send it on to Contact.
339 contact_mail_page_submit($form_id, $form_values);
340 }
341 }
342
343 /**
344 * This is the menu callback function to view entries in the log.
345 */
346 function gotcha_view($mid=NULL) {
347 if (is_null($mid)) {
348 drupal_set_message('A message ID is required.', 'warning');
349 return;
350 }
351 $output = "\n<h3>". t('View Message Number !num', array('!num' => $mid)) .'</h3>';
352 $msg = db_fetch_array(db_query('SELECT * FROM {gotcha} WHERE mid=%d', $mid));
353
354 // Compose the body. Note, we don't need an explanation if it's not suspect, because it was sent originally.
355 $body = "";
356 if ($msg['suspect']) {
357 $body = gotcha_explain($msg, 'html');
358 }
359 $body .= '<p>'. wordwrap($msg['body'], 75, '<br/>') .'</p>';
360
361 $rows[] = array(array('data' => t('To'), 'header' => TRUE), $msg['recipients']);
362 $rows[] = array(array('data' => t('From'), 'header' => TRUE), $msg['sendername']);
363 $rows[] = array(array('data' => t('Subject'), 'header' => TRUE), $msg['subject']);
364 $rows[] = array(array('data' => $body, 'colspan' => '2'));
365 $output .= theme('table', array(), $rows, array('border' => '2'));
366
367 // Now add a link back to the list, and links to delete and send
368 $output .= '<p align="center">'. l(t('Back to the log'), 'admin/logs/gotcha') .'&nbsp;';
369 $output .= '|&nbsp;'. l(t('Delete this message'), 'admin/logs/gotcha/delete/'. $mid) .'&nbsp;';
370 $output .= '|&nbsp;'. l(t('Send this message'), 'admin/logs/gotcha/send/'. $mid) .'</p>';
371
372 return $output;
373 }
374
375 /**
376 * Helper function to build explanation text on message.
377 */
378 function gotcha_explain($msg, $target='mail') {
379 $sitename = variable_get('site_name', 'Drupal');
380 switch ($msg['type']) {
381 case 'site':
382 $message = t("!name sent a message using the contact form at !site.\n",
383 array('!name' => $msg['sendername'],
384 '!site' => $sitename,
385 )
386 );
387 $message .= t("It was intercepted as potential spam on !date.", array('!date' => drupal_substr($msg['datestamp'], 0, 10))) ."\n";
388 break;
389
390 case 'user':
391 $message = t("!name (!senderemail) has sent you a message via your contact form at !site.",
392 array('!name' => $msg['sendername'],
393 '!senderemail' => $msg['senderemail'] ? $msg['senderemail'] : variable_get('site_mail', '?'),
394 '!site' => $sitename,
395 )
396 ) ."\n";
397 $message .= t("It was intercepted as potential spam on !date.", array('!date' => drupal_substr($msg['datestamp'], 0, 10))) ."\n";
398 $message .= t("If you don't want to receive such e-mails, you may change your settings.") . "\n";
399 $message .= "\n". t('Message:') ."\n";
400 break;
401 }
402
403 if ($target != 'mail') {
404 $message = nl2br($message);
405 }
406 return $message;
407 }
408
409 /**
410 * This is the menu callback function to send a message that was not spam.
411 */
412 function gotcha_send($mid=NULL) {
413 if (is_null($mid)) {
414 drupal_set_message('A message ID is required.', 'warning');
415 return;
416 }
417
418 $msg = db_fetch_array(db_query('SELECT * FROM {gotcha} WHERE mid=%d', $mid));
419 $title = $msg['subject'];
420
421 // Compose the body. Note, we don't need an explanation if it's not suspect, because it was sent originally.
422 $body = "";
423 if ($msg['suspect']) {
424 $body = gotcha_explain($msg, 'mail') ."\n\n";
425 }
426 $body .= "\n". wordwrap($msg['body'], 75, "\n") ."\n";
427
428 // Send the e-mail to the recipients:
429 drupal_mail('contact-page-mail', $msg['recipients'], $title, $body, $msg['sendername']);
430
431 $x = str_replace(' ', 'T', date('Y-m-d H:i:s'));
432 $mark_sent = db_query("UPDATE {gotcha} SET suspect=5, datesent='%s' WHERE mid=%d", $x, $mid);
433 $message = "Message sent to ". $msg['recipients'] .".";
434 drupal_set_message($message, 'notice');
435 }
436
437 /**
438 * This is the menu callback function to delete entries in the log.
439 */
440 function gotcha_delete($mid=NULL) {
441 if (is_null($mid)) {
442 drupal_set_message('A message ID is required.', 'warning');
443 return;
444 }
445 $delsql = db_query('DELETE FROM {gotcha} WHERE mid=%d', $mid);
446 drupal_set_message("Message #$mid deleted.", 'notice');
447 }
448
449 /**
450 * This is the menu callback function to list the emails that have been intercepted.
451 */
452 function gotcha_list($op=NULL, $mid=NULL) {
453 if ($op == 'delete') {
454 gotcha_delete($mid);
455 }
456 elseif ($op == 'send') {
457 gotcha_send($mid);
458 }
459
460 $show_site = variable_get('gotcha_show_site', FALSE);
461 $how_many = variable_get('gotcha_rows_per_page', 20);
462 $max_body_len = variable_get('gotcha_max_body', 100);
463 $max_subject_len = variable_get('gotcha_max_subject', 15);
464 $suspect_type = array(0 => t('No'), 1 => t('Yes'), 2 => t('Bot'), 5 => t('Sent'));
465 $sql = "SELECT * FROM {gotcha} g ORDER BY g.datestamp DESC";
466 $result = pager_query($sql, $how_many);
467 $rows = array();
468 while ($msg = db_fetch_array($result)) {
469 // We'll limit the body length.
470 $body = check_plain($msg['body']);
471 if (drupal_strlen($body) > $max_body_len) {
472 $body = drupal_substr($body, 0, $max_body_len) .'<strong>...</strong>';
473 }
474 // subject needs chomping too
475 $subject = check_plain($msg['subject']);
476 if (drupal_strlen($subject) > $max_subject_len) {
477 $subject = drupal_substr($subject, 0, $max_subject_len);
478 }
479
480 $op_links = array();
481 $op_links[] = l(t('view'), 'gotcha/view/'. $msg['mid']);
482 $op_links[] = l(t('delete'), 'admin/logs/gotcha/delete/'. $msg['mid']);
483 if ($msg['suspect'] > 0 && $msg['suspect'] < 5) {
484 $op_links[] = l(t('send'), 'admin/logs/gotcha/send/'. $msg['mid']);
485 }
486
487 $a = split('T', $msg['datestamp']);
488 $msg['datestamp'] = implode('<br/>', $a);
489 $row_data = array(
490 $msg['datestamp'],
491 array('data' => $suspect_type[$msg['suspect']], 'align' => 'center'),
492 $msg['userip'],
493 $msg['recipients'] .'<br/>('. $msg['type'] .')',
494 $subject,
495 $body,
496 array('data' => $msg['sendername'] .'<br/>'. $msg['sendermail'], 'align' => 'left'),
497 array('data' => implode('<br/>', $op_links), 'align' => 'left'),
498 );
499 if ($show_site) {
500 $row_data[0] .= '<br/>'. $msg['sitename'];
501 }
502 $rows[] = $row_data;
503 }
504
505 // All ready to show the table if there were any entries in the log.
506 if (count($rows)) {
507 $header = array(t('Date/Time'),
508 t('Suspect'),
509 t('User IP'),
510 t('To'),
511 t('Subject'),
512 t('Body'),
513 t('Sender Name') .'<br/>'. t('Sender Email'),
514 t('Action'),
515 );
516 if ($show_site) {
517 $header[0] .= '<br/>'. t('Site Name');
518 }
519 $output .= '<div class="gotcha_list">'.
520 theme('table', $header, $rows) .
521 theme('pager', NULL, $how_many)
522 ."</div>";
523 return $output;
524 }
525 else {
526 return '<p>'. t('No emails found in the log.') .'</p>';
527 }
528 }
529
530 /**
531 * for cron, to prune the log
532 */
533 function gotcha_delete_old() {
534 $interval = variable_get('gotcha_log_interval', 30);
535 $delsql = db_query('DELETE FROM {gotcha} WHERE DATE_SUB(CURDATE(),INTERVAL %d DAY) > datestamp', $interval);
536 }
537
538
539 /**
540 * Implementation of hook_cron().
541 */
542 function gotcha_cron() {
543 // we only want the cron to run once a day
544 $gotcha_timestamp = variable_get('gotcha_day_timestamp', '');
545 if ((time() - $gotcha_timestamp) >= 86400) {
546 variable_set('gotcha_day_timestamp', time());
547 gotcha_delete_old();
548 }
549 }
550

  ViewVC Help
Powered by ViewVC 1.1.2