Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ Skip to [restart Postfix](#restart-postfix) below.

#### Client Credentials

There's both the internally implemented token management, as well as the
possibility to use tokens managed externally (i. e. when already having
the proper OAuth2 tokens managed by a different application work-flow).

##### Internal management of client credentials
Follow [Microsoft's instructions to register an
application](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application).
Use any name you like (it doesn't have to be "sasl-xoauth2"). Under "Platform
Expand All @@ -205,7 +210,32 @@ points to Gmail by default):
}
```

We'll also need these credentials in the next step.
We'll also need these credentials in the next step ("Initial Access Token").

#### External token management

When running multiple applications using the same OAuth2 access (i. e. fetchmail and Postfix),
you may already have the according token management processes in place (and providing an always
current token in a user-specific file).

Under such circumstances, using another token manager may be problematic, leading to possible
token contention and other difficulties.

Assuming that the token management is already in place and provides a token file per user (and of course
maintaining that file to always contain the then valid token), you can configure sasl-xoauth2 to
simply read that user token file, without further interaction with upstream token issuers.

When doing so, you provide the token file name as the SASL password and need to set "external_token_manager" to "yes"
in sasl-xoauth's configuration file:

```json
{
"external_token_manager": "yes"
}
```

This setting will apply to all authentications handled by this plug-in. Please also make sure that the user
under which this plug-in is run, has access to the token file's content.

#### A Note on Token Endpoints

Expand Down
33 changes: 27 additions & 6 deletions src/client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
#include <string.h>

#include <sstream>
#include <fstream>

#include "config.h"
#include "log.h"
#include "token_store.h"

Expand Down Expand Up @@ -207,8 +209,13 @@ int Client::InitialStep(sasl_client_params_t *params,
if (err != SASL_OK) return err;

user_ = auth_name;
token_ = TokenStore::Create(log_.get(), password);
if (!token_) return SASL_FAIL;

if (Config::Get()->external_token_manager() == false) {
token_ = TokenStore::Create(log_.get(), password);
if (!token_) return SASL_FAIL;
} else {
tokenfile_ = password;
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's cleaner overall if instead of having two different code paths here, in Client, we push this complexity into TokenStore by adding a flag there that just disables refreshes. Client would remain unmodified except that it passes the value of manage_token_externally to TokenStore, and TokenStore would just always return whatever's in the token file, regardless of the expiry time.


err = SendToken(to_server, to_server_len);
if (err != SASL_OK) return err;
Expand Down Expand Up @@ -244,8 +251,10 @@ int Client::TokenSentStep(sasl_client_params_t *params,
}

if (status == "400" || status == "401") {
int err = token_->Refresh();
if (err != SASL_OK) return err;
if (Config::Get()->external_token_manager() == false) {
int err = token_->Refresh();
if (err != SASL_OK) return err;
}
return SASL_TRYAGAIN;
}

Expand All @@ -260,8 +269,20 @@ int Client::TokenSentStep(sasl_client_params_t *params,

int Client::SendToken(const char **to_server, unsigned int *to_server_len) {
std::string token;
int err = token_->GetAccessToken(&token);
if (err != SASL_OK) return err;

if (Config::Get()->external_token_manager() == false) {
int err = token_->GetAccessToken(&token);
if (err != SASL_OK) return err;
} else {
std::ifstream file(tokenfile_);
if (!file.good()) {
log_->Write("Client::SendToken: failed to open file %s: %s", tokenfile_.c_str(),
strerror(errno));
return SASL_FAIL;
} else {
file >> token;
}
}

response_ = "user=" + user_ + "\1auth=Bearer " + token + "\1\1";
log_->Write("Client::SendToken: response: %s", response_.c_str());
Expand Down
1 change: 1 addition & 0 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Client {

State state_ = State::kInitial;
std::string user_;
std::string tokenfile_;
std::string response_;

// Order of destruction matters -- token_ holds a pointer to log_.
Expand Down
19 changes: 13 additions & 6 deletions src/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,7 @@ int Config::Init(const Json::Value &root) {
try {
int err;

err = Fetch(root, "client_id", false, &client_id_);
if (err != SASL_OK) return err;

err = Fetch(root, "client_secret", false, &client_secret_);
err = Fetch(root, "external_token_manager", false, &external_token_manager_);
if (err != SASL_OK) return err;

err = Fetch(root, "log_to_syslog_on_failure", true,
Expand All @@ -148,8 +145,18 @@ int Config::Init(const Json::Value &root) {
&log_full_trace_on_failure_);
if (err != SASL_OK) return err;

err = Fetch(root, "token_endpoint", true, &token_endpoint_);
if (err != SASL_OK) return err;
// the internal token manager needs a number of configuration parameters
if (!external_token_manager_) {

err = Fetch(root, "client_id", false, &client_id_);
if (err != SASL_OK) return err;

err = Fetch(root, "client_secret", false, &client_secret_);
if (err != SASL_OK) return err;

err = Fetch(root, "token_endpoint", true, &token_endpoint_);
if (err != SASL_OK) return err;
}

return 0;

Expand Down
2 changes: 2 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class Config {

static Config *Get();

bool external_token_manager() const { return external_token_manager_; }
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call this "manage_token_externally"?

std::string client_id() const { return client_id_; }
std::string client_secret() const { return client_secret_; }
bool log_to_syslog_on_failure() const { return log_to_syslog_on_failure_; }
Expand All @@ -42,6 +43,7 @@ class Config {

std::string client_id_;
std::string client_secret_;
bool external_token_manager_ = false;
bool log_to_syslog_on_failure_ = true;
bool log_full_trace_on_failure_ = false;
std::string token_endpoint_ = "https://accounts.google.com/o/oauth2/token";
Expand Down
1 change: 1 addition & 0 deletions src/sasl-xoauth2.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"external_token_manager": "no",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to leave this out of the sample config file, since I imagine most users won't need it and I'd like to avoid the confusion.

"client_id": "CLIENT_ID_GOES_HERE",
"client_secret": "CLIENT_SECRET_GOES_HERE"
}