Skip to content

Commit

Permalink
add support of OAUTHBEARER (#79)
Browse files Browse the repository at this point in the history
* add support of OAUTHBEARER

Signed-off-by: Edouard Vanbelle <[email protected]>

* add OAUTH pseudo method

   * OAUTH pseudo method will elect either XOAUTH2 or OAUTHBEARER according to server's capabilities

Signed-off-by: Edouard Vanbelle <[email protected]>

---------

Signed-off-by: Edouard Vanbelle <[email protected]>
  • Loading branch information
EdouardVanbelle authored Feb 9, 2024
1 parent 0ae529d commit 57c969e
Showing 1 changed file with 63 additions and 9 deletions.
72 changes: 63 additions & 9 deletions Net/SMTP.php
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ public function __construct($host = null, $port = null, $localhost = null,
$this->setAuthMethod('LOGIN', array($this, 'authLogin'), false);
$this->setAuthMethod('PLAIN', array($this, 'authPlain'), false);
$this->setAuthMethod('XOAUTH2', array($this, 'authXOAuth2'), false);
$this->setAuthMethod('OAUTHBEARER', array($this, 'authOAuthBearer'), false);
}

/**
Expand Down Expand Up @@ -708,14 +709,17 @@ public function starttls()

return true;
}

/**
* Attempt to do SMTP authentication.
*
* @param string $uid The userid to authenticate as.
* @param string $pwd The password to authenticate with.
* @param string $method The requested authentication method. If none is
* @param string $method The requested authentication method. If none is
* specified, the best supported method will be used.
* If you use the special method `OAUTH`, library
* will choose between OAUTHBEARER or XOAUTH2
* according the server's capabilities.
* @param bool $tls Flag indicating whether or not TLS should be attempted.
* @param string $authz An optional authorization identifier. If specified, this
* identifier will be used as the authorization proxy.
Expand Down Expand Up @@ -749,6 +753,19 @@ public function auth($uid, $pwd , $method = '', $tls = true, $authz = '')
/* Return the PEAR_Error object from _getBestAuthMethod(). */
return $method;
}
} elseif ($method === 'OAUTH') {
// special case of OAUTH, use the supported method
$found = false;
$available_methods = explode(' ', $this->esmtp['AUTH']);
foreach (['OAUTHBEARER', 'XOAUTH2'] as $method) {
if (in_array($method, $available_methods)) {
$found = true;
break;
}
}
if (!$found) {
return PEAR::raiseError("neither OAUTHBEARER nor XOAUTH2 is a supported authentication method");
}
} else {
$method = strtoupper($method);
if (!array_key_exists($method, $this->auth_methods)) {
Expand Down Expand Up @@ -1101,28 +1118,65 @@ protected function authGSSAPI($uid, $pwd, $authz = '')
* Authenticates the user using the XOAUTH2 method.
*
* @param string $uid The userid to authenticate as.
* @param string $token The access token to authenticate with.
* @param string $token The access token prefixed by it's type
* example: "Bearer $access_token".
* @param string $authz The optional authorization proxy identifier.
* @param object $conn The current object
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @since 1.9.0
*/
//FIXME: to switch into protected method on next major release
public function authXOAuth2($uid, $token, $authz, $conn)
{
$auth = base64_encode("user=$uid\1auth=$token\1\1");
return $this->authenticateOAuth('XOAUTH2', $auth, $authz, $conn);
}

// Maximum length of the base64-encoded token to be sent in the initial response is 497 bytes, according to
// RFC 4954 (https://datatracker.ietf.org/doc/html/rfc4954); for longer tokens an empty initial
/**
* Authenticates the user using the OAUTHBEARER method.
*
* @param string $uid The userid to authenticate as.
* @param string $token The access token prefixed by it's type
* example: "Bearer $access_token".
* @param string $authz The optional authorization proxy identifier.
* @param object $conn The current object
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @since 1.9.3
* @see https://www.rfc-editor.org/rfc/rfc7628.html
*/
protected function authOAuthBearer($uid, $token, $authz, $conn)
{
$auth = base64_encode("n,a=$uid\1auth=$token\1\1");
return $this->authenticateOAuth('OAUTHBEARER', $auth, $authz, $conn);
}

/**
* Authenticates the user using the OAUTHBEARER or XOAUTH2 method.
*
* @param string $method The method (OAUTHBEARER or XOAUTH2)
* @param string $auth The authentication string (base64 coded)
* @param string $authz The optional authorization proxy identifier.
* @param object $conn The current object
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
*/
protected function authenticateOAuth( $method, $auth, $authz, $conn)
{
// Maximum length of the base64-encoded token to be sent in the initial response is 504 - strlen($method) bytes,
// according to RFC 4954 (https://datatracker.ietf.org/doc/html/rfc4954); for longer tokens an empty initial
// response MUST be sent and the token must be sent separately
// (497 bytes = 512 bytes /SMTP command length limit/ - 13 bytes /"AUTH XOAUTH2 "/ - 2 bytes /CRLF/)
if (strlen($auth) <= 497) {
if (PEAR::isError($error = $this->put('AUTH', 'XOAUTH2 ' . $auth))) {
// (504 bytes = /SMTP command length limit/ - 6 bytes /"AUTH "/ -strlen($method) - 1 byte /" "/ - 2 bytes /CRLF/)
if (strlen($auth) <= (504-strlen($method))) {
if (PEAR::isError($error = $this->put('AUTH', $method . ' ' . $auth))) {
return $error;
}
} else {
if (PEAR::isError($error = $this->put('AUTH', 'XOAUTH2'))) {
if (PEAR::isError($error = $this->put('AUTH', $method))) {
return $error;
}

Expand Down

0 comments on commit 57c969e

Please sign in to comment.