mirror of
https://github.com/postgres/postgres.git
synced 2026-05-28 04:35:45 -04:00
Remove RADIUS support.
Our RADIUS implementation supported only the deprecated RADIUS/UDP variant, without the recommended Message-Authenticator attribute to mitigate against the Blast-RADIUS vulnerability. By now, popular RADIUS servers are expected to generate loud warnings or reject our authentication attempts outright. Since there have been no user reports about this, it seems unlikely that there are users. Reviewed-by: Álvaro Herrera <alvherre@kurilemu.de> Reviewed-by: Aleksander Alekseev <aleksander@tigerdata.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com> Reviewed-by: Michael Banck <mbanck@gmx.net> Discussion: https://postgr.es/m/CA%2BhUKG%2BSH309V8KECU5%3DxuLP9Dks0v9f9UVS2W74fPAE5O21dg%40mail.gmail.com
This commit is contained in:
parent
28972b6fc3
commit
a1643d40b3
10 changed files with 25 additions and 886 deletions
20
doc/src/sgml/appendix-obsolete-auth-radius.sgml
Normal file
20
doc/src/sgml/appendix-obsolete-auth-radius.sgml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<!-- doc/src/sgml/appendix-obsolete-auth-radius.sgml -->
|
||||
<!--
|
||||
See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
|
||||
-->
|
||||
|
||||
<sect1 id="auth-radius">
|
||||
<title>RADIUS authentication removed</title>
|
||||
|
||||
<indexterm zone="auth-radius">
|
||||
<primary>RADIUS</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
PostgreSQL 18 and below supported the RADIUS authentication protocol.
|
||||
Information about an alternative way to configure RADIUS support
|
||||
is available on the PostgreSQL
|
||||
<ulink url="https://wiki.postgresql.org/wiki/RADIUS">wiki</ulink>.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
|
@ -38,5 +38,6 @@
|
|||
&obsolete-pgxlogdump;
|
||||
&obsolete-pgresetxlog;
|
||||
&obsolete-pgreceivexlog;
|
||||
&obsolete-auth-radius;
|
||||
|
||||
</appendix>
|
||||
|
|
|
|||
|
|
@ -616,16 +616,6 @@ include_dir <replaceable>directory</replaceable>
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>radius</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Authenticate using a RADIUS server. See <xref
|
||||
linkend="auth-radius"/> for details.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>cert</literal></term>
|
||||
<listitem>
|
||||
|
|
@ -1127,12 +1117,6 @@ omicron bryanh guest1
|
|||
relies on an LDAP authentication server.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<link linkend="auth-radius">RADIUS authentication</link>, which
|
||||
relies on a RADIUS authentication server.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<link linkend="auth-cert">Certificate authentication</link>, which
|
||||
|
|
@ -2096,118 +2080,6 @@ host ... ldap ldapbasedn="dc=example,dc=net"
|
|||
|
||||
</sect1>
|
||||
|
||||
<sect1 id="auth-radius">
|
||||
<title>RADIUS Authentication</title>
|
||||
|
||||
<indexterm zone="auth-radius">
|
||||
<primary>RADIUS</primary>
|
||||
</indexterm>
|
||||
|
||||
<para>
|
||||
This authentication method operates similarly to
|
||||
<literal>password</literal> except that it uses RADIUS
|
||||
as the password verification method. RADIUS is used only to validate
|
||||
the user name/password pairs. Therefore the user must already
|
||||
exist in the database before RADIUS can be used for
|
||||
authentication.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When using RADIUS authentication, an Access Request message will be sent
|
||||
to the configured RADIUS server. This request will be of type
|
||||
<literal>Authenticate Only</literal>, and include parameters for
|
||||
<literal>user name</literal>, <literal>password</literal> (encrypted) and
|
||||
<literal>NAS Identifier</literal>. The request will be encrypted using
|
||||
a secret shared with the server. The RADIUS server will respond to
|
||||
this request with either <literal>Access Accept</literal> or
|
||||
<literal>Access Reject</literal>. 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, separate the server names with commas and surround the list
|
||||
with double quotes. If multiple servers are specified, the other
|
||||
RADIUS options can also be given as comma-separated lists, to provide
|
||||
individual values for each server. They can also be specified as
|
||||
a single value, in which case that value will apply to all servers.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The following configuration options are supported for RADIUS:
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><literal>radiusservers</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The DNS names or IP addresses of the RADIUS servers to connect to.
|
||||
This parameter is required.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>radiussecrets</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The shared secrets used when talking securely to the RADIUS
|
||||
servers. 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.
|
||||
<note>
|
||||
<para>
|
||||
The encryption vector used will only be cryptographically
|
||||
strong if <productname>PostgreSQL</productname> is built with support for
|
||||
<productname>OpenSSL</productname>. In other cases, the transmission to the
|
||||
RADIUS server should only be considered obfuscated, not secured, and
|
||||
external security measures should be applied if necessary.
|
||||
</para>
|
||||
</note>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>radiusports</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The port numbers to connect to on the RADIUS servers. If no port
|
||||
is specified, the default RADIUS port (<literal>1812</literal>)
|
||||
will be used.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>radiusidentifiers</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The strings to be used as <literal>NAS Identifier</literal> in the
|
||||
RADIUS requests. This parameter can be used, for example, to
|
||||
identify which database cluster the user is attempting to connect
|
||||
to, which can be useful for policy matching on
|
||||
the RADIUS server. If no identifier is specified, the default
|
||||
<literal>postgresql</literal> will be used.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If it is necessary to have a comma or whitespace in a RADIUS parameter
|
||||
value, that can be done by putting double quotes around the value, but
|
||||
it is tedious because two layers of double-quoting are now required.
|
||||
An example of putting whitespace into RADIUS secret strings is:
|
||||
<programlisting>
|
||||
host ... radius radiusservers="server1,server2" radiussecrets="""secret one"",""secret two"""
|
||||
</programlisting>
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
<sect1 id="auth-cert">
|
||||
<title>Certificate Authentication</title>
|
||||
|
||||
|
|
|
|||
|
|
@ -208,3 +208,4 @@
|
|||
<!ENTITY obsolete-pgxlogdump SYSTEM "appendix-obsolete-pgxlogdump.sgml">
|
||||
<!ENTITY obsolete-pgresetxlog SYSTEM "appendix-obsolete-pgresetxlog.sgml">
|
||||
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
|
||||
<!ENTITY obsolete-auth-radius SYSTEM "appendix-obsolete-auth-radius.sgml">
|
||||
|
|
|
|||
|
|
@ -203,13 +203,6 @@ static int pg_SSPI_make_upn(char *accountname,
|
|||
bool update_accountname);
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* RADIUS Authentication
|
||||
*----------------------------------------------------------------
|
||||
*/
|
||||
static int CheckRADIUSAuth(Port *port);
|
||||
static int PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd);
|
||||
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* Global authentication functions
|
||||
|
|
@ -299,9 +292,6 @@ auth_failed(Port *port, int elevel, int status, const char *logdetail)
|
|||
case uaCert:
|
||||
errstr = gettext_noop("certificate authentication failed for user \"%s\"");
|
||||
break;
|
||||
case uaRADIUS:
|
||||
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
|
||||
break;
|
||||
case uaOAuth:
|
||||
errstr = gettext_noop("OAuth bearer authentication failed for user \"%s\"");
|
||||
break;
|
||||
|
|
@ -630,9 +620,6 @@ ClientAuthentication(Port *port)
|
|||
Assert(false);
|
||||
#endif
|
||||
break;
|
||||
case uaRADIUS:
|
||||
status = CheckRADIUSAuth(port);
|
||||
break;
|
||||
case uaCert:
|
||||
/* uaCert will be treated as if clientcert=verify-full (uaTrust) */
|
||||
case uaTrust:
|
||||
|
|
@ -775,7 +762,7 @@ recv_password_packet(Port *port)
|
|||
* We rely on that for MD5 and SCRAM authentication, but we still need
|
||||
* this check here, to prevent an empty password from being used with
|
||||
* authentication methods that check the password against an external
|
||||
* system, like PAM, LDAP and RADIUS.
|
||||
* system, like PAM and LDAP.
|
||||
*/
|
||||
if (buf.len == 1)
|
||||
ereport(ERROR,
|
||||
|
|
@ -2790,499 +2777,3 @@ CheckCertAuth(Port *port)
|
|||
return status_check_usermap;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/*----------------------------------------------------------------
|
||||
* RADIUS authentication
|
||||
*----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* RADIUS authentication is described in RFC2865 (and several others).
|
||||
*/
|
||||
|
||||
#define RADIUS_VECTOR_LENGTH 16
|
||||
#define RADIUS_HEADER_LENGTH 20
|
||||
#define RADIUS_MAX_PASSWORD_LENGTH 128
|
||||
|
||||
/* Maximum size of a RADIUS packet we will create or accept */
|
||||
#define RADIUS_BUFFER_SIZE 1024
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8 attribute;
|
||||
uint8 length;
|
||||
uint8 data[FLEXIBLE_ARRAY_MEMBER];
|
||||
} radius_attribute;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint8 code;
|
||||
uint8 id;
|
||||
uint16 length;
|
||||
uint8 vector[RADIUS_VECTOR_LENGTH];
|
||||
/* this is a bit longer than strictly necessary: */
|
||||
char pad[RADIUS_BUFFER_SIZE - RADIUS_VECTOR_LENGTH];
|
||||
} radius_packet;
|
||||
|
||||
/* RADIUS packet types */
|
||||
#define RADIUS_ACCESS_REQUEST 1
|
||||
#define RADIUS_ACCESS_ACCEPT 2
|
||||
#define RADIUS_ACCESS_REJECT 3
|
||||
|
||||
/* RADIUS attributes */
|
||||
#define RADIUS_USER_NAME 1
|
||||
#define RADIUS_PASSWORD 2
|
||||
#define RADIUS_SERVICE_TYPE 6
|
||||
#define RADIUS_NAS_IDENTIFIER 32
|
||||
|
||||
/* RADIUS service types */
|
||||
#define RADIUS_AUTHENTICATE_ONLY 8
|
||||
|
||||
/* Seconds to wait - XXX: should be in a config variable! */
|
||||
#define RADIUS_TIMEOUT 3
|
||||
|
||||
static void
|
||||
radius_add_attribute(radius_packet *packet, uint8 type, const unsigned char *data, int len)
|
||||
{
|
||||
radius_attribute *attr;
|
||||
|
||||
if (packet->length + len > RADIUS_BUFFER_SIZE)
|
||||
{
|
||||
/*
|
||||
* With remotely realistic data, this can never happen. But catch it
|
||||
* just to make sure we don't overrun a buffer. We'll just skip adding
|
||||
* the broken attribute, which will in the end cause authentication to
|
||||
* fail.
|
||||
*/
|
||||
elog(WARNING,
|
||||
"adding attribute code %d with length %d to radius packet would create oversize packet, ignoring",
|
||||
type, len);
|
||||
return;
|
||||
}
|
||||
|
||||
attr = (radius_attribute *) ((unsigned char *) packet + packet->length);
|
||||
attr->attribute = type;
|
||||
attr->length = len + 2; /* total size includes type and length */
|
||||
memcpy(attr->data, data, len);
|
||||
packet->length += attr->length;
|
||||
}
|
||||
|
||||
static int
|
||||
CheckRADIUSAuth(Port *port)
|
||||
{
|
||||
char *passwd;
|
||||
ListCell *server,
|
||||
*secrets,
|
||||
*radiusports,
|
||||
*identifiers;
|
||||
|
||||
/* Make sure struct alignment is correct */
|
||||
Assert(offsetof(radius_packet, vector) == 4);
|
||||
|
||||
/* Verify parameters */
|
||||
if (port->hba->radiusservers == NIL)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS server not specified")));
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
if (port->hba->radiussecrets == NIL)
|
||||
{
|
||||
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) > RADIUS_MAX_PASSWORD_LENGTH)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS authentication does not support passwords longer than %d characters", RADIUS_MAX_PASSWORD_LENGTH)));
|
||||
pfree(passwd);
|
||||
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)
|
||||
{
|
||||
set_authn_id(port, port->user_name);
|
||||
|
||||
pfree(passwd);
|
||||
return STATUS_OK;
|
||||
}
|
||||
else if (ret == STATUS_EOF)
|
||||
{
|
||||
pfree(passwd);
|
||||
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(port->hba->radiussecrets, secrets);
|
||||
if (list_length(port->hba->radiusports) > 1)
|
||||
radiusports = lnext(port->hba->radiusports, radiusports);
|
||||
if (list_length(port->hba->radiusidentifiers) > 1)
|
||||
identifiers = lnext(port->hba->radiusidentifiers, identifiers);
|
||||
}
|
||||
|
||||
/* No servers left to try, so give up */
|
||||
pfree(passwd);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
static int
|
||||
PerformRadiusTransaction(const char *server, const char *secret, const char *portstr, const char *identifier, const char *user_name, const char *passwd)
|
||||
{
|
||||
radius_packet radius_send_pack;
|
||||
radius_packet radius_recv_pack;
|
||||
radius_packet *packet = &radius_send_pack;
|
||||
radius_packet *receivepacket = &radius_recv_pack;
|
||||
void *radius_buffer = &radius_send_pack;
|
||||
void *receive_buffer = &radius_recv_pack;
|
||||
int32 service = pg_hton32(RADIUS_AUTHENTICATE_ONLY);
|
||||
uint8 *cryptvector;
|
||||
int encryptedpasswordlen;
|
||||
uint8 encryptedpassword[RADIUS_MAX_PASSWORD_LENGTH];
|
||||
uint8 *md5trailer;
|
||||
int packetlength;
|
||||
pgsocket sock;
|
||||
|
||||
struct sockaddr_in6 localaddr;
|
||||
struct sockaddr_in6 remoteaddr;
|
||||
struct addrinfo hint;
|
||||
struct addrinfo *serveraddrs;
|
||||
int port;
|
||||
socklen_t 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;
|
||||
packet->length = RADIUS_HEADER_LENGTH;
|
||||
if (!pg_strong_random(packet->vector, RADIUS_VECTOR_LENGTH))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not generate random encryption vector")));
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
packet->id = packet->vector[0];
|
||||
radius_add_attribute(packet, RADIUS_SERVICE_TYPE, (const unsigned char *) &service, sizeof(service));
|
||||
radius_add_attribute(packet, RADIUS_USER_NAME, (const unsigned char *) user_name, strlen(user_name));
|
||||
radius_add_attribute(packet, RADIUS_NAS_IDENTIFIER, (const unsigned char *) identifier, strlen(identifier));
|
||||
|
||||
/*
|
||||
* RADIUS password attributes are calculated as: e[0] = p[0] XOR
|
||||
* MD5(secret + Request Authenticator) for the first group of 16 octets,
|
||||
* and then: e[i] = p[i] XOR MD5(secret + e[i-1]) for the following ones
|
||||
* (if necessary)
|
||||
*/
|
||||
encryptedpasswordlen = ((strlen(passwd) + RADIUS_VECTOR_LENGTH - 1) / RADIUS_VECTOR_LENGTH) * RADIUS_VECTOR_LENGTH;
|
||||
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)
|
||||
{
|
||||
const char *errstr = NULL;
|
||||
|
||||
memcpy(cryptvector + strlen(secret), md5trailer, RADIUS_VECTOR_LENGTH);
|
||||
|
||||
/*
|
||||
* .. and for subsequent iterations the result of the previous XOR
|
||||
* (calculated below)
|
||||
*/
|
||||
md5trailer = encryptedpassword + i;
|
||||
|
||||
if (!pg_md5_binary(cryptvector, strlen(secret) + RADIUS_VECTOR_LENGTH,
|
||||
encryptedpassword + i, &errstr))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not perform MD5 encryption of password: %s",
|
||||
errstr)));
|
||||
pfree(cryptvector);
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
for (j = i; j < i + RADIUS_VECTOR_LENGTH; j++)
|
||||
{
|
||||
if (j < strlen(passwd))
|
||||
encryptedpassword[j] = passwd[j] ^ encryptedpassword[j];
|
||||
else
|
||||
encryptedpassword[j] = '\0' ^ encryptedpassword[j];
|
||||
}
|
||||
}
|
||||
pfree(cryptvector);
|
||||
|
||||
radius_add_attribute(packet, RADIUS_PASSWORD, encryptedpassword, encryptedpasswordlen);
|
||||
|
||||
/* Length needs to be in network order on the wire */
|
||||
packetlength = packet->length;
|
||||
packet->length = pg_hton16(packet->length);
|
||||
|
||||
sock = socket(serveraddrs[0].ai_family, SOCK_DGRAM, 0);
|
||||
if (sock == PGINVALID_SOCKET)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not create RADIUS socket: %m")));
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
memset(&localaddr, 0, sizeof(localaddr));
|
||||
localaddr.sin6_family = serveraddrs[0].ai_family;
|
||||
localaddr.sin6_addr = in6addr_any;
|
||||
if (localaddr.sin6_family == AF_INET6)
|
||||
addrsize = sizeof(struct sockaddr_in6);
|
||||
else
|
||||
addrsize = sizeof(struct sockaddr_in);
|
||||
|
||||
if (bind(sock, (struct sockaddr *) &localaddr, addrsize))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not bind local RADIUS socket: %m")));
|
||||
closesocket(sock);
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
if (sendto(sock, radius_buffer, packetlength, 0,
|
||||
serveraddrs[0].ai_addr, serveraddrs[0].ai_addrlen) < 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not send RADIUS packet: %m")));
|
||||
closesocket(sock);
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
/* Don't need the server address anymore */
|
||||
pg_freeaddrinfo_all(hint.ai_family, serveraddrs);
|
||||
|
||||
/*
|
||||
* Figure out at what time we should time out. We can't just use a single
|
||||
* call to select() with a timeout, since somebody can be sending invalid
|
||||
* packets to our port thus causing us to retry in a loop and never time
|
||||
* out.
|
||||
*
|
||||
* XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
|
||||
* the latch was set would improve the responsiveness to
|
||||
* timeouts/cancellations.
|
||||
*/
|
||||
gettimeofday(&endtime, NULL);
|
||||
endtime.tv_sec += RADIUS_TIMEOUT;
|
||||
|
||||
while (true)
|
||||
{
|
||||
struct timeval timeout;
|
||||
struct timeval now;
|
||||
int64 timeoutval;
|
||||
const char *errstr = NULL;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
timeoutval = (endtime.tv_sec * 1000000 + endtime.tv_usec) - (now.tv_sec * 1000000 + now.tv_usec);
|
||||
if (timeoutval <= 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("timeout waiting for RADIUS response from %s",
|
||||
server)));
|
||||
closesocket(sock);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
timeout.tv_sec = timeoutval / 1000000;
|
||||
timeout.tv_usec = timeoutval % 1000000;
|
||||
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(sock, &fdset);
|
||||
|
||||
r = select(sock + 1, &fdset, NULL, NULL, &timeout);
|
||||
if (r < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
/* Anything else is an actual error */
|
||||
ereport(LOG,
|
||||
(errmsg("could not check status on RADIUS socket: %m")));
|
||||
closesocket(sock);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
if (r == 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("timeout waiting for RADIUS response from %s",
|
||||
server)));
|
||||
closesocket(sock);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Attempt to read the response packet, and verify the contents.
|
||||
*
|
||||
* Any packet that's not actually a RADIUS packet, or otherwise does
|
||||
* not validate as an explicit reject, is just ignored and we retry
|
||||
* for another packet (until we reach the timeout). This is to avoid
|
||||
* the possibility to denial-of-service the login by flooding the
|
||||
* server with invalid packets on the port that we're expecting the
|
||||
* RADIUS response on.
|
||||
*/
|
||||
|
||||
addrsize = sizeof(remoteaddr);
|
||||
packetlength = recvfrom(sock, receive_buffer, RADIUS_BUFFER_SIZE, 0,
|
||||
(struct sockaddr *) &remoteaddr, &addrsize);
|
||||
if (packetlength < 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not read RADIUS response: %m")));
|
||||
closesocket(sock);
|
||||
return STATUS_ERROR;
|
||||
}
|
||||
|
||||
if (remoteaddr.sin6_port != pg_hton16(port))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s was sent from incorrect port: %d",
|
||||
server, pg_ntoh16(remoteaddr.sin6_port))));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packetlength < RADIUS_HEADER_LENGTH)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s too short: %d", server, packetlength)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packetlength != pg_ntoh16(receivepacket->length))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s has corrupt length: %d (actual length %d)",
|
||||
server, pg_ntoh16(receivepacket->length), packetlength)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (packet->id != receivepacket->id)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s is to a different request: %d (should be %d)",
|
||||
server, receivepacket->id, packet->id)));
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify the response authenticator, which is calculated as
|
||||
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
|
||||
*/
|
||||
cryptvector = palloc(packetlength + strlen(secret));
|
||||
|
||||
memcpy(cryptvector, receivepacket, 4); /* code+id+length */
|
||||
memcpy(cryptvector + 4, packet->vector, RADIUS_VECTOR_LENGTH); /* request
|
||||
* authenticator, from
|
||||
* original packet */
|
||||
if (packetlength > RADIUS_HEADER_LENGTH) /* there may be no
|
||||
* attributes at all */
|
||||
memcpy(cryptvector + RADIUS_HEADER_LENGTH,
|
||||
(char *) receive_buffer + RADIUS_HEADER_LENGTH,
|
||||
packetlength - RADIUS_HEADER_LENGTH);
|
||||
memcpy(cryptvector + packetlength, secret, strlen(secret));
|
||||
|
||||
if (!pg_md5_binary(cryptvector,
|
||||
packetlength + strlen(secret),
|
||||
encryptedpassword, &errstr))
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("could not perform MD5 encryption of received packet: %s",
|
||||
errstr)));
|
||||
pfree(cryptvector);
|
||||
continue;
|
||||
}
|
||||
pfree(cryptvector);
|
||||
|
||||
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s has incorrect MD5 signature",
|
||||
server)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
|
||||
{
|
||||
closesocket(sock);
|
||||
return STATUS_OK;
|
||||
}
|
||||
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
|
||||
{
|
||||
closesocket(sock);
|
||||
return STATUS_EOF;
|
||||
}
|
||||
else
|
||||
{
|
||||
ereport(LOG,
|
||||
(errmsg("RADIUS response from %s has invalid code (%d) for user \"%s\"",
|
||||
server, receivepacket->code, user_name)));
|
||||
continue;
|
||||
}
|
||||
} /* while (true) */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ static const char *const UserAuthName[] =
|
|||
"bsd",
|
||||
"ldap",
|
||||
"cert",
|
||||
"radius",
|
||||
"peer",
|
||||
"oauth",
|
||||
};
|
||||
|
|
@ -1744,8 +1743,6 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
|
|||
#else
|
||||
unsupauth = "cert";
|
||||
#endif
|
||||
else if (strcmp(token->string, "radius") == 0)
|
||||
parsedline->auth_method = uaRADIUS;
|
||||
else if (strcmp(token->string, "oauth") == 0)
|
||||
parsedline->auth_method = uaOAuth;
|
||||
else
|
||||
|
|
@ -1947,87 +1944,6 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
|
|||
}
|
||||
}
|
||||
|
||||
if (parsedline->auth_method == uaRADIUS)
|
||||
{
|
||||
MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
|
||||
MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
|
||||
|
||||
if (parsedline->radiusservers == NIL)
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("list of RADIUS servers cannot be empty"),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*err_msg = "list of RADIUS servers cannot be empty";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (parsedline->radiussecrets == NIL)
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("list of RADIUS secrets cannot be empty"),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*err_msg = "list of RADIUS secrets cannot be empty";
|
||||
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 (!(list_length(parsedline->radiussecrets) == 1 ||
|
||||
list_length(parsedline->radiussecrets) == list_length(parsedline->radiusservers)))
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiussecrets),
|
||||
list_length(parsedline->radiusservers)),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*err_msg = psprintf("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiussecrets),
|
||||
list_length(parsedline->radiusservers));
|
||||
return NULL;
|
||||
}
|
||||
if (!(list_length(parsedline->radiusports) == 0 ||
|
||||
list_length(parsedline->radiusports) == 1 ||
|
||||
list_length(parsedline->radiusports) == list_length(parsedline->radiusservers)))
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiusports),
|
||||
list_length(parsedline->radiusservers)),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*err_msg = psprintf("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiusports),
|
||||
list_length(parsedline->radiusservers));
|
||||
return NULL;
|
||||
}
|
||||
if (!(list_length(parsedline->radiusidentifiers) == 0 ||
|
||||
list_length(parsedline->radiusidentifiers) == 1 ||
|
||||
list_length(parsedline->radiusidentifiers) == list_length(parsedline->radiusservers)))
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiusidentifiers),
|
||||
list_length(parsedline->radiusservers)),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*err_msg = psprintf("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
|
||||
list_length(parsedline->radiusidentifiers),
|
||||
list_length(parsedline->radiusservers));
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Enforce any parameters implied by other settings.
|
||||
*/
|
||||
|
|
@ -2350,138 +2266,6 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
|
|||
else
|
||||
hbaline->upn_username = false;
|
||||
}
|
||||
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, "radiusservers", "radius");
|
||||
|
||||
if (!SplitGUCList(dupval, ',', &parsed_servers))
|
||||
{
|
||||
/* syntax error in list */
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("could not parse RADIUS server list \"%s\"",
|
||||
val),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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, file_name)));
|
||||
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, "radiusports") == 0)
|
||||
{
|
||||
List *parsed_ports;
|
||||
ListCell *l;
|
||||
char *dupval = pstrdup(val);
|
||||
|
||||
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
|
||||
|
||||
if (!SplitGUCList(dupval, ',', &parsed_ports))
|
||||
{
|
||||
ereport(elevel,
|
||||
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
||||
errmsg("could not parse RADIUS port list \"%s\"",
|
||||
val),
|
||||
errcontext("line %d of configuration file \"%s\"",
|
||||
line_num, file_name)));
|
||||
*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, file_name)));
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
hbaline->radiusports = parsed_ports;
|
||||
hbaline->radiusports_s = pstrdup(val);
|
||||
}
|
||||
else if (strcmp(name, "radiussecrets") == 0)
|
||||
{
|
||||
List *parsed_secrets;
|
||||
char *dupval = pstrdup(val);
|
||||
|
||||
REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
|
||||
|
||||
if (!SplitGUCList(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, file_name)));
|
||||
return false;
|
||||
}
|
||||
|
||||
hbaline->radiussecrets = parsed_secrets;
|
||||
hbaline->radiussecrets_s = pstrdup(val);
|
||||
}
|
||||
else if (strcmp(name, "radiusidentifiers") == 0)
|
||||
{
|
||||
List *parsed_identifiers;
|
||||
char *dupval = pstrdup(val);
|
||||
|
||||
REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
|
||||
|
||||
if (!SplitGUCList(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, file_name)));
|
||||
return false;
|
||||
}
|
||||
|
||||
hbaline->radiusidentifiers = parsed_identifiers;
|
||||
hbaline->radiusidentifiers_s = pstrdup(val);
|
||||
}
|
||||
else if (strcmp(name, "issuer") == 0)
|
||||
{
|
||||
REQUIRE_AUTH_OPTION(uaOAuth, "issuer", "oauth");
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@
|
|||
# directly connected to.
|
||||
#
|
||||
# METHOD can be "trust", "reject", "md5", "password", "scram-sha-256",
|
||||
# "gss", "sspi", "ident", "peer", "pam", "oauth", "ldap", "radius" or
|
||||
# "cert". Note that "password" sends passwords in clear text; "md5" or
|
||||
# "gss", "sspi", "ident", "peer", "pam", "oauth", "ldap" or "cert".
|
||||
# Note that "password" sends passwords in clear text; "md5" or
|
||||
# "scram-sha-256" are preferred since they send encrypted passwords.
|
||||
#
|
||||
# OPTIONS are a set of options for the authentication in the format
|
||||
|
|
|
|||
|
|
@ -135,25 +135,6 @@ get_hba_options(HbaLine *hba)
|
|||
CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
|
||||
}
|
||||
|
||||
if (hba->auth_method == uaRADIUS)
|
||||
{
|
||||
if (hba->radiusservers_s)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
|
||||
|
||||
if (hba->radiussecrets_s)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
|
||||
|
||||
if (hba->radiusidentifiers_s)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
|
||||
|
||||
if (hba->radiusports_s)
|
||||
options[noptions++] =
|
||||
CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
|
||||
}
|
||||
|
||||
if (hba->auth_method == uaOAuth)
|
||||
{
|
||||
if (hba->oauth_issuer)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ typedef enum UserAuth
|
|||
uaBSD,
|
||||
uaLDAP,
|
||||
uaCert,
|
||||
uaRADIUS,
|
||||
uaPeer,
|
||||
uaOAuth,
|
||||
#define USER_AUTH_LAST uaOAuth /* Must be last value of this enum */
|
||||
|
|
@ -128,14 +127,6 @@ typedef struct HbaLine
|
|||
bool include_realm;
|
||||
bool compat_realm;
|
||||
bool upn_username;
|
||||
List *radiusservers;
|
||||
char *radiusservers_s;
|
||||
List *radiussecrets;
|
||||
char *radiussecrets_s;
|
||||
List *radiusidentifiers;
|
||||
char *radiusidentifiers_s;
|
||||
List *radiusports;
|
||||
char *radiusports_s;
|
||||
char *oauth_issuer;
|
||||
char *oauth_scope;
|
||||
char *oauth_validator;
|
||||
|
|
|
|||
|
|
@ -4185,8 +4185,6 @@ qc_hash_func
|
|||
qsort_arg_comparator
|
||||
qsort_comparator
|
||||
query_pathkeys_callback
|
||||
radius_attribute
|
||||
radius_packet
|
||||
RadixSortInfo
|
||||
rangeTableEntry_used_context
|
||||
rank_context
|
||||
|
|
|
|||
Loading…
Reference in a new issue