1<?php 2/** 3 * Copyright 2003-2017 Horde LLC (http://www.horde.org/) 4 * 5 * See the enclosed file COPYING for license information (GPL). If you 6 * did not receive this file, see http://www.horde.org/licenses/gpl. 7 * 8 * @category Horde 9 * @copyright 2003-2017 Horde LLC 10 * @license http://www.horde.org/licenses/gpl GPL 11 * @package Passwd 12 */ 13 14/** 15 * Changes a user's password in a Pine password file. 16 * 17 * @author Max Kalika <max@horde.org> 18 * @category Horde 19 * @copyright 2003-2017 Horde LLC 20 * @license http://www.horde.org/licenses/gpl GPL 21 * @package Passwd 22 */ 23class Passwd_Driver_Pine extends Passwd_Driver 24{ 25 /** Lower boundary character. */ 26 const FIRSTCH = 0x20; 27 28 /** Upper boundary character. */ 29 const LASTCH = 0x7e; 30 31 /** Median character. */ 32 const TABSZ = 0x5f; 33 34 /** 35 * Boolean which contains state of the ftp connection. 36 * 37 * @var boolean 38 */ 39 protected $_connected = false; 40 41 /** 42 * Contents array of the pine password file. 43 * 44 * @var array 45 */ 46 protected $_contents = array(); 47 48 /** 49 * Horde_Vfs instance. 50 * 51 * @var VFS 52 */ 53 protected $_ftp; 54 55 /** 56 */ 57 public function __construct(array $params = array()) 58 { 59 self::__construct(array_merge(array( 60 /* We self-encrypt here, so plaintext is needed. */ 61 'encryption' => 'plain', 62 'show_encryption' => false, 63 64 /* Sensible FTP server parameters. */ 65 'host' => 'localhost', 66 'port' => 21, 67 'path' => '', 68 'file' => '.pinepw', 69 70 /* Connect to FTP server using just-passed-in credentials? 71 * Only useful if using the composite driver and changing 72 * system (FTP) password prior to this one. */ 73 'use_new_passwd' => false, 74 75 /* What host to look for on each line? */ 76 'imaphost' => 'localhost' 77 ), $params)); 78 } 79 80 /** 81 * Connects to the FTP server. 82 * 83 * @throws Passwd_Exception 84 */ 85 protected function _connect($user, $password) 86 { 87 if ($this->_connected) { 88 return; 89 } 90 91 $params = array( 92 'username' => $user, 93 'password' => $password, 94 'hostspec' => $this->_params['host'], 95 'port' => $this->_params['port'], 96 ); 97 98 try { 99 $this->_ftp = Horde_Vfs::factory('ftp', $params); 100 $this->_ftp->checkCredentials(); 101 } catch (Horde_Vfs_Exception $e) { 102 throw new Passwd_Exception($e); 103 } 104 105 $this->_connected = true; 106 } 107 108 /** 109 * Disconnect from the FTP server. 110 * 111 * @throws Passwd_Exception 112 */ 113 protected function _disconnect() 114 { 115 if ($this->_connected) { 116 try { 117 $this->_ftp->disconnect(); 118 } catch (Horde_Vfs_Exception $e) { 119 throw new Passwd_Exception($e); 120 } 121 $this->_connected = false; 122 } 123 } 124 125 /** 126 * Decodes a Pine-encoded password string. 127 * 128 * The algorithm is borrowed from read_passfile() and xlate_out() 129 * functions in pine/imap.c file distributed in the Pine source archive. 130 * 131 * @param string $string The contents of a pine-encoded password file. 132 * 133 * @return array List of lines of decoded elements. 134 */ 135 protected function _decode($string) 136 { 137 $list = array(); 138 139 $lines = explode("\n", $string); 140 for ($n = 0; $n < sizeof($lines); $n++) { 141 $key = $n; 142 $tmp = $lines[$n]; 143 for ($i = 0; $i < strlen($tmp); $i++) { 144 if ((ord($tmp[$i]) >= self::FIRSTCH) && 145 (ord($tmp[$i]) <= self::LASTCH)) { 146 $xch = ord($tmp[$i]) - ($dti = $key); 147 $xch += ($xch < self::FIRSTCH - self::TABSZ) 148 ? 2 * self::TABSZ 149 : ($xch < self::FIRSTCH) ? self::TABSZ : 0; 150 $dti = ($xch - self::FIRSTCH) + $dti; 151 $dti -= ($dti >= 2 * self::TABSZ) 152 ? 2 * self::TABSZ 153 : ($dti >= self::TABSZ) ? self::TABSZ : 0; 154 $key = $dti; 155 $tmp[$i] = chr($xch); 156 } 157 } 158 159 if ($i && $tmp[$i - 1] == "\n") { 160 $tmp = substr($tmp, 0, -1); 161 } 162 163 $parts = explode("\t", $tmp); 164 if (count($parts) >= 4) { 165 $list[] = $parts; 166 } 167 } 168 169 return $list; 170 } 171 172 /** 173 * Encodes an array of elements into a Pine-readable password string. 174 * 175 * The algorithm is borrowed from write_passfile() and xlate_in() 176 * functions in pine/imap.c file distributed in the Pine source archive. 177 * 178 * @param array $lines List of lines of decoded elements. 179 * 180 * @return array Contents of a pine-readable password file. 181 */ 182 protected function _encode($lines) 183 { 184 $string = ''; 185 for ($n = 0; $n < sizeof($lines); $n++) { 186 if (isset($lines[$n][4])) { 187 $lines[$n][4] = "\t" . $lines[$n][4]; 188 } else { 189 $lines[$n][4] = ''; 190 } 191 192 $key = $n; 193 $tmp = vsprintf("%.100s\t%.100s\t%.100s\t%d%s\n", $lines[$n]); 194 for ($i = 0; $i < strlen($tmp); $i++) { 195 $eti = $key; 196 if ((ord($tmp[$i]) >= self::FIRSTCH) && 197 (ord($tmp[$i]) <= self::LASTCH)) { 198 $eti += ord($tmp[$i]) - self::FIRSTCH; 199 $eti -= ($eti >= 2 * self::TABSZ) 200 ? 2 * self::TABSZ 201 : ($eti >= self::TABSZ) ? self::TABSZ : 0; 202 $key = $eti; 203 $tmp[$i] = chr($eti + self::FIRSTCH); 204 } 205 } 206 207 $string .= $tmp; 208 } 209 210 return $string; 211 } 212 213 /** 214 * Finds out if a username and password is valid. 215 * 216 * @param string $user The userID to check. 217 * @param string $oldPassword An old password to check. 218 * 219 * @throws Passwd_Exception 220 */ 221 protected function _lookup($user, $oldPassword) 222 { 223 try { 224 $contents = $this->_ftp->read($this->_params['path'], 225 $this->_params['file']); 226 } catch (Horde_Vfs_Exception $e) { 227 throw new Passwd_Exception($e); 228 } 229 230 $this->_contents = $this->_decode($contents); 231 foreach ($this->_contents as $line) { 232 if ($line[1] == $user && 233 (($line[2] == $this->_params['imaphost']) || 234 (!empty($line[4]) && $line[4] == $this->_params['imaphost']))) { 235 $this->_comparePasswords($line[0], $oldPassword); 236 return; 237 } 238 } 239 240 throw new Passwd_Exception(_("User not found.")); 241 } 242 243 /** 244 * Modifies a pine password record for a user. 245 * 246 * @param string $user The user whose record we will udpate. 247 * @param string $newPassword The new password value to set. 248 * 249 * @throws Passwd_Exception 250 */ 251 protected function _modify($user, $newPassword) 252 { 253 for ($i = 0; $i < sizeof($this->_contents); $i++) { 254 if ($this->_contents[$i][1] == $user && 255 (($this->_contents[$i][2] == $this->_params['imaphost']) || 256 (!empty($this->_contents[$i][4]) && 257 $this->_contents[$i][4] == $this->_params['imaphost']))) { 258 $this->_contents[$i][0] = $newPassword; 259 } 260 } 261 262 $string = $this->_encode($this->_contents); 263 try { 264 $this->_ftp->writeData($this->_params['path'], 265 $this->_params['file'], 266 $string); 267 } catch (Horde_Vfs_Exception $e) { 268 throw new Passwd_Exception($e); 269 } 270 } 271 272 /** 273 */ 274 protected function _changePassword($user, $oldpass, $newpass) 275 { 276 /* Connect to the ftp server. */ 277 $this->_connect($user, $this->_params['use_new_passwd'] ? $newpass : $oldpass); 278 $this->_lookup($user, $oldpass); 279 $this->_modify($user, $newpass); 280 $this->_disconnect(); 281 } 282 283} 284