From 61738782c0ad8894c29e66ace9ae3944dc46e2ee Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 4 Jun 2020 01:18:24 -0400 Subject: [PATCH 1/3] fix #1107 --- conventional.yaml | 15 +++++++ docs/MANUAL.md | 32 ++++++++++++++ irc/accounts.go | 97 ++++++++++++++++++++++++++++++----------- irc/authscript.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++ irc/config.go | 10 +++++ irc/errors.go | 1 + oragono.yaml | 15 +++++++ 7 files changed, 253 insertions(+), 26 deletions(-) create mode 100644 irc/authscript.go diff --git a/conventional.yaml b/conventional.yaml index fe3e5bbb..bbb631d0 100644 --- a/conventional.yaml +++ b/conventional.yaml @@ -491,6 +491,21 @@ accounts: # attributes: # member-of: "memberOf" + # pluggable authentication mechanism, via subprocess invocation + # see the manual for details on how to write an authentication plugin script + auth-script: + enabled: false + command: "/usr/local/bin/authenticate-irc-user" + # constant list of args to pass to the command; the actual authentication + # data is transmitted over stdin/stdout: + args: [] + # should we automatically create users if the plugin returns success? + autocreate: true + # timeout for process execution, after which we send a SIGTERM: + timeout: 9s + # how long after the SIGTERM before we follow up with a SIGKILL: + kill-timeout: 1s + # channel options channels: # modes that are set when new channels are created diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 9adad8db..17682274 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -47,6 +47,7 @@ _Copyright © Daniel Oaks , Shivaram Lingamneni Date: Thu, 4 Jun 2020 02:03:15 -0400 Subject: [PATCH 2/3] review fix --- docs/MANUAL.md | 19 ++++++++++--------- irc/accounts.go | 5 +++-- irc/authscript.go | 13 +++++++------ 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 17682274..7eb8f7a4 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -851,15 +851,16 @@ Oragono can emulate certain capabilities of the ZNC bouncer for the benefit of c Oragono can be configured to call arbitrary scripts to authenticate users; see the `auth-script` section of the config. The API for these scripts is as follows: Oragono will invoke the script with a configurable set of arguments, then send it the authentication data as JSON on the first line (`\n`-terminated) of stdin. The input is a JSON-encoded dictionary with the following keys: -* `AccountName`: this is a string during passphrase-based authentication, otherwise the empty string -* `Passphrase`: this is a string during passphrase-based authentication, otherwise the empty string -* `Certfp`: this is a string during certfp-based authentication, otherwise the empty string +* `accountName`: during passphrase-based authentication, this is a string, otherwise omitted +* `passphrase`: during passphrase-based authentication, this is a string, otherwise omitted +* `certfp`: during certfp-based authentication, this is a string, otherwise omitted +* `ip`: a string representation of the client's IP address The script must print a single line (`\n`-terminated) to its output and exit. This line must be a JSON-encoded dictionary with the following keys: -* `Success`, a boolean indicating whether the authentication was successful -* `AccountName`, a string containing the normalized account name (in the case of passphrase-based authentication, it is permissible to return the empty string or omit the value) -* `Error`, containing a human-readable description of the authentication error to be logged if applicable +* `success`, a boolean indicating whether the authentication was successful +* `accountName`, a string containing the normalized account name (in the case of passphrase-based authentication, it is permissible to return the empty string or omit the value) +* `error`, containing a human-readable description of the authentication error to be logged if applicable Here is a toy example of an authentication script in Python that checks that the account name and the password are equal (and rejects any attempts to authenticate via certfp): @@ -870,10 +871,10 @@ import sys, json raw_input = sys.stdin.readline() input = json.loads(b) -account_name = input.get("AccountName") -passphrase = input.get("Passphrase") +account_name = input.get("accountName") +passphrase = input.get("passphrase") success = bool(account_name) and bool(passphrase) and account_name == passphrase -print(json.dumps({"Success": success}) +print(json.dumps({"success": success}) ``` Note that after a failed script invocation, Oragono will proceed to check the credentials against its local database. diff --git a/irc/accounts.go b/irc/accounts.go index 08f37cfc..271dc04e 100644 --- a/irc/accounts.go +++ b/irc/accounts.go @@ -1073,7 +1073,7 @@ func (am *AccountManager) AuthenticateByPassphrase(client *Client, accountName s if config.Accounts.AuthScript.Enabled { var output AuthScriptOutput output, err = CheckAuthScript(config.Accounts.AuthScript, - AuthScriptInput{AccountName: accountName, Passphrase: passphrase}) + AuthScriptInput{AccountName: accountName, Passphrase: passphrase, IP: client.IP().String()}) if err != nil { am.server.logger.Error("internal", "failed shell auth invocation", err.Error()) return err @@ -1411,7 +1411,8 @@ func (am *AccountManager) AuthenticateByCertFP(client *Client, certfp, authzid s config := am.server.Config() if config.Accounts.AuthScript.Enabled { var output AuthScriptOutput - output, err = CheckAuthScript(config.Accounts.AuthScript, AuthScriptInput{Certfp: certfp}) + output, err = CheckAuthScript(config.Accounts.AuthScript, + AuthScriptInput{Certfp: certfp, IP: client.IP().String()}) if err != nil { am.server.logger.Error("internal", "failed shell auth invocation", err.Error()) return err diff --git a/irc/authscript.go b/irc/authscript.go index 479fbe40..4bf6bb1e 100644 --- a/irc/authscript.go +++ b/irc/authscript.go @@ -15,15 +15,16 @@ import ( // JSON-serializable input and output types for the script type AuthScriptInput struct { - AccountName string - Passphrase string - Certfp string + AccountName string `json:"accountName,omitempty"` + Passphrase string `json:"passphrase,omitempty"` + Certfp string `json:"certfp,omitempty"` + IP string `json:"ip,omitempty"` } type AuthScriptOutput struct { - AccountName string - Success bool - Error string + AccountName string `json:"accountName"` + Success bool `json:"success"` + Error string `json:"error"` } // internal tupling of output and error for passing over a channel From 11a2cec003089292df23ba768b68d741a51d6d8f Mon Sep 17 00:00:00 2001 From: Shivaram Lingamneni Date: Thu, 4 Jun 2020 10:11:10 -0400 Subject: [PATCH 3/3] review fix --- docs/MANUAL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 7eb8f7a4..ea58261e 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -849,14 +849,14 @@ Oragono can emulate certain capabilities of the ZNC bouncer for the benefit of c ## External authentication systems -Oragono can be configured to call arbitrary scripts to authenticate users; see the `auth-script` section of the config. The API for these scripts is as follows: Oragono will invoke the script with a configurable set of arguments, then send it the authentication data as JSON on the first line (`\n`-terminated) of stdin. The input is a JSON-encoded dictionary with the following keys: +Oragono can be configured to call arbitrary scripts to authenticate users; see the `auth-script` section of the config. The API for these scripts is as follows: Oragono will invoke the script with a configurable set of arguments, then send it the authentication data as JSON on the first line (`\n`-terminated) of stdin. The input is a JSON dictionary with the following keys: * `accountName`: during passphrase-based authentication, this is a string, otherwise omitted * `passphrase`: during passphrase-based authentication, this is a string, otherwise omitted * `certfp`: during certfp-based authentication, this is a string, otherwise omitted * `ip`: a string representation of the client's IP address -The script must print a single line (`\n`-terminated) to its output and exit. This line must be a JSON-encoded dictionary with the following keys: +The script must print a single line (`\n`-terminated) to its output and exit. This line must be a JSON dictionary with the following keys: * `success`, a boolean indicating whether the authentication was successful * `accountName`, a string containing the normalized account name (in the case of passphrase-based authentication, it is permissible to return the empty string or omit the value)