Skip to content

Commit

Permalink
add SMTP email sending, LDAP authentication with auto user creation
Browse files Browse the repository at this point in the history
additional options are only visible when they are selected

Signed-off-by: Aisha Tammy <[email protected]>
  • Loading branch information
epsilon-0 committed Jul 4, 2022
1 parent 9e43a17 commit 9ef2ecc
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 16 deletions.
197 changes: 197 additions & 0 deletions Core/Frameworks/Baikal/Core/IMipSMTPPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
<?php

declare(strict_types=1);

namespace Baikal\Core;

use \Sabre\DAV;
use \Sabre\VObject\ITip;

/**
* iMIP handler using Pear SMTP.
*
* This class is responsible for sending out iMIP messages. iMIP is the
* email-based transport for iTIP. iTIP deals with scheduling operations for
* iCalendar objects.
*
* If you want to customize the email that gets sent out, you can do so by
* extending this class and overriding the sendMessage method.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Aisha Tammy <[email protected]>
* @license http://sabre.io/license/ Modified BSD License
*/
class IMipSMTPPlugin extends \Sabre\DAV\ServerPlugin
{
/**
* Email address used in From: header.
*
* @var string
*/
protected $senderEmail;

/**
* SMTP connection made by PEAR
*
*/
protected $smtp;

/**
* ITipMessage.
*
* @var ITip\Message
*/
protected $itipMessage;

/**
* Creates the email handler.
*
* @param string $senderEmail. The 'senderEmail' is the email that shows up
* in the 'From:' address. This should
* generally be some kind of no-reply email
* address you own.
*/
public function __construct($senderEmail, $smtp_host = "", $smtp_port = "", $smtp_username = "", $smtp_password = "")
{
require_once "Mail.php";
$this->senderEmail = $senderEmail;
$this->smtp = \Mail::factory('smtp', array ('host' => $smtp_host, 'port' => $smtp_port, 'auth' => true, 'username' => $smtp_username, 'password' => $smtp_password));
}

/*
* This initializes the plugin.
*
* This function is called by Sabre\DAV\Server, after
* addPlugin is called.
*
* This method should set up the required event subscriptions.
*
* @param DAV\Server $server
* @return void
*/
public function initialize(DAV\Server $server)
{
$server->on('schedule', [$this, 'schedule'], 120);
}

/**
* Returns a plugin name.
*
* Using this name other plugins will be able to access other plugins
* using \Sabre\DAV\Server::getPlugin
*
* @return string
*/
public function getPluginName()
{
return 'imip-smtp';
}

/**
* Event handler for the 'schedule' event.
*/
public function schedule(ITip\Message $iTipMessage)
{
// Not sending any emails if the system considers the update
// insignificant.
if (!$iTipMessage->significantChange) {
if (!$iTipMessage->scheduleStatus) {
$iTipMessage->scheduleStatus = '1.0;We got the message, but it\'s not significant enough to warrant an email';
}

return;
}

$summary = $iTipMessage->message->VEVENT->SUMMARY;

if ('mailto' !== parse_url($iTipMessage->sender, PHP_URL_SCHEME)) {
return;
}

if ('mailto' !== parse_url($iTipMessage->recipient, PHP_URL_SCHEME)) {
return;
}

$sender = substr($iTipMessage->sender, 7);
$recipient = substr($iTipMessage->recipient, 7);

if ($iTipMessage->senderName) {
$sender = $iTipMessage->senderName.' <'.$sender.'>';
}
if ($iTipMessage->recipientName && $iTipMessage->recipientName != $recipient) {
$recipient = $iTipMessage->recipientName.' <'.$recipient.'>';
}

$subject = 'SabreDAV iTIP message';
switch (strtoupper($iTipMessage->method)) {
case 'REPLY':
$subject = 'Re: '.$summary;
break;
case 'REQUEST':
$subject = 'Invitation: '.$summary;
break;
case 'CANCEL':
$subject = 'Cancelled: '.$summary;
break;
}

$headers = array(
'Reply-To' => $sender,
'From' => $iTipMessage->senderName.' <'.$this->senderEmail.'>',
'To' => $recipient,
'Subject' => $subject,
'MIME-Version' => '1.0',
'Content-Type' => 'text/calendar; charset=UTF-8; method='.$iTipMessage->method,
);
if (DAV\Server::$exposeVersion) {
$headers += ['X-Sabre-Version' => DAV\Version::VERSION];
}
$this->mail(
$recipient,
$headers,
$iTipMessage->message->serialize()
);
$iTipMessage->scheduleStatus = '1.1; Scheduling message is sent via iMip';
}

// @codeCoverageIgnoreStart
// This is deemed untestable in a reasonable manner

/**
* This function is responsible for sending the actual email.
*
* @param string $to Recipient email address
* @param string $body iCalendar body
* @param array $headers List of headers
*/
protected function mail($to, array $headers, $body)
{
$mail = $this->smtp->send($to, $headers, $body);
if (\PEAR::isError($mail))
error_log($mail->getMessage());
else
error_log("Email successfully sent!");
}

// @codeCoverageIgnoreEnd

/**
* Returns a bunch of meta-data about the plugin.
*
* Providing this information is optional, and is mainly displayed by the
* Browser plugin.
*
* The description key in the returned array may contain html and will not
* be sanitized.
*
* @return array
*/
public function getPluginInfo()
{
return [
'name' => $this->getPluginName(),
'description' => 'Email delivery (rfc6047) for CalDAV scheduling using Pear SMTP',
'link' => 'http://sabre.io/dav/scheduling/',
];
}
}
166 changes: 166 additions & 0 deletions Core/Frameworks/Baikal/Core/LDAP.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<?php

