In a discussion at
https://www.postgresql.org/message-id/55d51b54.4050...@joh.to we talked
about adding RADIUS fallback servers. It never got to the point of it being
done.

PFA a patch that implements this.

It supports multiple RADIUS servers. For all other parameters (secret,
port, identifier) one can specify either the exact same number of entries,
in which case each server gets it's own, or exactly one entry in which case
that entry will apply to all servers. (Or zero entries for everything
except secret, which will make it the default).

Each server is tried in order. If it responds positive, auth is OK. If it
responds negative, auth is rejected. If it does not respond at all, we move
on to the next one.

I'm wondering if in doing this we should also make the RADIUS timeout a
configurable as HBA option, since it might become more important now?

-- 
 Magnus Hagander
 Me: http://www.hagander.net/
 Work: http://www.redpill-linpro.com/
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 231fc40..f1ecf95 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -1589,23 +1589,35 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub";
    </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.
@@ -1623,17 +1635,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
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 824e408..e033b24 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -195,6 +195,7 @@ static int pg_SSPI_make_upn(char *accountname,
  *----------------------------------------------------------------
  */
 static int	CheckRADIUSAuth(Port *port);
+static int	PerformRadiusTransaction(char *server, char *secret, char *portstr, char *identifier, char *user_name, char *passwd);
 
 
 /*
@@ -2455,7 +2456,97 @@ static int
 CheckRADIUSAuth(Port *port)
 {
 	char	   *passwd;
-	char	   *identifier = "postgresql";
+	ListCell   *server,
+			   *secrets,
+			   *radiusports,
+			   *identifiers;
+
+	/* Make sure struct alignment is correct */
+	Assert(offsetof(radius_packet, vector) == 4);
+
+	/* Verify parameters */
+	if (list_length(port->hba->radiusservers) < 1)
+	{
+		ereport(LOG,
+				(errmsg("RADIUS server not specified")));
+		return STATUS_ERROR;
+	}
+
+	if (list_length(port->hba->radiussecrets) < 1)
+	{
+		ereport(LOG,
+				(errmsg("RADIUS secret not specified")));
+		return STATUS_ERROR;
+	}
+
+	/* Send regular password request to client, and get the response */
+	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
+
+	passwd = recv_password_packet(port);
+	if (passwd == NULL)
+		return STATUS_EOF;		/* client wouldn't send password */
+
+	if (strlen(passwd) == 0)
+	{
+		ereport(LOG,
+				(errmsg("empty password returned by client")));
+		return STATUS_ERROR;
+	}
+
+	if (strlen(passwd) > RADIUS_MAX_PASSWORD_LENGTH)
+	{
+		ereport(LOG,
+				(errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH)));
+		return STATUS_ERROR;
+	}
+
+	/*
+	 * Loop over and try each server in order.
+	 */
+	secrets = list_head(port->hba->radiussecrets);
+	radiusports = list_head(port->hba->radiusports);
+	identifiers = list_head(port->hba->radiusidentifiers);
+	foreach(server, port->hba->radiusservers)
+	{
+		int			ret = PerformRadiusTransaction(lfirst(server),
+												   lfirst(secrets),
+									radiusports ? lfirst(radiusports) : NULL,
+									identifiers ? lfirst(identifiers) : NULL,
+												   port->user_name,
+												   passwd);
+
+		/*------
+		 * STATUS_OK = Login OK
+		 * STATUS_ERROR = Login not OK, but try next server
+		 * STATUS_EOF = Login not OK, and don't try next server
+		 *------
+		 */
+		if (ret == STATUS_OK)
+			return STATUS_OK;
+		else if (ret == STATUS_EOF)
+			return STATUS_ERROR;
+
+		/*
+		 * secret, port and identifiers either have length 0 (use default),
+		 * length 1 (use the same everywhere) or the same length as servers.
+		 * So if the length is >1, we advance one step. In other cases, we
+		 * don't and will then reuse the correct value.
+		 */
+		if (list_length(port->hba->radiussecrets) > 1)
+			secrets = lnext(secrets);
+		if (list_length(port->hba->radiusports) > 1)
+			radiusports = lnext(radiusports);
+		if (list_length(port->hba->radiusidentifiers) > 1)
+			identifiers = lnext(identifiers);
+	}
+
+	/* No servers left to try, so give up */
+	return STATUS_ERROR;
+}
+
+static int
+PerformRadiusTransaction(char *server, char *secret, char *portstr, char *identifier, char *user_name, char *passwd)
+{
 	char		radius_buffer[RADIUS_BUFFER_SIZE];
 	char		receive_buffer[RADIUS_BUFFER_SIZE];
 	radius_packet *packet = (radius_packet *) radius_buffer;
@@ -2477,7 +2568,7 @@ CheckRADIUSAuth(Port *port)
 #endif
 	struct addrinfo hint;
 	struct addrinfo *serveraddrs;
-	char		portstr[128];
+	int			port;
 	ACCEPT_TYPE_ARG3 addrsize;
 	fd_set		fdset;
 	struct timeval endtime;
@@ -2485,69 +2576,29 @@ CheckRADIUSAuth(Port *port)
 				j,
 				r;
 
-	/* Make sure struct alignment is correct */
-	Assert(offsetof(radius_packet, vector) == 4);
-
-	/* Verify parameters */
-	if (!port->hba->radiusserver || port->hba->radiusserver[0] == '\0')
-	{
-		ereport(LOG,
-				(errmsg("RADIUS server not specified")));
-		return STATUS_ERROR;
-	}
-
-	if (!port->hba->radiussecret || port->hba->radiussecret[0] == '\0')
-	{
-		ereport(LOG,
-				(errmsg("RADIUS secret not specified")));
-		return STATUS_ERROR;
-	}
-
-	if (port->hba->radiusport == 0)
-		port->hba->radiusport = 1812;
+	/* Assign default values */
+	if (portstr == NULL)
+		portstr = "1812";
+	if (identifier == NULL)
+		identifier = "postgresql";
 
 	MemSet(&hint, 0, sizeof(hint));
 	hint.ai_socktype = SOCK_DGRAM;
 	hint.ai_family = AF_UNSPEC;
-	snprintf(portstr, sizeof(portstr), "%d", port->hba->radiusport);
+	port = atoi(portstr);
 
-	r = pg_getaddrinfo_all(port->hba->radiusserver, portstr, &hint, &serveraddrs);
+	r = pg_getaddrinfo_all(server, portstr, &hint, &serveraddrs);
 	if (r || !serveraddrs)
 	{
 		ereport(LOG,
 				(errmsg("could not translate RADIUS server name \"%s\" to address: %s",
-						port->hba->radiusserver, gai_strerror(r))));
+						server, gai_strerror(r))));
 		if (serveraddrs)
 			pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
 		return STATUS_ERROR;
 	}
 	/* XXX: add support for multiple returned addresses? */
 
