1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-11 10:01:57 +03:00

Add support for RADIUS authentication.

This commit is contained in:
Magnus Hagander
2010-01-27 12:12:00 +00:00
parent 000416ac32
commit b3daac5a9c
7 changed files with 532 additions and 11 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.191 2010/01/10 14:16:07 mha Exp $
* $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.192 2010/01/27 12:11:59 mha Exp $
*
*-------------------------------------------------------------------------
*/
@ -27,12 +27,14 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stddef.h>
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/md5.h"
#include "miscadmin.h"
#include "storage/ipc.h"
@ -182,6 +184,15 @@ typedef SECURITY_STATUS
static int pg_SSPI_recvauth(Port *port);
#endif
/*----------------------------------------------------------------
* RADIUS Authentication
*----------------------------------------------------------------
*/
#ifdef USE_SSL
#include <openssl/rand.h>
#endif
static int CheckRADIUSAuth(Port *port);
/*
* Maximum accepted size of GSS and SSPI authentication tokens.
@ -265,6 +276,9 @@ auth_failed(Port *port, int status)
case uaLDAP:
errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
break;
case uaRADIUS:
errstr = gettext_noop("RADIUS authentication failed for user \"%s\"");
break;
default:
errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
break;
@ -473,7 +487,9 @@ ClientAuthentication(Port *port)
Assert(false);
#endif
break;
case uaRADIUS:
status = CheckRADIUSAuth(port);
break;
case uaTrust:
status = STATUS_OK;
break;
@ -2415,3 +2431,350 @@ CheckCertAuth(Port *port)
}
#endif
/*----------------------------------------------------------------
* RADIUS authentication
*----------------------------------------------------------------
*/
/*
* RADIUS authentication is described in RFC2865 (and several
* others).
*/
#define RADIUS_VECTOR_LENGTH 16
#define RADIUS_HEADER_LENGTH 20
typedef struct
{
uint8 attribute;
uint8 length;
uint8 data[1];
} radius_attribute;
typedef struct
{
uint8 code;
uint8 id;
uint16 length;
uint8 vector[RADIUS_VECTOR_LENGTH];
} radius_packet;
/* RADIUS packet types */
#define RADIUS_ACCESS_REQUEST 1
#define RADIUS_ACCESS_ACCEPT 2
#define RADIUS_ACCESS_REJECT 3
/* RAIDUS 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
/* Maximum size of a RADIUS packet we will create or accept */
#define RADIUS_BUFFER_SIZE 1024
/* 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 %i with length %i 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;
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;
uint8 encryptedpassword[RADIUS_VECTOR_LENGTH];
int packetlength;
pgsocket sock;
struct sockaddr_in localaddr;
struct sockaddr_in remoteaddr;
socklen_t addrsize;
fd_set fdset;
struct timeval timeout;
int i,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;
memset(&remoteaddr, 0, sizeof(remoteaddr));
remoteaddr.sin_family = AF_INET;
remoteaddr.sin_addr.s_addr = inet_addr(port->hba->radiusserver);
if (remoteaddr.sin_addr.s_addr == INADDR_NONE)
{
ereport(LOG,
(errmsg("RADIUS server '%s' is not a valid IP address",
port->hba->radiusserver)));
return STATUS_ERROR;
}
remoteaddr.sin_port = htons(port->hba->radiusport);
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);
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_VECTOR_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS authentication does not support passwords longer than 16 characters")));
return STATUS_ERROR;
}
/* Construct RADIUS packet */
packet->code = RADIUS_ACCESS_REQUEST;
packet->length = RADIUS_HEADER_LENGTH;
#ifdef USE_SSL
if (RAND_bytes(packet->vector, RADIUS_VECTOR_LENGTH) != 1)
{
ereport(LOG,
(errmsg("could not generate random encryption vector")));
return STATUS_ERROR;
}
#else
for (i = 0; i < RADIUS_VECTOR_LENGTH; i++)
/* Use a lower strengh random number of OpenSSL is not available */
packet->vector[i] = random() % 255;
#endif
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_NAS_IDENTIFIER, (unsigned char *) identifier, strlen(identifier));
/*
* RADIUS password attributes are calculated as:
* e[0] = p[0] XOR MD5(secret + vector)
*/
cryptvector = palloc(RADIUS_VECTOR_LENGTH + strlen(port->hba->radiussecret));
memcpy(cryptvector, port->hba->radiussecret, strlen(port->hba->radiussecret));
memcpy(cryptvector + strlen(port->hba->radiussecret), packet->vector, RADIUS_VECTOR_LENGTH);
if (!pg_md5_binary(cryptvector, RADIUS_VECTOR_LENGTH + strlen(port->hba->radiussecret), encryptedpassword))
{
ereport(LOG,
(errmsg("could not perform md5 encryption of password")));
pfree(cryptvector);
return STATUS_ERROR;
}
pfree(cryptvector);
for (i = 0; i < RADIUS_VECTOR_LENGTH; i++)
{
if (i < strlen(passwd))
encryptedpassword[i] = passwd[i] ^ encryptedpassword[i];
else
encryptedpassword[i] = '\0' ^ encryptedpassword[i];
}
radius_add_attribute(packet, RADIUS_PASSWORD, encryptedpassword, RADIUS_VECTOR_LENGTH);
/* Length need to be in network order on the wire */
packetlength = packet->length;
packet->length = htons(packet->length);
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
{
ereport(LOG,
(errmsg("could not create RADIUS socket: %m")));
return STATUS_ERROR;
}
memset(&localaddr, 0, sizeof(localaddr));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *) &localaddr, sizeof(localaddr)))
{
ereport(LOG,
(errmsg("could not bind local RADIUS socket: %m")));
closesocket(sock);
return STATUS_ERROR;
}
if (sendto(sock, radius_buffer, packetlength, 0,
(struct sockaddr *) &remoteaddr, sizeof(remoteaddr)) < 0)
{
ereport(LOG,
(errmsg("could not send RADIUS packet: %m")));
closesocket(sock);
return STATUS_ERROR;
}
timeout.tv_sec = RADIUS_TIMEOUT;
timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
while (true)
{
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")));
closesocket(sock);
return STATUS_ERROR;
}
/* else we actually have a packet ready to read */
break;
}
/* Read the response packet */
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;
}
closesocket(sock);
if (remoteaddr.sin_port != htons(port->hba->radiusport))
{
ereport(LOG,
(errmsg("RADIUS response was sent from incorrect port: %i",
ntohs(remoteaddr.sin_port))));
return STATUS_ERROR;
}
if (packetlength < RADIUS_HEADER_LENGTH)
{
ereport(LOG,
(errmsg("RADIUS response too short: %i", packetlength)));
return STATUS_ERROR;
}
if (packetlength != ntohs(receivepacket->length))
{
ereport(LOG,
(errmsg("RADIUS response has corrupt length: %i (actual length %i)",
ntohs(receivepacket->length), packetlength)));
return STATUS_ERROR;
}
if (packet->id != receivepacket->id)
{
ereport(LOG,
(errmsg("RADIUS response is to a different request: %i (should be %i)",
receivepacket->id, packet->id)));
return STATUS_ERROR;
}
/*
* Verify the response authenticator, which is calculated as
* MD5(Code+ID+Length+RequestAuthenticator+Attributes+Secret)
*/
cryptvector = palloc(packetlength + strlen(port->hba->radiussecret));
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, receive_buffer + RADIUS_HEADER_LENGTH, packetlength-RADIUS_HEADER_LENGTH);
memcpy(cryptvector+packetlength, port->hba->radiussecret, strlen(port->hba->radiussecret));
if (!pg_md5_binary(cryptvector,
packetlength + strlen(port->hba->radiussecret),
encryptedpassword))
{
ereport(LOG,
(errmsg("could not perform md5 encryption of received packet")));
pfree(cryptvector);
return STATUS_ERROR;
}
pfree(cryptvector);
if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
{
ereport(LOG,
(errmsg("RADIUS response has incorrect MD5 signature")));
return STATUS_ERROR;
}
if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
return STATUS_OK;
else if (receivepacket->code == RADIUS_ACCESS_REJECT)
return STATUS_ERROR;
else
{
ereport(LOG,
(errmsg("RADIUS response has invalid code (%i) for user '%s'",
receivepacket->code, port->user_name)));
return STATUS_ERROR;
}
}