namespace Baikal\Core;

/**
* This is an authentication backend that uses ldap.
*
* @copyright Copyright (C) fruux GmbH (https://fruux.com/)
* @author Aisha Tammy <[email protected]>
* @license http://sabre.io/license/ Modified BSD License
*/
class LDAP extends \Sabre\DAV\Auth\Backend\AbstractBasic
{
/**
* Reference to PDO connection.
*
* @var PDO
*/
protected $pdo;

/**
* PDO table name we'll be using.
*
* @var string
*/
protected $table_name;

/**
* LDAP server uri.
* e.g. ldaps://ldap.example.org
*
* @var string
*/
protected $ldap_uri;

/*
* LDAP dn pattern for binding
*
* %u - gets replaced by full username
* %U - gets replaced by user part when the
* username is an email address
* %d - gets replaced by domain part when the
* username is an email address
* %1-9 - gets replaced by parts of the the domain
* split by '.' in reverse order
* mail.example.org: %1 = org, %2 = example, %3 = mail
*
* @var string
*/
protected $ldap_dn;

/*
* LDAP attribute to use for name
*
* @var string
*/
protected $ldap_cn;

/*
* LDAP attribute used for mail
*
* @var string
*/
protected $ldap_mail;

/**
* Creates the backend object.
*
* @param string $ldap_uri
* @param string $ldap_dn
* @param string $ldap_cn
* @param string $ldap_mail
*
*/
public function __construct(\PDO $pdo, $table_name = 'users', $ldap_uri = 'ldap://127.0.0.1', $ldap_dn = 'mail=%u', $ldap_cn = 'cn', $ldap_mail = 'mail')
{
$this->pdo = $pdo;
$this->table_name = $table_name;
$this->ldap_uri = $ldap_uri;
$this->ldap_dn = $ldap_dn;
$this->ldap_cn = $ldap_cn;
$this->ldap_mail = $ldap_mail;
}

/**
* Connects to an LDAP server and tries to authenticate.
*
* @param string $username
* @param string $password
*
* @return bool
*/
protected function ldapOpen($username, $password)
{
$conn = ldap_connect($this->ldap_uri);
if(!$conn)
return false;
if(!ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3))
return false;

$success = false;

$user_split = explode('@', $username, 2);
$ldap_user = $user_split[0];
$ldap_domain = '';
if (count($user_split) > 1)
$ldap_domain = $user_split[1];
$domain_split = array_reverse(explode('.', $ldap_domain));

$dn = str_replace('%u', $username, $this->ldap_dn);
$dn = str_replace('%U', $ldap_user, $dn);
$dn = str_replace('%d', $ldap_domain, $dn);
for($i = 1; $i <= count($domain_split) and $i <= 9; $i++)
$dn = str_replace('%' . $i, $domain_split[$i - 1], $dn);

try {
$bind = ldap_bind($conn, $dn, $password);
if ($bind) {
$success = true;
}
} catch (\ErrorException $e) {
error_log($e->getMessage());
error_log(ldap_error($conn));
}

if($success){
$stmt = $this->pdo->prepare('SELECT username, digesta1 FROM ' . $this->table_name . ' WHERE username = ?');
$stmt->execute([$username]);
$result = $stmt->fetchAll();

if (empty($result)) {
$search_results = ldap_read($conn, $dn, '(objectclass=*)', array($this->ldap_cn, $this->ldap_mail));
$entry = ldap_get_entries($conn, $search_results);
$user_displayname = $username;
$user_email = 'unset-email';
if (!empty($entry[0][$this->ldap_cn]))
$user_displayname = $entry[0][$this->ldap_cn][0];
if (!empty($entry[0][$this->ldap_mail]))
$user_email = $entry[0][$this->ldap_mail][0];

$user = new \Baikal\Model\User();
$user->set('username', $username);
$user->set('displayname', $user_displayname);
$user->set('email', $user_email);
$user->persist();
}
}

ldap_close($conn);

return $success;
}

