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