-	if (port->hba->radiusidentifier && port->hba->radiusidentifier[0])
-		identifier = port->hba->radiusidentifier;
-
-	/* Send regular password request to client, and get the response */
-	sendAuthRequest(port, AUTH_REQ_PASSWORD, NULL, 0);
-
-	passwd = recv_password_packet(port);
-	if (passwd == NULL)
-		return STATUS_EOF;		/* client wouldn't send password */
-
-	if (strlen(passwd) == 0)
-	{
-		ereport(LOG,
-				(errmsg("empty password returned by client")));
-		return STATUS_ERROR;
-	}
-
-	if (strlen(passwd) > RADIUS_MAX_PASSWORD_LENGTH)
-	{
-		ereport(LOG,
-				(errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH)));
-		return STATUS_ERROR;
-	}
-
-
 	/* Construct RADIUS packet */
 	packet->code = RADIUS_ACCESS_REQUEST;
 	packet->length = RADIUS_HEADER_LENGTH;
@@ -2559,7 +2610,7 @@ CheckRADIUSAuth(Port *port)
 	}
 	packet->id = packet->vector[0];
 	radius_add_attribute(packet, RADIUS_SERVICE_TYPE, (unsigned char *) &service, sizeof(service));
-	radius_add_attribute(packet, RADIUS_USER_NAME, (unsigned char *) port->user_name, strlen(port->user_name));
+	radius_add_attribute(packet, RADIUS_USER_NAME, (unsigned char *) user_name, strlen(user_name));
 	radius_add_attribute(packet, RADIUS_NAS_IDENTIFIER, (unsigned char *) identifier, strlen(identifier));
 
 	/*
@@ -2569,14 +2620,14 @@ CheckRADIUSAuth(Port *port)
 	 * (if necessary)
 	 */
 	encryptedpasswordlen = ((strlen(passwd) + RADIUS_VECTOR_LENGTH - 1) / RADIUS_VECTOR_LENGTH) * RADIUS_VECTOR_LENGTH;
