Source for file class.mail_fetch.php

Documentation is available at class.mail_fetch.php

  1. <?php
  2. /**
  3.  * POP client class
  4.  *
  5.  * Class depends on PHP pcre extension and fsockopen() function. Some features
  6.  * might require PHP 4.3.0 with OpenSSL or PHP 5.1.0+. Class checks those extra
  7.  * dependencies internally, if used function needs it.
  8.  * @copyright 2006-2020 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: class.mail_fetch.php 14845 2020-01-07 08:09:34Z pdontthink $
  11.  * @package plugins
  12.  * @subpackage mail_fetch
  13.  * @link http://www.ietf.org/rfc/rfc1939.txt RFC1939
  14.  * @link http://www.ietf.org/rfc/rfc2449.txt POP3EXT
  15.  * @link http://www.ietf.org/rfc/rfc2595.txt STARTTLS
  16.  * @link http://www.ietf.org/rfc/rfc1734.txt AUTH command (unsupported)
  17.  */
  18.  
  19. /**
  20.  * POP3 client class
  21.  *
  22.  * POP connection is opened when class is constructed. All command_* methods
  23.  * execute specific POP commands on server. Most of other methods should be
  24.  * used only internally. Only login() method is public. If command returns
  25.  * mixed content and you expect message text, ids or something else, make sure
  26.  * that it is not boolean false.
  27.  *
  28.  * Basic use:
  29.  * 1. create object with connection params, see mail_fetch method.
  30.  * 2. check error buffer
  31.  * 3. login($username,$password) - true = login successful, false = login error.
  32.  * 4. command_stat() - get number of messages
  33.  * 5. command_list() - get message ids, use command_uidl(), if you implement
  34.  * 'keep mess on server' functions. Make sure that you handle possible UIDL
  35.  * command errors.
  36.  * 6. command_retr($some_message_id) - get message contents
  37.  * 7. command_dele($some_message_id) - mark message for deletion
  38.  * 8. command_quit() - close connection. You must close connection in order
  39.  *    to delete messages and remove mailbox lock.
  40.  * @package plugins
  41.  * @subpackage mail_fetch
  42.  */
  43. class mail_fetch {
  44.     /**
  45.      * Server name
  46.      * @var string 
  47.      */
  48.     var $host = '';
  49.  
  50.     /**
  51.      * POP connection port.
  52.      * Defaults to 110 on plain text connections and to 995 on TLS
  53.      * @var integer 
  54.      */
  55.     var $port = 0;
  56.  
  57.     /**
  58.      * Connection type
  59.      * 0 - plain text (default)
  60.      * 1 - tls (php 4.3 and openssl extension requirement)
  61.      * 2 - stls (stream_socket_enable_crypto() requirement. PHP 5.1.0, POP3
  62.      *     server with POP3EXT and STLS support)
  63.      * @var integer 
  64.      */
  65.     var $tls = 0;
  66.  
  67.     /**
  68.      * Authentication type
  69.      *
  70.      * Bitwise variable. If variable covers more than one authentication method,
  71.      * login() tries to use all of them until first successful auth.
  72.      * 1 - user/pass (rfc1939, default)
  73.      * 2 - apop (rfc1939, timestamp must be present in greeting)
  74.      * 3 - apop or user/pass
  75.      * @var integer 
  76.      */
  77.     var $auth = 1;
  78.  
  79.     /**
  80.      * Connection timeout
  81.      * @var integer 
  82.      */
  83.     var $timeout = 60;
  84.  
  85.     /**
  86.      * Connection resource
  87.      * @var stream 
  88.      */
  89.     var $conn = false;
  90.  
  91.     /**
  92.      * Server greeting
  93.      * @var string 
  94.      */
  95.     var $greeting = '';
  96.  
  97.     /**
  98.      * Timestamp (with <> or empty string)
  99.      * @var string 
  100.      */
  101.     var $timestamp = '';
  102.  
  103.     /**
  104.      * Capabilities (POP3EXT capa)
  105.      * @var array 
  106.      */
  107.     var $capabilities = array();
  108.  
  109.     /**
  110.      * Error message buffer
  111.      * @var string 
  112.      */
  113.     var $error = '';
  114.  
  115.     /**
  116.      * Response buffer
  117.      *
  118.      * Variable is used to store last positive POP server response
  119.      * checked in check_response() method. Used internally to handle
  120.      * mixed single and multiline command responses.
  121.      * @var string 
  122.      */
  123.     var $response = '';
  124.  
  125.     /**
  126.      * Constructor function
  127.      * 
  128.      * parameter array keys
  129.      * 'host' - required string, address of server. ip or fqn
  130.      * 'port' - optional integer, port of server.
  131.      * 'tls' - optional integer, connection type
  132.      * 'timeout' - optional integer, connection timeout
  133.      * 'auth' - optional integer, used authentication mechanism.
  134.      * See description of class properties
  135.      * @param array $aParams connection params
  136.      */
  137.     function mail_fetch($aParams=array()) {
  138.         // hostname
  139.         if (isset($aParams['host'])) {
  140.             $this->host = $aParams['host'];
  141.         else {
  142.             return $this->set_error('Server name is not set');
  143.         }
  144.         // tls
  145.         if (isset($aParams['tls'])) {
  146.             $this->tls = (int) $aParams['tls'];
  147.         }
  148.         // port
  149.         if (isset($aParams['port'])) {
  150.             $this->port = (int) $aParams['port'];
  151.         }
  152.         // set default ports
  153.         if ($this->port == 0{
  154.             if ($this->tls===1{
  155.                 // pops
  156.                 $this->port = 995;
  157.             else {
  158.                 // pop3
  159.                 $this->port = 110;
  160.             }
  161.         }
  162.         // timeout
  163.         if (isset($aParams['timeout'])) {
  164.             $this->timeout = (int) $aParams['timeout'];
  165.         }
  166.         // authentication mech
  167.         if (isset($aParams['auth'])) {
  168.             $this->auth = (int) $aParams['auth'];
  169.         }
  170.  
  171.         // open connection
  172.         $this->open();
  173.     }
  174.  
  175.     // Generic methods to handle connection and login operations.
  176.  
  177.     /**
  178.      * Opens pop connection
  179.      *
  180.      * Command handles TLS and STLS connection differences and fills capabilities
  181.      * array with RFC2449 CAPA data.
  182.      * @return boolean 
  183.      */
  184.     function open({
  185.         if ($this->conn{
  186.             return true;
  187.         }
  188.  
  189.         if ($this->tls===1{
  190.             if ($this->check_php_version(4,3|| extension_loaded('openssl')) {
  191.                 return $this->set_error('Used PHP version does not support functions required for POP TLS.');
  192.             }
  193.             $target 'tls://' $this->host;
  194.         else {
  195.             $target $this->host;
  196.         }
  197.  
  198.         $this->conn = @fsockopen($target$this->port$errno$errstr$this->timeout);
  199.  
  200.         if (!$this->conn{
  201.             $error sprintf('Error %d: ',$errno$errstr;
  202.             return $this->set_error($error);
  203.         }
  204.  
  205.         // read greeting
  206.         $this->greeting = trim(fgets($this->conn));
  207.  
  208.         // check greeting for errors and extract timestamp
  209.         if (preg_match('/^-ERR (.+)/',$this->greeting,$matches)) {
  210.             return $this->set_error($matches[1],true);
  211.         elseif (preg_match('/^\+OK.+(<.+>)/',$this->greeting,$matches)) {
  212.             $this->timestamp = $matches[1];
  213.         }
  214.  
  215.         /**
  216.          * fill capability only when connection uses some non-rfc1939
  217.          * authentication type (currently unsupported) or STARTTLS.
  218.          * Command is not part of rfc1939 and we don't have to use it 
  219.          * in simple POP connection.
  220.          */
  221.         if ($this->auth > || $this->tls===2{
  222.             $this->command_capa();
  223.         }
  224.  
  225.         // STARTTLS support
  226.         if ($this->tls===2{
  227.             return $this->command_stls();
  228.         }
  229.  
  230.         return true;
  231.     }
  232.  
  233.     /**
  234.      * Reads first response line and checks it for errors
  235.      * @return boolean true = success, false = failure, check error buffer
  236.      */
  237.     function check_response({
  238.         $line fgets($this->conn);
  239.         if (preg_match('/^-ERR (.+)/',$line,$matches)) {
  240.             return $this->set_error($matches[1]);
  241.         elseif (preg_match('/^\+OK/',$line)) {
  242.             $this->response = trim($line);
  243.             return true;
  244.         else {
  245.             $this->response = trim($line);
  246.             return $this->set_error('Unknown response');
  247.         }
  248.     }
  249.  
  250.     /**
  251.      * Standard SquirrelMail function copied to class in order to make class
  252.      * independent from SquirrelMail.
  253.      */
  254.     function check_php_version ($a '0'$b '0'$c '0'{
  255.         return version_compare PHP_VERSION"$a.$b.$c"'ge' );
  256.     }
  257.  
  258.     /**
  259.      * Generic login wrapper
  260.      *
  261.      * Connection is not closed on login error (unless POP server drops
  262.      * connection)
  263.      * @param string $username 
  264.      * @param string $password 
  265.      * @return boolean 
  266.      */
  267.     function login($username,$password{
  268.         $ret false;
  269.  
  270.         // RFC1939 APOP authentication
  271.         if ($ret && $this->auth 2{
  272.             // clean error buffer
  273.             $this->error = '';
  274.             // APOP login
  275.             $ret $this->command_apop($username,$password);
  276.         }
  277.  
  278.         // RFC1939 USER authentication
  279.         if ($ret && $this->auth 1{
  280.             // clean error buffer
  281.             $this->error = '';
  282.             // Default to USER/PASS login
  283.             if ($this->command_user($username)) {
  284.                 // error is already in error buffer
  285.                 $ret false;
  286.             else {
  287.                 $ret $this->command_pass($password);
  288.             }
  289.         }
  290.         return $ret;
  291.     }
  292.  
  293.     /**
  294.      * Sets error in error buffer and returns boolean false
  295.      * @param string $error Error message
  296.      * @param boolean $close_conn Do we have to close connection
  297.      * @return boolean false
  298.      */
  299.     function set_error($error,$close_conn=false{
  300.         $this->error = $error;
  301.         if ($close_conn{
  302.             $this->command_quit();
  303.         }
  304.         return false;
  305.     }
  306.  
  307.     // POP (rfc 1939) commands
  308.  
  309.     /**
  310.      * Gets mailbox status
  311.      * array with 'count' and 'size' keys
  312.      * @return mixed array or boolean false
  313.      */
  314.     function command_stat({
  315.          fwrite($this->conn,"STAT\r\n");
  316.          $response fgets($this->conn);
  317.          if (preg_match('/^\+OK (\d+) (\d+)/',$response,$matches)) {
  318.             return array('count' => $matches[1],
  319.                          'size'  => $matches[2]);
  320.         else {
  321.             return $this->set_error('stat command failed');
  322.         }
  323.     }
  324.  
  325.     /**
  326.      * List mailbox messages
  327.      * @param integer $msg 
  328.      * @return mixed array with message ids (keys) and sizes (values) or boolean false
  329.      */
  330.     function command_list($msg=''{
  331.         // add space between command and msg_id
  332.         if(!empty($msg)) $msg ' ' $msg;
  333.  
  334.         fwrite($this->conn,"LIST$msg\r\n");
  335.         
  336.         if($this->check_response()) {
  337.             $ret array();
  338.             if (!empty($msg)) {
  339.                 list($ok,$msg_id,$sizeexplode(' ',trim($this->response));
  340.                 $ret[$msg_id$size;
  341.             else {
  342.                 while($line fgets($this->conn)) {
  343.                     if (trim($line)=='.'{
  344.                         break;
  345.                     else {
  346.                         list($msg_id,$sizeexplode(' ',trim($line));
  347.                         $ret[$msg_id$size;
  348.                     }
  349.                 }
  350.             }
  351.             return $ret;
  352.         else {
  353.             return false;
  354.         }
  355.     }
  356.  
  357.     /**
  358.      * Gets message text
  359.      * @param integer $msg message id
  360.      * @return mixed rfc822 message (CRLF line endings) or boolean false
  361.      */
  362.     function command_retr($msg{
  363.         fwrite($this->conn,"RETR $msg\r\n");
  364.         
  365.         if($this->check_response()) {
  366.             $ret '';
  367.             while($line fgets($this->conn)) {
  368.                 if ($line == ".\r\n"{
  369.                     break;
  370.                 elseif $line{0== '.' {
  371.                     $ret .= substr($line,1);
  372.                 else {
  373.                     $ret.= $line;
  374.                 }
  375.             }
  376.             return $ret;
  377.         else {
  378.             return false;
  379.         }
  380.     }
  381.  
  382.     /**
  383.      * @param integer $msg 
  384.      * @return boolean 
  385.      */
  386.     function command_dele($msg{
  387.        fwrite($this->conn,"DELE $msg\r\n");
  388.        return $this->check_response();
  389.     }
  390.  
  391.     /**
  392.      * POP noop command
  393.      * @return boolean 
  394.      */
  395.     function command_noop({
  396.         fwrite($this->conn,"NOOP\r\n");
  397.         return $this->check_response();
  398.     }
  399.  
  400.     /**
  401.      * Resets message state
  402.      * @return boolean 
  403.      */
  404.     function command_rset({
  405.         fwrite($this->conn,"RSET\r\n");
  406.         return $this->check_response();
  407.     }
  408.  
  409.     /**
  410.      * Closes POP connection
  411.      */
  412.     function command_quit({
  413.         fwrite($this->conn,"QUIT\r\n");
  414.         fclose($this->conn);
  415.         $this->conn = false;
  416.     }
  417.  
  418.     // Optional RFC1939 commands
  419.  
  420.     /**
  421.      * Gets message headers and $n of body lines.
  422.      *
  423.      * Command is optional and not required by rfc1939
  424.      * @param integer $msg 
  425.      * @param integer $n 
  426.      * @return string or boolean false
  427.      */
  428.     function command_top($msg,$n{
  429.         fwrite($this->conn,"TOP $msg $n\r\n");
  430.         
  431.         if($this->check_response()) {
  432.             $ret '';
  433.             while($line fgets($this->conn)) {
  434.                 if (trim($line)=='.'{
  435.                     break;
  436.                 else {
  437.                     $ret.= $line;
  438.                 }
  439.             }
  440.             return $ret;
  441.         else {
  442.             return false;
  443.         }
  444.     }
  445.  
  446.     /**
  447.      * Gets unique message ids
  448.      * Command is optional and not required by rfc1939
  449.      * @param integer $msg message id
  450.      * @return mixed array with message ids (keys) and unique ids (values)
  451.      *  or boolean false
  452.      */
  453.     function command_uidl($msg=''{
  454.         //return $this->set_error('Unsupported command.');
  455.         // add space between command and msg_id
  456.         if(!empty($msg)) $msg ' ' $msg;
  457.         fwrite($this->conn,"UIDL$msg\r\n");
  458.         if($this->check_response()) {
  459.             $ids array();
  460.             if (!empty($msg)) {
  461.                 list($ok,$msg_id,$unique_idexplode(' ',trim($this->response));
  462.                 $ids[$msg_id"$unique_id";
  463.             else {
  464.                 while($line fgets($this->conn)) {
  465.                     if (trim($line)=='.'{
  466.                         break;
  467.                     else {
  468.                         list($msg_id,$unique_idexplode(' ',trim($line));
  469.                         // make sure that unique_id is a string.
  470.                         $ids[$msg_id"$unique_id";
  471.                     }
  472.                 }
  473.             }
  474.             return $ids;
  475.         else {
  476.             return false;
  477.         }
  478.     }
  479.  
  480.     /**
  481.      * USER authentication (username command)
  482.      * 
  483.      * Command is optional and not required by rfc1939. If command
  484.      * is successful, pass command must be executed after it.
  485.      * @param string $username 
  486.      * @return boolean true = success, false = failure.
  487.      */
  488.     function command_user($username{
  489.         fwrite($this->conn,"USER $username\r\n");
  490.         return $this->check_response();
  491.     }
  492.  
  493.     /**
  494.      * USER authentication (password command)
  495.      *
  496.      * Command is optional and not required by rfc1939. Requires
  497.      * successful user command.
  498.      * @param string $password 
  499.      * @return boolean true = success, false = failure.
  500.      */
  501.     function command_pass($password{
  502.         fwrite($this->conn,"PASS $password\r\n");
  503.         return $this->check_response();
  504.     }
  505.  
  506.     /**
  507.      * APOP authentication
  508.      *
  509.      * Command is optional and not required by rfc1939. APOP support
  510.      * requires plain text passwords stored on server and some servers
  511.      * don't support it. Standard qmail pop3d declares apop support
  512.      * without checking if checkpassword supports it.
  513.      * @param string $username 
  514.      * @param string $password 
  515.      * @return boolean true = success, false = failure.
  516.      */
  517.     function command_apop($username,$password{
  518.         if (empty($this->timestamp)) {
  519.             return $this->set_error('APOP is not supported by selected server.');
  520.         }
  521.         $digest md5($this->timestamp . $password);
  522.  
  523.         fwrite($this->conn,"APOP $username $digest\r\n");
  524.         return $this->check_response();
  525.     }
  526.  
  527.     // RFC2449 POP3EXT
  528.  
  529.     /**
  530.      * Checks pop server capabilities
  531.      * 
  532.      * RFC2449. Fills capabilities array.
  533.      * @return void 
  534.      */
  535.     function command_capa({
  536.         fwrite($this->conn,"CAPA\r\n");
  537.         if ($this->check_response()) {
  538.             // reset array. capabilities depend on authorization state
  539.             $this->capabilities = array();
  540.             while($line fgets($this->conn)) {
  541.                 if (trim($line)=='.'{
  542.                     break;
  543.                 else {
  544.                     $this->capabilities[trim($line);
  545.                 }
  546.             }
  547.         else {
  548.             // if capa fails, error buffer contains error message.
  549.             // Clean error buffer,
  550.             // if POP3EXT is not supported, capability array will be empty
  551.             $this->error = '';
  552.         }
  553.     }
  554.  
  555.     // RFC2595 STARTTLS
  556.  
  557.     /**
  558.      * RFC 2595 POP STARTTLS support
  559.      * @return boolean 
  560.      */
  561.     function command_stls({
  562.         if (function_exists('stream_socket_enable_crypto')) {
  563.             return $this->set_error('Used PHP version does not support functions required for POP STLS.',true);
  564.         elseif (in_array('STLS',$this->capabilities)) {
  565.             return $this->set_error('Selected POP3 server does not support STLS.',true);
  566.         }
  567.         fwrite($this->conn,"STLS\r\n");
  568.         if ($this->check_response()) {
  569.             $this->command_quit();
  570.             return false;
  571.         }
  572.  
  573.         if (@stream_socket_enable_crypto($this->conn,true,STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
  574.             // starttls was successful (rfc2595 4. POP3 STARTTLS extension.)
  575.             // get new CAPA response
  576.             $this->command_capa();
  577.         else {
  578.             /** stream_socket_enable_crypto() call failed. */
  579.             return $this->set_error('Unable to start TLS.',true);
  580.         }
  581.         return true;
  582.     }
  583. }

Documentation generated on Mon, 13 Jan 2020 04:22:03 +0100 by phpDocumentor 1.4.3