Files
server/usr/share/psa-horde/passwd/lib/Driver/Pine.php
2026-01-07 20:52:11 +01:00

284 lines
8.2 KiB
PHP

<?php
/**
* Copyright 2003-2017 Horde LLC (http://www.horde.org/)
*
* See the enclosed file COPYING for license information (GPL). If you
* did not receive this file, see http://www.horde.org/licenses/gpl.
*
* @category Horde
* @copyright 2003-2017 Horde LLC
* @license http://www.horde.org/licenses/gpl GPL
* @package Passwd
*/
/**
* Changes a user's password in a Pine password file.
*
* @author Max Kalika <max@horde.org>
* @category Horde
* @copyright 2003-2017 Horde LLC
* @license http://www.horde.org/licenses/gpl GPL
* @package Passwd
*/
class Passwd_Driver_Pine extends Passwd_Driver
{
/** Lower boundary character. */
const FIRSTCH = 0x20;
/** Upper boundary character. */
const LASTCH = 0x7e;
/** Median character. */
const TABSZ = 0x5f;
/**
* Boolean which contains state of the ftp connection.
*
* @var boolean
*/
protected $_connected = false;
/**
* Contents array of the pine password file.
*
* @var array
*/
protected $_contents = array();
/**
* Horde_Vfs instance.
*
* @var VFS
*/
protected $_ftp;
/**
*/
public function __construct(array $params = array())
{
self::__construct(array_merge(array(
/* We self-encrypt here, so plaintext is needed. */
'encryption' => 'plain',
'show_encryption' => false,
/* Sensible FTP server parameters. */
'host' => 'localhost',
'port' => 21,
'path' => '',
'file' => '.pinepw',
/* Connect to FTP server using just-passed-in credentials?
* Only useful if using the composite driver and changing
* system (FTP) password prior to this one. */
'use_new_passwd' => false,
/* What host to look for on each line? */
'imaphost' => 'localhost'
), $params));
}
/**
* Connects to the FTP server.
*
* @throws Passwd_Exception
*/
protected function _connect($user, $password)
{
if ($this->_connected) {
return;
}
$params = array(
'username' => $user,
'password' => $password,
'hostspec' => $this->_params['host'],
'port' => $this->_params['port'],
);
try {
$this->_ftp = Horde_Vfs::factory('ftp', $params);
$this->_ftp->checkCredentials();
} catch (Horde_Vfs_Exception $e) {
throw new Passwd_Exception($e);
}
$this->_connected = true;
}
/**
* Disconnect from the FTP server.
*
* @throws Passwd_Exception
*/
protected function _disconnect()
{
if ($this->_connected) {
try {
$this->_ftp->disconnect();
} catch (Horde_Vfs_Exception $e) {
throw new Passwd_Exception($e);
}
$this->_connected = false;
}
}
/**
* Decodes a Pine-encoded password string.
*
* The algorithm is borrowed from read_passfile() and xlate_out()
* functions in pine/imap.c file distributed in the Pine source archive.
*
* @param string $string The contents of a pine-encoded password file.
*
* @return array List of lines of decoded elements.
*/
protected function _decode($string)
{
$list = array();
$lines = explode("\n", $string);
for ($n = 0; $n < sizeof($lines); $n++) {
$key = $n;
$tmp = $lines[$n];
for ($i = 0; $i < strlen($tmp); $i++) {
if ((ord($tmp[$i]) >= self::FIRSTCH) &&
(ord($tmp[$i]) <= self::LASTCH)) {
$xch = ord($tmp[$i]) - ($dti = $key);
$xch += ($xch < self::FIRSTCH - self::TABSZ)
? 2 * self::TABSZ
: ($xch < self::FIRSTCH) ? self::TABSZ : 0;
$dti = ($xch - self::FIRSTCH) + $dti;
$dti -= ($dti >= 2 * self::TABSZ)
? 2 * self::TABSZ
: ($dti >= self::TABSZ) ? self::TABSZ : 0;
$key = $dti;
$tmp[$i] = chr($xch);
}
}
if ($i && $tmp[$i - 1] == "\n") {
$tmp = substr($tmp, 0, -1);
}
$parts = explode("\t", $tmp);
if (count($parts) >= 4) {
$list[] = $parts;
}
}
return $list;
}
/**
* Encodes an array of elements into a Pine-readable password string.
*
* The algorithm is borrowed from write_passfile() and xlate_in()
* functions in pine/imap.c file distributed in the Pine source archive.
*
* @param array $lines List of lines of decoded elements.
*
* @return array Contents of a pine-readable password file.
*/
protected function _encode($lines)
{
$string = '';
for ($n = 0; $n < sizeof($lines); $n++) {
if (isset($lines[$n][4])) {
$lines[$n][4] = "\t" . $lines[$n][4];
} else {
$lines[$n][4] = '';
}
$key = $n;
$tmp = vsprintf("%.100s\t%.100s\t%.100s\t%d%s\n", $lines[$n]);
for ($i = 0; $i < strlen($tmp); $i++) {
$eti = $key;
if ((ord($tmp[$i]) >= self::FIRSTCH) &&
(ord($tmp[$i]) <= self::LASTCH)) {
$eti += ord($tmp[$i]) - self::FIRSTCH;
$eti -= ($eti >= 2 * self::TABSZ)
? 2 * self::TABSZ
: ($eti >= self::TABSZ) ? self::TABSZ : 0;
$key = $eti;
$tmp[$i] = chr($eti + self::FIRSTCH);
}
}
$string .= $tmp;
}
return $string;
}
/**
* Finds out if a username and password is valid.
*
* @param string $user The userID to check.
* @param string $oldPassword An old password to check.
*
* @throws Passwd_Exception
*/
protected function _lookup($user, $oldPassword)
{
try {
$contents = $this->_ftp->read($this->_params['path'],
$this->_params['file']);
} catch (Horde_Vfs_Exception $e) {
throw new Passwd_Exception($e);
}
$this->_contents = $this->_decode($contents);
foreach ($this->_contents as $line) {
if ($line[1] == $user &&
(($line[2] == $this->_params['imaphost']) ||
(!empty($line[4]) && $line[4] == $this->_params['imaphost']))) {
$this->_comparePasswords($line[0], $oldPassword);
return;
}
}
throw new Passwd_Exception(_("User not found."));
}
/**
* Modifies a pine password record for a user.
*
* @param string $user The user whose record we will udpate.
* @param string $newPassword The new password value to set.
*
* @throws Passwd_Exception
*/
protected function _modify($user, $newPassword)
{
for ($i = 0; $i < sizeof($this->_contents); $i++) {
if ($this->_contents[$i][1] == $user &&
(($this->_contents[$i][2] == $this->_params['imaphost']) ||
(!empty($this->_contents[$i][4]) &&
$this->_contents[$i][4] == $this->_params['imaphost']))) {
$this->_contents[$i][0] = $newPassword;
}
}
$string = $this->_encode($this->_contents);
try {
$this->_ftp->writeData($this->_params['path'],
$this->_params['file'],
$string);
} catch (Horde_Vfs_Exception $e) {
throw new Passwd_Exception($e);
}
}
/**
*/
protected function _changePassword($user, $oldpass, $newpass)
{
/* Connect to the ftp server. */
$this->_connect($user, $this->_params['use_new_passwd'] ? $newpass : $oldpass);
$this->_lookup($user, $oldpass);
$this->_modify($user, $newpass);
$this->_disconnect();
}
}