-	cryptvector = palloc(strlen(port->hba->radiussecret) + RADIUS_VECTOR_LENGTH);
-	memcpy(cryptvector, port->hba->radiussecret, strlen(port->hba->radiussecret));
+	cryptvector = palloc(strlen(secret) + RADIUS_VECTOR_LENGTH);
+	memcpy(cryptvector, secret, strlen(secret));
 
 	/* for the first iteration, we use the Request Authenticator vector */
 	md5trailer = packet->vector;
 	for (i = 0; i < encryptedpasswordlen; i += RADIUS_VECTOR_LENGTH)
 	{
-		memcpy(cryptvector + strlen(port->hba->radiussecret), md5trailer, RADIUS_VECTOR_LENGTH);
+		memcpy(cryptvector + strlen(secret), md5trailer, RADIUS_VECTOR_LENGTH);
 
 		/*
 		 * .. and for subsequent iterations the result of the previous XOR
@@ -2584,7 +2635,7 @@ CheckRADIUSAuth(Port *port)
 		 */
 		md5trailer = encryptedpassword + i;
 
-		if (!pg_md5_binary(cryptvector, strlen(port->hba->radiussecret) + RADIUS_VECTOR_LENGTH, encryptedpassword + i))
+		if (!pg_md5_binary(cryptvector, strlen(secret) + RADIUS_VECTOR_LENGTH, encryptedpassword + i))
 		{
 			ereport(LOG,
 					(errmsg("could not perform MD5 encryption of password")));
@@ -2676,7 +2727,8 @@ CheckRADIUSAuth(Port *port)
 		if (timeoutval <= 0)
 		{
 			ereport(LOG,
-					(errmsg("timeout waiting for RADIUS response")));
+					(errmsg("timeout waiting for RADIUS response from %s",
+							server)));
 			closesocket(sock);
 			return STATUS_ERROR;
 		}
@@ -2701,7 +2753,8 @@ CheckRADIUSAuth(Port *port)
 		if (r == 0)
 		{
 			ereport(LOG,
-					(errmsg("timeout waiting for RADIUS response")));
+					(errmsg("timeout waiting for RADIUS response from %s",
+							server)));
 			closesocket(sock);
 			return STATUS_ERROR;
 		}
@@ -2728,19 +2781,19 @@ CheckRADIUSAuth(Port *port)
 		}
 
 #ifdef HAVE_IPV6
-		if (remoteaddr.sin6_port != htons(port->hba->radiusport))
+		if (remoteaddr.sin6_port != htons(port))
 #else
