class.mail_fetch.php 17 KB

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