/**
* Validates a username and password by trying to authenticate against LDAP.
*
* @param string $username
* @param string $password
*
* @return bool
*/
protected function validateUserPass($username, $password)
{
return $this->ldapOpen($username, $password);
}
}
9 changes: 8 additions & 1 deletion Core/Frameworks/Baikal/Core/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ protected function initServer() {
$authBackend = new \Baikal\Core\PDOBasicAuth($this->pdo, $this->authRealm);
} elseif ($this->authType === 'Apache') {
$authBackend = new \Sabre\DAV\Auth\Backend\Apache();
} elseif ($this->authType === 'LDAP') {
$authBackend = new \Baikal\Core\LDAP($this->pdo, 'users', $config['system']['ldap_uri'], $config['system']['ldap_dn'], $config['system']['ldap_cn'], $config['system']['ldap_mail']);
} else {
$authBackend = new \Sabre\DAV\Auth\Backend\PDO($this->pdo);
$authBackend->setRealm($this->authRealm);
Expand Down Expand Up @@ -174,7 +176,12 @@ protected function initServer() {
$this->server->addPlugin(new \Sabre\DAV\Sharing\Plugin());
$this->server->addPlugin(new \Sabre\CalDAV\SharingPlugin());
if (isset($config['system']["invite_from"]) && $config['system']["invite_from"] !== "") {
$this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($config['system']["invite_from"]));
if($config['system']['use_smtp']) {
$this->server->addPlugin(new \Baikal\Core\IMipSMTPPlugin($config['system']["invite_from"], $config['system']['smtp_host'], $config['system']['smtp_port'], $config['system']['smtp_username'], $config['system']['smtp_password']));
}
else {
$this->server->addPlugin(new \Sabre\CalDAV\Schedule\IMipPlugin($config['system']["invite_from"]));
}
}
}
if ($this->enableCardDAV) {
Expand Down
Loading

0 comments on commit 9ef2ecc

Please sign in to comment.