-		if (remoteaddr.sin_port != htons(port->hba->radiusport))
+		if (remoteaddr.sin_port != htons(port))
 #endif
 		{
 #ifdef HAVE_IPV6
 			ereport(LOG,
-				  (errmsg("RADIUS response was sent from incorrect port: %d",
-						  ntohs(remoteaddr.sin6_port))));
+					(errmsg("RADIUS response from %s was sent from incorrect port: %d",
+							server, ntohs(remoteaddr.sin6_port))));
 #else
 			ereport(LOG,
-				  (errmsg("RADIUS response was sent from incorrect port: %d",
-						  ntohs(remoteaddr.sin_port))));
+					(errmsg("RADIUS response from %s was sent from incorrect port: %d",
+							server, ntohs(remoteaddr.sin_port))));
 #endif
 			continue;
 		}
@@ -2748,23 +2801,23 @@ CheckRADIUSAuth(Port *port)
 		if (packetlength < RADIUS_HEADER_LENGTH)
 		{
 			ereport(LOG,
-					(errmsg("RADIUS response too short: %d", packetlength)));
+					(errmsg("RADIUS response from %s too short: %d", server, packetlength)));
 			continue;
 		}
 
 		if (packetlength != ntohs(receivepacket->length))
 		{
 			ereport(LOG,
-					(errmsg("RADIUS response has corrupt length: %d (actual length %d)",
-							ntohs(receivepacket->length), packetlength)));
+					(errmsg("RADIUS response from %s has corrupt length: %d (actual length %d)",
+					   server, ntohs(receivepacket->length), packetlength)));
 			continue;
 		}
 
 		if (packet->id != receivepacket->id)
 		{
 			ereport(LOG,
-					(errmsg("RADIUS response is to a different request: %d (should be %d)",
-							receivepacket->id, packet->id)));
+					(errmsg("RADIUS response from %s is to a different request: %d (should be %d)",
+							server, receivepacket->id, packet->id)));
 			continue;
 		}
 
@@ -2772,7 +2825,7 @@ CheckRADIUSAuth(Port *port)
 		 * Verify the response authenticator, which is calculated as
 		 * MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
 		 */
-		cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
+		cryptvector = palloc(packetlength + strlen(secret));
 
 		memcpy(cryptvector, receivepacket, 4);	/* code+id+length */
 		memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH);	/* request
@@ -2781,10 +2834,10 @@ CheckRADIUSAuth(Port *port)
 		if (packetlength > RADIUS_HEADER_LENGTH)		/* there may be no
 														 * attributes at all */
 			memcpy(cryptvector + RADIUS_HEADER_LENGTH, receive_buffer + RADIUS_HEADER_LENGTH, packetlength - RADIUS_HEADER_LENGTH);
-		memcpy(cryptvector + packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
+		memcpy(cryptvector + packetlength, secret, strlen(secret));
 
 		if (!pg_md5_binary(cryptvector,
-						   packetlength + strlen(port->hba->radiussecret),
+						   packetlength + strlen(secret),
 						   encryptedpassword))
 		{
 			ereport(LOG,
@@ -2797,7 +2850,8 @@ CheckRADIUSAuth(Port *port)
 		if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
 		{
 			ereport(LOG,
-					(errmsg("RADIUS response has incorrect MD5 signature")));
+			   (errmsg("RADIUS response from %s has incorrect MD5 signature",
+					   server)));
 			continue;
 		}
 
@@ -2809,13 +2863,13 @@ CheckRADIUSAuth(Port *port)
 		else if (receivepacket->code == RADIUS_ACCESS_REJECT)
 		{
 			closesocket(sock);
-			return STATUS_ERROR;
+			return STATUS_EOF;
 		}
 		else
 		{
 			ereport(LOG,
-			 (errmsg("RADIUS response has invalid code (%d) for user \"%s\"",
-					 receivepacket->code, port->user_name)));
+					(errmsg("RADIUS response from %s has invalid code (%d) for user \"%s\"",
+							server, receivepacket->code, user_name)));
 			continue;
 		}
 	}							/* while (true) */
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index be63a4b..9e81099 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -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"
@@ -145,6 +146,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);
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 748a072..13706ff 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -88,10 +88,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
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to