diff options
author | Magnus Hagander <magnus@hagander.net> | 2010-01-27 12:12:00 +0000 |
---|---|---|
committer | Magnus Hagander <magnus@hagander.net> | 2010-01-27 12:12:00 +0000 |
commit | b3daac5a9c34b1a567a0bc3574446ee57564770c (patch) | |
tree | 9193529d6fb1fcc844d48e95bafb07cd82f4171f /src/backend/libpq/auth.c | |
parent | 000416ac32f2b7cf62f21c51a7890cf4a4d07313 (diff) | |
download | postgresql-b3daac5a9c34b1a567a0bc3574446ee57564770c.tar.gz postgresql-b3daac5a9c34b1a567a0bc3574446ee57564770c.zip |
Add support for RADIUS authentication.
Diffstat (limited to 'src/backend/libpq/auth.c')
-rw-r--r-- | src/backend/libpq/auth.c | 367 |
1 files changed, 365 insertions, 2 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index e7d0dbb2985..26d2080e6f9 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -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; + } +} |