Mailinglist
 All Data Structures Files Functions Variables Pages
MailinglistPop3Retrieve.class.php
Go to the documentation of this file.
1 <?php
11 
21  function edit_form(&$form, &$form_state) {
22  parent::edit_form($form, $form_state);
23  global $cookie_domain;
24 
25  /* Parameters that we need:
26  Base:
27  Hostname:
28  Port:
29  Timeout:
30  UseSocket
31  Username:
32  Password:
33 
34  Extra:
35  APOPDetect:
36  Delete when done:
37  Logging: No, Yes, Yes-ShowPasswords
38  */
39 
40  $ajax_settings = array(
41  'callback' => '_mailinglist_mailbox_test',
42  'wrapper' => 'mailinglist_test_results',
43  'event' => 'change',
44  'progress' => array(
45  'type' => 'throbber',
46  'message' => t('Please wait - testing connection settings...'),
47  ),
48  );
49  $form['connection']['settings']['domain'] = array(
50  '#type' => 'textfield',
51  '#title' => t('Domain'),
52  '#default_value' => $this->get_setting('domain'),
53  '#required' => TRUE,
54  '#description' => t('The domain of the server used to collect mail.'),
55  '#ajax' => $ajax_settings,
56  );
57  $form['connection']['settings']['port'] = array(
58  '#type' => 'textfield',
59  '#title' => t('Port'),
60  '#size' => 5,
61  '#maxlength' => 5,
62  '#default_value' => $this->get_setting('port'),
63  '#description' => t('The mailbox port number (usually 110 for POP3).'),
64  '#element_validate' => array('element_validate_integer_positive'),
65  '#required' => TRUE,
66  '#ajax' => $ajax_settings,
67  );
68  $form['connection']['settings']['name'] = array(
69  '#type' => 'textfield',
70  '#title' => t('Username'),
71  '#default_value' => $this->get_setting('name'),
72  '#required' => TRUE,
73  '#description' => t('This username is used while logging into this mailbox during mail retrieval.'),
74  '#ajax' => $ajax_settings,
75  );
76  $form['connection']['settings']['pass'] = array(
77  '#type' => 'textfield',
78  '#title' => t('Password'),
79  '#default_value' => _mailinglist_decode_password($this->get_setting('pass')),
80  '#description' => t('The mailbox password corresponding to the username above. Consider using a non-vital password, since this field is stored with minimal encryption in the database and displayed here.'),
81  '#ajax' => $ajax_settings,
82  );
83 
84  $form['connection']['settings']['results'] = array(
85  '#type' => 'container',
86  '#attributes' => array(
87  'id' => 'mailinglist_test_results',
88  ),
89  );
90 
91  $form['connection']['settings']['timeout'] = array(
92  '#type' => 'textfield',
93  '#title' => t('Timeout'),
94  '#size' => 5,
95  '#maxlength' => 5,
96  '#default_value' => $this->get_setting('timeout', 10),
97  '#description' => t('Timeout (in Sec) to connect to server'),
98  '#element_validate' => array('element_validate_number'),
99  '#required' => TRUE,
100  '#ajax' => $ajax_settings,
101  );
102 
103  $form['extra']['settings']['apop'] = array(
104  '#type' => 'checkbox',
105  '#title' => t('Detect APOP?'),
106  '#default_value' => $this->get_setting('apop', FALSE),
107  '#description' => t('Check to enable APOP protocal detection'),
108  );
109 
110  $form['extra']['settings']['delete_after_read'] = array(
111  '#type' => 'checkbox',
112  '#title' => t('Delete messages after they are processed?'),
113  '#default_value' => $this->get_setting('delete_after_read', TRUE),
114  '#description' => t('Uncheck this box to leave read messages in the mailbox. They will not be processed again unless they become marked as unread. You normallys should check this box.'),
115  );
116 
117  $form['extra']['settings']['ipv6'] = array(
118  '#type' => 'checkbox',
119  '#title' => t('IPv6'),
120  '#default_value' => $this->get_setting('ipv6', FALSE),
121  '#description' => t('Use IPv6'),
122  );
123 
124  }
125 
130  function edit_form_validate(&$form, &$form_state) {
131  parent::edit_form_validate($form, $form_state);
132  if ($form_state['values']['settings']['port'] < 1 || 65535 < $form_state['values']['settings']['port']) {
133  form_set_error('port', t('Port must be between 1 and 65535'));
134  }
135 
136  if ($form_state['values']['settings']['port'] != 110) {
137  drupal_set_message(t('Non-standard Pop3 Port: @port', array('@port' => (int)$form_state['values']['settings']['port'])), 'warning');
138  }
139 
140  // If POP3 mailbox is chosen, messages should be deleted after processing.
141  // Do not set an actual error because this is helpful for testing purposes.
142  if ( $form_state['values']['settings']['delete_after_read'] == 0) {
143  drupal_set_message(t('Unless you check off "Delete messages after they are processed" when using a POP3 mailbox, old emails will be re-imported each time the mailbox is processed.'), 'warning');
144  }
145  }
146 
151  function edit_form_submit(&$form, &$form_state) {
152  parent::edit_form_submit($form, $form_state);
153  }
155 
156 
170  public function get_message_list($max=0) {
171  if (!$this->connected) {
172  if (!$this->connect()) return FALSE;
173  }
174 
175  $stat = $this->get_stat();
176  $limit = $stat['count'];
177  if ($max > 0 && $max < $limit) $limit = $max;
178  $res = array();
179  for ($i=1; $i<=$limit; $i++) {
180  $res[] = $i;
181  }
182  return $res;
183  }
184 
188  public function get_message($id) {
189  $msg = $this->get_msg($id);
190  return new MailinglistMessage($msg);
191  }
192 
196  public function purge_message($id) {
197  $this->delete_msg($id);
198 
199  }
200 
204  public function close() {
205  $this->quit();
206  $this->disconnect();
207  }
209 
210  /*****************************************************************************
211  * Private implementation
212  */
213 
214  const DEFAULT_BUFFER_SIZE = 4096;
215 
216  private $connected = FALSE;
217  private $socket;
218  private $ipaddr;
219  private $apop_banner = NULL;
220 
225  public function __destruct() {
226  $this->disconnect();
227  }
228 
235  protected function connect() {
236  // Make sure all needed parms are defined so we don't need to test everywhere
237  if (!isset($this->settings['apop'])) {
238  $this->settings['apop'] = FALSE;
239  }
240 
241  if (!isset($this->settings['use_sockets']) || !extension_loaded("sockets")) {
242  $this->settings['use_sockets'] = FALSE;
243  }
244 
245  if (!isset($this->settings['ipv6'])) $this->settings['ipv6'] = FALSE;
246 
247  $timeout = (double)$this->settings['timeout'];
248  $timeout_sec = (int)floor($timeout);
249  $timeout_usec = (int)(1000000*($timeout-$timeout_sec));
250  $timeout_array = array('sec' => $timeout_sec, 'usec' => $timeout_usec);
251  $host = $this->settings['domain'];
252  $port = $this->settings['port'];
253 
254  if ($this->settings['use_sockets']) {
255  $this->socket = socket_create(($this->settings['ipv6'] ? AF_INET6 : AF_INET), SOCK_STREAM, SOL_TCP );
256  if (!$this->socket) {
257  $this->log('Error creating Socket', this::LOG_ERROR);
258  return;
259  }
260  if (!socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_array) || !socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_array)) {
261  $this->log('Error setting socket timeout', this::LOG_ERROR);
262  return FALSE;
263  }
264  if (!socket_connect($this->socket, $host, $port) || !socket_getpeername($this->socket, $this->ipaddr)) {
265  $this->log("Error connecting to server", this::LOG_ERROR);
266  return FALSE;
267  }
268  }
269  else {
270  $url = 'tcp://' . $host . ':' . $port;
271  $intErrno = 0;
272  $strError = "";
273  $this->socket = fsockopen($url, $intErrno, $strError, $timeout);
274  if (!stream_set_timeout($this->socket, $timeout_sec, $timeout_usec)) {
275  $this->log('Error setting socket timeout', this::LOG_ERROR);
276  }
277  $this->ipaddr = gethostbyname($host);
278  }
279  $this->log("Connected to " . $this->ipaddr . ':' . $port . ' [' . $host . ']');
280 
281  // Get the first response with, if APOP support avalible, the apop banner.
282  $buff = "";
283  $buff = $this->recv_string();
284 
285  // Check for APOP banner
286  $start = strpos($buff, '<');
287  if ($start !== FALSE) {
288  $stop = strpos($buff, '>', $start);
289  if ($stop != FALSE) {
290  $this->apop_banner = substr($buff, $start+1, $stop-$start-1);
291  }
292  }
293  // Now we can Login to the server
294  $apop = FALSE;
295 
296  if ($this->settings['apop'] && !is_null($this->apop_banner)) {
297  $apop = TRUE;
298  }
299 
300  if ($apop) {
301  // APOP Auth
302  $ans = $this->send_cmd("APOP " . $this->settings['name'] . " " . hash("md5", $this->strAPOPBanner . _mailinglist_decode_password($this->settings['pass']), FALSE));
303  }
304  else {
305  // POP3 Auth
306  $this->send_cmd( "USER " . $this->settings['name']);
307  $ans = $this->send_cmd( "PASS " . _mailinglist_decode_password($this->settings['pass']));
308  }
309  if ($ans[0] == '+') {
310  $this->connected = TRUE;
311  }
312  return $this->connected;
313  }
314 
315  /*
316  * Disconnect from the server.
317  * CAUTION:
318  * This function doesn't send the QUIT command to the server so all as delete marked emails won't delete.
319  *
320  * @return void
321  */
322  protected function disconnect() {
323  if ($this->connected ) {
324  if ($this->settings['use_sockets']) {
325  if (socket_close($this->socket) === FALSE ) {
326  $this->log("Error On Socket Disconnect", this::LOG_ERROR);
327  }
328  }
329  else {
330  if (!fclose($this->socket)) {
331  $this->log("Error On Socket Disconnect", this::LOG_ERROR);
332  }
333  }
334  $this->connected = FALSE;
335  }
336  }
337 
354  protected function getOfficeStatus($limit = 0) {
355  if (!$this->connected) {
356  $this->connect();
357  }
358  $res = array();
359 
360  $res = $this->get_stat();
361 
362  if ($res["count"] > 0) {
363  $uidls = $this->get_uidls();
364  $lists = $this->get_list();
365 
366  if ($limit == 0 || $limit > $res['count']) $limit = $res['count'];
367  for ($i=1; $i<=$limit; $i++) {
368  list(, $uidl) = explode(" ", trim($uidls[$i-1]));
369  list(, $list) = explode(" ", trim($lists[$i-1]));
370  $res[$i]["uid"] = (int) $uidl;
371  $res[$i]["octets"] = (int) $list;
372  }
373  }
374  return $res;
375  }
376 
383  protected function get_uidls() {
384  if (!$this->connected) {
385  $this->connect();
386  }
387  $this->send_cmd("UIDL");
388  $uidls = $this->recv_to_point();
389  $uidls = explode("\r\n", trim($uidls));
390  return $uidls;
391  }
392 
398  protected function get_list() {
399  if (!$this->connected) {
400  $this->connect();
401  }
402  $this->send_cmd("LIST");
403  $lists = $this->recv_to_point();
404  $lists = explode("\r\n", trim($lists));
405  return $lists;
406  }
407 
415  protected function get_stat() {
416  if (!$this->connected) {
417  $this->connect();
418  }
419  $stat = $this->send_cmd("STAT");
420  $stat = explode(" ", trim($stat));
421  $res['count'] = (int) $stat[1];
422  $res['octets'] = (int) $stat[2];
423  return $res;
424  }
425 
426  /*
427  * Mark a message as delete
428  *
429  * @param int $msg_num Message Number on the pop3 server
430  *
431  * @return NULL
432  * @throw POP3_Exception
433  */
434  protected function delete_msg( $msg_num ) {
435  $this->send_cmd("DELE " . $msg_num);
436  return;
437  }
438 
444  protected function quit() {
445  if ($this->connected) {
446  $this->send_cmd("QUIT");
447  }
448  }
449 
457  protected function get_msg( $msg_num ) {
458  $this->send_cmd("RETR " . $msg_num);
459  return $this->recv_to_point();
460  }
461 
470  protected function recv_string($buff_size = self::DEFAULT_BUFFER_SIZE, $log = TRUE) {
471  $buff = "";
472  if ($this->settings['use_sockets']) {
473  $buff = socket_read($this->socket, $buff_size , PHP_NORMAL_READ);
474  if ($buff === FALSE ) {
475  $this->log("Socket Error", this::LOG_ERROR);
476  }
477  $len = strlen($buff);
478  if ($buff[$len-1] != "\r") {
479  $this->log('Line too long', this::LOG_ERROR);
480  }
481  else {
482  $buff = substr($buff, 0, $len-1);
483  // Workaround because socket_read with PHP_NORMAL_READ stop at "\r" but the network string ends with "\r\n"
484  // so we need to call the socket_read function again to get the "\n"
485  $buff2 = socket_read($this->socket, 1 , PHP_NORMAL_READ);
486  if ($buff2 === FALSE ) {
487  $this->log("Socket Error", this::LOG_ERROR);
488  }
489  if ($buff2 != "\n") {
490  $this->log('Bad Line Ends', this::LOG_ERROR);
491  }
492  }
493  }
494  else {
495  $buff = fgets($this->socket, $buff_size);
496  if (!$buff) {
497  $this->log("fgets(): Couldn't recieve the string from socket", this::LOG_ERROR);
498  }
499  $buff = substr($buff, 0, strlen($buff)-2);
500  }
501  if ($log) $this->log($buff);
502  return $buff;
503  }
504 
511  protected function recv_to_point() {
512  $buff = "";
513  while (TRUE) {
514  $buffer = $this->recv_string(self::DEFAULT_BUFFER_SIZE, FALSE);
515  if ($buffer == '.') {
516  break;
517  }
518  $buff .= $buffer . "\n";
519  }
520  return $buff;
521  }
522 
531  protected function send( $cmd ) {
532  $cmd .= "\r\n";
533  if ($this->settings['use_sockets']) {
534  if (socket_send($this->socket, $cmd, strlen($cmd), 0) === FALSE ) {
535  $this->log("Pop3 Send Error", this::LOG_ERROR);
536  }
537  }
538  else {
539  if (!fwrite($this->socket, $cmd, strlen($cmd))) {
540  $this->log("fwrite(): Failed to write string to socket", this::LOG_ERROR);
541  }
542  }
543  }
544 
554  protected function send_cmd($cmd) {
555  $this->log($cmd);
556  $this->send($cmd);
557  $res = $this->recv_string();
558  // 1. the check for the strlen of the result is a workaround for some server who don't send something after the quit command
559  // 2. should run with qmailer too...qmailer bug (pop3.class.inc) "." instead of "+OK" after RETR command
560  if (strlen($res) > 0 && $res[0] == '-') {
561  $this->log("Response Error " . $res, this::LOG_ERROR);
562  }
563  return $res;
564  }
565 }
send_cmd($cmd)
This function send the command to the server and will get the response If the command goes failed...
get_message_list($max=0)
Overrides/Implements Mailinglist:Retrieve::get_message_list Connect to mailbox and run message retrie...
purge_message($id)
Overrides/Implements MailinglistRetrieve::purge_message();.
edit_form_validate(&$form, &$form_state)
Implements ctools_export_ui::edit_form_validate().
close()
Overrides/Implements MailinglistRetrieve::close().
Class defining the contents of an e-mail message.
get_setting($name, $def=NULL)
get_settings()
get_msg($msg_num)
Recieve a raw message.
$ipaddr
String for ipaddr we are connecting to.
$socket
Handle for Socket being used.
get_message($id)
Overrides/Implements MailinglistRetrieve::get_message().
edit_form(&$form, &$form_state)
Implements ctools_export_ui::edit_form().
log($str, $level=self::LOG_NOTICE, $parms=array())
log()
edit_form_submit(&$form, &$form_state)
Implements ctools_export_ui::edit_form_submit().
quit()
Send the quit command to the server.
send($cmd)
Send a string to the server.
_mailinglist_decode_password($code)
_mailinglist_decode_password
recv_string($buff_size=self::DEFAULT_BUFFER_SIZE, $log=TRUE)
recv_string
Retrieve messages using PHP IMAP library.
get_stat()
Get the stats from the pop3 server This is only a string with the count of mails and their size in yo...
$connected
Are we currently connected/logged in.
Retrieve messages from a Mailinglist Mailbox.
getOfficeStatus($limit=0)
Get the office status.