Add support for SMTP

Signed-off-by: Shin'ya Minazuki <shinyoukai@laidback.moe>
This commit is contained in:
2026-01-11 20:29:57 -03:00
parent 51feb76b47
commit b86ecbfc99
3 changed files with 146 additions and 1 deletions

View File

@@ -220,6 +220,9 @@ Add the following to your `config.php`:
**⚠⚠ Warning:** If you need to set *5 (Hashed Password in Database)* to false, your Prosody Instance is storing passwords in plaintext. This is insecure and not recommended. We highly recommend that you change your Prosody configuration to protect the passwords of your Prosody users. ⚠⚠
## SMTP
Works the same way as the IMAP section above, but authenticates against an SMTP server.
Alternatives
------------
Other extensions allow connecting to external user databases directly via SQL, which may be faster:

View File

@@ -11,6 +11,7 @@
* FTP
* WebDAV
* HTTP BasicAuth
* SMTP
* SSH
* XMPP
@@ -33,6 +34,6 @@ Read the [documentation](https://github.com/nextcloud/user_external#readme) to l
<bugs>https://github.com/nextcloud/user_external/issues</bugs>
<repository type="git">https://github.com/nextcloud/user_external.git</repository>
<dependencies>
<nextcloud min-version="25" max-version="30" />
<nextcloud min-version="25" max-version="32" />
</dependencies>
</info>

141
lib/SMTP.php Normal file
View File

@@ -0,0 +1,141 @@
<?php
/**
* @author Robin Appelman <icewind@owncloud.com>
* @author Jonas Sulzer <jonas@violoncello.ch>
* @copyright (c) 2012 Robin Appelman <icewind@owncloud.com>
* @copyright (c) 2026 Yakumo Laboratories -- https://yakumolabs.privatedns.org --
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCA\UserExternal;
/**
* User authentication against an SMTP mail server
* (modified from IMAP.php)
*
* @category Apps
* @package UserExternal
* @author Robin Appelman <icewind@owncloud.com>
* @license http://www.gnu.org/licenses/agpl AGPL
* @link http://github.com/owncloud/apps
*/
class SMTP extends Base {
private $mailbox;
private $port;
private $sslmode;
private $domain;
private $stripeDomain;
private $groupDomain;
/**
* Create new SMTP authentication provider
*
* @param string $mailbox SMTP server domain/IP
* @param int $port SMTP server $port
* @param string $sslmode
* @param string $domain If provided, loging will be restricted to this domain
* @param boolean $stripeDomain (whether to stripe the domain part from the username or not)
* @param boolean $groupDomain (whether to add the usere to a group corresponding to the domain of the address)
*/
public function __construct($mailbox, $port = null, $sslmode = null, $domain = null, $stripeDomain = true, $groupDomain = false) {
parent::__construct($mailbox);
$this->mailbox = $mailbox;
$this->port = $port === null ? 25 : $port;
$this->sslmode = $sslmode;
$this->domain = $domain === null ? '' : $domain;
$this->stripeDomain = $stripeDomain;
$this->groupDomain = $groupDomain;
}
/**
* Check if the password is correct without logging in the user
*
* @param string $uid The username
* @param string $password The password
*
* @return true/false
*/
public function checkPassword($uid, $password) {
// Replace escaped @ symbol in uid (which is a mail address)
// but only if there is no @ symbol and if there is a %40 inside the uid
if (!(strpos($uid, '@') !== false) && (strpos($uid, '%40') !== false)) {
$uid = str_replace("%40", "@", $uid);
}
$pieces = explode('@', $uid);
if ($this->domain !== '') {
if (count($pieces) === 1) {
$username = $uid . '@' . $this->domain;
} elseif (count($pieces) === 2 && $pieces[1] === $this->domain) {
$username = $uid;
if ($this->stripeDomain) {
$uid = $pieces[0];
}
} else {
$this->logger->error(
'ERROR: User has a wrong domain! Expecting: '.$this->domain,
['app' => 'user_external']
);
return false;
}
} else {
$username = $uid;
}
$groups = [];
if ((count($pieces) > 1) && $this->groupDomain && $pieces[1]) {
$groups[] = $pieces[1];
}
$protocol = ($this->sslmode === "ssl") ? "smtps" : "smtp";
$url = "{$protocol}://{$this->mailbox}:{$this->port}";
$ch = curl_init();
if ($this->sslmode === 'tls') {
curl_setopt($ch, CURLOPT_USE_SSL, CURLUSESSL_ALL);
}
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $username.":".$password);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_exec($ch);
$errorcode = curl_errno($ch);
if ($errorcode === 0) {
curl_close($ch);
$uid = mb_strtolower($uid);
$this->storeUser($uid, $groups);
return $uid;
} elseif ($errorcode === CURLE_COULDNT_CONNECT ||
$errorcode === CURLE_SSL_CONNECT_ERROR ||
$errorcode === 28) {
# This is not defined in PHP-8.x
# 28: CURLE_OPERATION_TIMEDOUT
$this->logger->error(
'ERROR: Could not connect to smtp server via curl: ' . curl_strerror($errorcode),
['app' => 'user_external']
);
} elseif ($errorcode === 9 ||
$errorcode === 67 ||
$errorcode === 94) {
# These are not defined in PHP-8.x
# 9: CURLE_REMOTE_ACCESS_DENIED
# 67: CURLE_LOGIN_DENIED
# 94: CURLE_AUTH_ERROR)
$this->logger->error(
'ERROR: SMTP Login failed via curl: ' . curl_strerror($errorcode),
['app' => 'user_external']
);
} else {
$this->logger->error(
'ERROR: SMTP server returned an error: ' . $errorcode . ' / ' . curl_strerror($errorcode),
['app' => 'user_external']
);
}
curl_close($ch);
return false;
}
}