Commit 6b76f1bb authored by Magnus Hagander's avatar Magnus Hagander

Support multiple RADIUS servers

This changes all the RADIUS related parameters (radiusserver,
radiussecret, radiusport, radiusidentifier) to be plural and to accept a
comma separated list of servers, which will be tried in order.

Reviewed by Adam Brightwell
parent c137c68e
......@@ -1621,24 +1621,36 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub"
<literal>Access Reject</>. There is no support for RADIUS accounting.
</para>
<para>
Multiple RADIUS servers can be specified, in which case they will
be tried sequentially. If a negative response is received from
a server, the authentication will fail. If no response is received,
the next server in the list will be tried. To specify multiple
servers, put the names within quotes and separate the server names
with a comma. If multiple servers are specified, all other RADIUS
options can also be given as a comma separate list, to apply
individual values to each server. They can also be specified as
a single value, in which case this value will apply to all servers.
</para>
<para>
The following configuration options are supported for RADIUS:
<variablelist>
<varlistentry>
<term><literal>radiusserver</literal></term>
<term><literal>radiusservers</literal></term>
<listitem>
<para>
The name or IP address of the RADIUS server to connect to.
The name or IP addresses of the RADIUS servers to connect to.
This parameter is required.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>radiussecret</literal></term>
<term><literal>radiussecrets</literal></term>
<listitem>
<para>
The shared secret used when talking securely to the RADIUS
The shared secrets used when talking securely to the RADIUS
server. This must have exactly the same value on the PostgreSQL
and RADIUS servers. It is recommended that this be a string of
at least 16 characters. This parameter is required.
......@@ -1656,17 +1668,17 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub"
</varlistentry>
<varlistentry>
<term><literal>radiusport</literal></term>
<term><literal>radiusports</literal></term>
<listitem>
<para>
The port number on the RADIUS server to connect to. If no port
The port number on the RADIUS servers to connect to. If no port
is specified, the default port <literal>1812</> will be used.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>radiusidentifier</literal></term>
<term><literal>radiusidentifiers</literal></term>
<listitem>
<para>
The string used as <literal>NAS Identifier</> in the RADIUS
......
This diff is collapsed.
......@@ -39,6 +39,7 @@
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/varlena.h"
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
......@@ -143,6 +144,8 @@ static List *tokenize_inc_file(List *tokens, const char *outer_filename,
const char *inc_filename, int elevel, char **err_msg);
static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
int elevel, char **err_msg);
static bool verify_option_list_length(List *options, char *optionname,
List *masters, char *mastername, int line_num);
static ArrayType *gethba_options(HbaLine *hba);
static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
int lineno, HbaLine *hba, const char *err_msg);
......@@ -1532,8 +1535,52 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
if (parsedline->auth_method == uaRADIUS)
{
MANDATORY_AUTH_ARG(parsedline->radiusserver, "radiusserver", "radius");
MANDATORY_AUTH_ARG(parsedline->radiussecret, "radiussecret", "radius");
MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
if (list_length(parsedline->radiusservers) < 1)
{
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("list of RADIUS servers cannot be empty"),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return NULL;
}
if (list_length(parsedline->radiussecrets) < 1)
{
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("list of RADIUS secrets cannot be empty"),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return NULL;
}
/*
* Verify length of option lists - each can be 0 (except for secrets,
* but that's already checked above), 1 (use the same value
* everywhere) or the same as the number of servers.
*/
if (!verify_option_list_length(parsedline->radiussecrets,
"RADIUS secrets",
parsedline->radiusservers,
"RADIUS servers",
line_num))
return NULL;
if (!verify_option_list_length(parsedline->radiusports,
"RADIUS ports",
parsedline->radiusservers,
"RADIUS servers",
line_num))
return NULL;
if (!verify_option_list_length(parsedline->radiusidentifiers,
"RADIUS identifiers",
parsedline->radiusservers,
"RADIUS servers",
line_num))
return NULL;
}
/*
......@@ -1547,6 +1594,28 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
return parsedline;
}
static bool
verify_option_list_length(List *options, char *optionname, List *masters, char *mastername, int line_num)
{
if (list_length(options) == 0 ||
list_length(options) == 1 ||
list_length(options) == list_length(masters))
return true;
ereport(LOG,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("the number of %s (%i) must be 1 or the same as the number of %s (%i)",
optionname,
list_length(options),
mastername,
list_length(masters)
),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return false;
}
/*
* Parse one name-value pair as an authentication option into the given
* HbaLine. Return true if we successfully parse the option, false if we
......@@ -1766,60 +1835,137 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
else
hbaline->upn_username = false;
}
else if (strcmp(name, "radiusserver") == 0)
else if (strcmp(name, "radiusservers") == 0)
{
struct addrinfo *gai_result;
struct addrinfo hints;
int ret;
List *parsed_servers;
ListCell *l;
char *dupval = pstrdup(val);
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
MemSet(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM;
hints.ai_family = AF_UNSPEC;
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
if (ret || !gai_result)
if (!SplitIdentifierString(dupval, ',', &parsed_servers))
{
/* syntax error in list */
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not translate RADIUS server name \"%s\" to address: %s",
val, gai_strerror(ret)),
errmsg("could not parse RADIUS server list \"%s\"",
val),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = psprintf("could not translate RADIUS server name \"%s\" to address: %s",
val, gai_strerror(ret));
if (gai_result)
pg_freeaddrinfo_all(hints.ai_family, gai_result);
return false;
}
pg_freeaddrinfo_all(hints.ai_family, gai_result);
hbaline->radiusserver = pstrdup(val);
/* For each entry in the list, translate it */
foreach(l, parsed_servers)
{
MemSet(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_DGRAM;
hints.ai_family = AF_UNSPEC;
ret = pg_getaddrinfo_all((char *) lfirst(l), NULL, &hints, &gai_result);
if (ret || !gai_result)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not translate RADIUS server name \"%s\" to address: %s",
(char *) lfirst(l), gai_strerror(ret)),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
if (gai_result)
pg_freeaddrinfo_all(hints.ai_family, gai_result);
list_free(parsed_servers);
return false;
}
pg_freeaddrinfo_all(hints.ai_family, gai_result);
}
/* All entries are OK, so store them */
hbaline->radiusservers = parsed_servers;
hbaline->radiusservers_s = pstrdup(val);
}
else if (strcmp(name, "radiusport") == 0)
else if (strcmp(name, "radiusports") == 0)
{
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
hbaline->radiusport = atoi(val);
if (hbaline->radiusport == 0)
List *parsed_ports;
ListCell *l;
char *dupval = pstrdup(val);
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
if (!SplitIdentifierString(dupval, ',', &parsed_ports))
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("invalid RADIUS port number: \"%s\"", val),
errmsg("could not parse RADIUS port list \"%s\"",
val),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
*err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
return false;
}
foreach(l, parsed_ports)
{
if (atoi(lfirst(l)) == 0)
{
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("invalid RADIUS port number: \"%s\"", val),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return false;
}
}
hbaline->radiusports = parsed_ports;
hbaline->radiusports_s = pstrdup(val);
}
else if (strcmp(name, "radiussecret") == 0)
else if (strcmp(name, "radiussecrets") == 0)
{
REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
hbaline->radiussecret = pstrdup(val);
List *parsed_secrets;
char *dupval = pstrdup(val);
REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
if (!SplitIdentifierString(dupval, ',', &parsed_secrets))
{
/* syntax error in list */
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not parse RADIUS secret list \"%s\"",
val),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return false;
}
hbaline->radiussecrets = parsed_secrets;
hbaline->radiussecrets_s = pstrdup(val);
}
else if (strcmp(name, "radiusidentifier") == 0)
else if (strcmp(name, "radiusidentifiers") == 0)
{
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
hbaline->radiusidentifier = pstrdup(val);
List *parsed_identifiers;
char *dupval = pstrdup(val);
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
if (!SplitIdentifierString(dupval, ',', &parsed_identifiers))
{
/* syntax error in list */
ereport(elevel,
(errcode(ERRCODE_CONFIG_FILE_ERROR),
errmsg("could not parse RADIUS identifiers list \"%s\"",
val),
errcontext("line %d of configuration file \"%s\"",
line_num, HbaFileName)));
return false;
}
hbaline->radiusidentifiers = parsed_identifiers;
hbaline->radiusidentifiers_s = pstrdup(val);
}
else
{
......@@ -2124,21 +2270,21 @@ gethba_options(HbaLine *hba)
if (hba->auth_method == uaRADIUS)
{
if (hba->radiusserver)
if (hba->radiusservers_s)
options[noptions++] =
CStringGetTextDatum(psprintf("radiusserver=%s", hba->radiusserver));
CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
if (hba->radiussecret)
if (hba->radiussecrets_s)
options[noptions++] =
CStringGetTextDatum(psprintf("radiussecret=%s", hba->radiussecret));
CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
if (hba->radiusidentifier)
if (hba->radiusidentifiers_s)
options[noptions++] =
CStringGetTextDatum(psprintf("radiusidentifier=%s", hba->radiusidentifier));
CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
if (hba->radiusport)
if (hba->radiusports_s)
options[noptions++] =
CStringGetTextDatum(psprintf("radiusport=%d", hba->radiusport));
CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
}
Assert(noptions <= MAX_HBA_OPTIONS);
......
......@@ -89,10 +89,14 @@ typedef struct HbaLine
bool include_realm;
bool compat_realm;
bool upn_username;
char *radiusserver;
char *radiussecret;
char *radiusidentifier;
int radiusport;
List *radiusservers;
char *radiusservers_s;
List *radiussecrets;
char *radiussecrets_s;
List *radiusidentifiers;
char *radiusidentifiers_s;
List *radiusports;
char *radiusports_s;
} HbaLine;
typedef struct IdentLine
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment