From 6b76f1bb58f53aec25cfec76391270ea36ad1170 Mon Sep 17 00:00:00 2001 From: Magnus Hagander Date: Wed, 22 Mar 2017 17:55:16 +0100 Subject: [PATCH] Support multiple RADIUS servers This changes all the RADIUS related parameters (radiusserver, radiussecret, radiusport, radiusidentifier) to be plural and to accept a comma separated list of servers, which will be tried in order. Reviewed by Adam Brightwell --- doc/src/sgml/client-auth.sgml | 26 ++-- src/backend/libpq/auth.c | 212 ++++++++++++++++++++------------ src/backend/libpq/hba.c | 220 ++++++++++++++++++++++++++++------ src/include/libpq/hba.h | 12 +- 4 files changed, 343 insertions(+), 127 deletions(-) diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml index d6b8c04edc..28f5296b5a 100644 --- a/doc/src/sgml/client-auth.sgml +++ b/doc/src/sgml/client-auth.sgml @@ -1621,24 +1621,36 @@ host ... ldap ldapurl="ldap://ldap.example.net/dc=example,dc=net?uid?sub" Access Reject. There is no support for RADIUS accounting. + + 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. + + The following configuration options are supported for RADIUS: - radiusserver + radiusservers - 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. - radiussecret + radiussecrets - 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" - radiusport + radiusports - 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 1812 will be used. - radiusidentifier + radiusidentifiers The string used as NAS Identifier in the RADIUS diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index ebf10bbbae..a699a09e9a 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -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,77 +2592,29 @@ static int CheckRADIUSAuth(Port *port) { char *passwd; - char *identifier = "postgresql"; - char radius_buffer[RADIUS_BUFFER_SIZE]; - char receive_buffer[RADIUS_BUFFER_SIZE]; - radius_packet *packet = (radius_packet *) radius_buffer; - radius_packet *receivepacket = (radius_packet *) receive_buffer; - int32 service = htonl(RADIUS_AUTHENTICATE_ONLY); - uint8 *cryptvector; - int encryptedpasswordlen; - uint8 encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH]; - uint8 *md5trailer; - int packetlength; - pgsocket sock; - -#ifdef HAVE_IPV6 - struct sockaddr_in6 localaddr; - struct sockaddr_in6 remoteaddr; -#else - struct sockaddr_in localaddr; - struct sockaddr_in remoteaddr; -#endif - struct addrinfo hint; - struct addrinfo *serveraddrs; - char portstr[128]; - ACCEPT_TYPE_ARG3 addrsize; - fd_set fdset; - struct timeval endtime; - int i, - j, - r; + ListCell *server, + *secrets, + *radiusports, + *identifiers; /* Make sure struct alignment is correct */ Assert(offsetof(radius_packet, vector) == 4); /* Verify parameters */ - if (!port->hba->radiusserver || port->hba->radiusserver[0] == '\0') + if (list_length(port->hba->radiusservers) < 1) { ereport(LOG, (errmsg("RADIUS server not specified"))); return STATUS_ERROR; } - if (!port->hba->radiussecret || port->hba->radiussecret[0] == '\0') + if (list_length(port->hba->radiussecrets) < 1) { ereport(LOG, (errmsg("RADIUS secret not specified"))); return STATUS_ERROR; } - if (port->hba->radiusport == 0) - port->hba->radiusport = 1812; - - MemSet(&hint, 0, sizeof(hint)); - hint.ai_socktype = SOCK_DGRAM; - hint.ai_family = AF_UNSPEC; - snprintf(portstr, sizeof(portstr), "%d", port->hba->radiusport); - - r = pg_getaddrinfo_all(port->hba->radiusserver, 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)))); - 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); @@ -2683,6 +2636,104 @@ CheckRADIUSAuth(Port *port) 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; + radius_packet *receivepacket = (radius_packet *) receive_buffer; + int32 service = htonl(RADIUS_AUTHENTICATE_ONLY); + uint8 *cryptvector; + int encryptedpasswordlen; + uint8 encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH]; + uint8 *md5trailer; + int packetlength; + pgsocket sock; + +#ifdef HAVE_IPV6 + struct sockaddr_in6 localaddr; + struct sockaddr_in6 remoteaddr; +#else + struct sockaddr_in localaddr; + struct sockaddr_in remoteaddr; +#endif + struct addrinfo hint; + struct addrinfo *serveraddrs; + int port; + ACCEPT_TYPE_ARG3 addrsize; + fd_set fdset; + struct timeval endtime; + int i, + j, + r; + + /* 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; + port = atoi(portstr); + + r = pg_getaddrinfo_all(server, portstr, &hint, &serveraddrs); + if (r || !serveraddrs) + { + ereport(LOG, + (errmsg("could not translate RADIUS server name \"%s\" to address: %s", + server, gai_strerror(r)))); + if (serveraddrs) + pg_freeaddrinfo_all(hint.ai_family, serveraddrs); + return STATUS_ERROR; + } + /* XXX: add support for multiple returned addresses? */ /* Construct RADIUS packet */ packet->code = RADIUS_ACCESS_REQUEST; @@ -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) */ diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 7abcae618d..49be6638b8 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" @@ -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"); + REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius"); - MemSet(&hints, 0, sizeof(hints)); - hints.ai_socktype = SOCK_DGRAM; - hints.ai_family = AF_UNSPEC; - - 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 8f55edb16a..6c7382e67f 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -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