Support multiple RADIUS servers
authorMagnus Hagander <magnus@hagander.net>
Wed, 22 Mar 2017 16:55:16 +0000 (17:55 +0100)
committerMagnus Hagander <magnus@hagander.net>
Wed, 22 Mar 2017 17:11:08 +0000 (18:11 +0100)
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

doc/src/sgml/client-auth.sgml
src/backend/libpq/auth.c
src/backend/libpq/hba.c
src/include/libpq/hba.h

index d6b8c04edc5387ea23f7cb5ac2aa3dacc5f5be41..28f5296b5af21a961397213af1faf69de42573de 100644 (file)
@@ -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
index ebf10bbbaefe94e3041ec89ca2da72f3125e325f..a699a09e9ad4ce4f5c5ac6e52675444719d8d981 100644 (file)
@@ -197,6 +197,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);
 
 
 /*----------------------------------------------------------------
@@ -2591,7 +2592,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;
@@ -2613,7 +2704,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;
@@ -2621,69 +2712,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;
@@ -2695,7 +2746,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));
 
        /*
@@ -2705,14 +2756,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
@@ -2720,7 +2771,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")));
@@ -2812,7 +2863,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;
                }
@@ -2837,7 +2889,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;
                }
@@ -2864,19 +2917,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;
                }
@@ -2884,23 +2937,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;
                }
 
@@ -2908,7 +2961,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
@@ -2917,10 +2970,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,
@@ -2933,7 +2986,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;
                }
 
@@ -2945,13 +2999,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) */
index 7abcae618db5ece859c1c4f1456c6bacebd7e045..49be6638b8606de2e66985e764a1749e61f6cac4 100644 (file)
@@ -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);
index 8f55edb16aa3d7bcfd4169a0cc894d860e71581d..6c7382e67fdb23c6687d4c6a90af2da324d56b4d 100644 (file)
@@ -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