diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 366cb26..35f6779 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -395,6 +395,16 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
        </varlistentry>
 
        <varlistentry>
+        <term><literal>radius</></term>
+        <listitem>
+         <para>
+          Authenticate using a RADIUS server. See <xref
+          linkend="auth-radius"> for detauls.
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
         <term><literal>cert</></term>
         <listitem>
          <para>
@@ -1331,6 +1341,95 @@ ldapserver=ldap.example.net ldapprefix="cn=" ldapsuffix=", dc=example, dc=net"
 
   </sect2>
 
+  <sect2 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>password</> (encrypted) and
+    <literal>NAS Identifier</>. The request will be encrypted using
+    a secret shared with the server. The RADIUS server will respond to
+    this server with either <literal>Access Accept</> or
+    <literal>Access Reject</>. There is no support for RADIUS accounting.
+   </para>
+
+   <para>
+    The following configuration options are supported for RADIUS:
+     <variablelist>
+      <varlistentry>
+       <term><literal>radiusserver</literal></term>
+       <listitem>
+        <para>
+         The IP address of the RADIUS server to connect to. This must
+         be an IPV4 address and not a hostname. This parameter is required.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term><literal>radiussecret</literal></term>
+       <listitem>
+        <para>
+         The shared secret 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 is 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</> is built with support for
+          <productname>OpenSSL</>. 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>radiusport</literal></term>
+       <listitem>
+        <para>
+         The port number on the RADIUS server 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>
+       <listitem>
+        <para>
+         The string used as <literal>NAS Identifier</> in the RADIUS
+         requests. This parameter can be used as a second parameter
+         identifying for example which database the user is attempting
+         to authenticate as, which can be used for policy matching on
+         the RADIUS server. If no identifier is specified, the default
+         <literal>postgresql</> will be used.
+        </para>
+       </listitem>
+      </varlistentry>
+
+     </variablelist>
+   </para>
+  </sect2>
+
   <sect2 id="auth-cert">
    <title>Certificate authentication</title>
 
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index f4fd4d6..b601454 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -33,6 +33,7 @@
 #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 +183,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 +275,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 +486,9 @@ ClientAuthentication(Port *port)
 			Assert(false);
 #endif
 			break;
-
+		case uaRADIUS:
+			status = CheckRADIUSAuth(port);
+			break;
 		case uaTrust:
 			status = STATUS_OK;
 			break;
@@ -2415,3 +2430,347 @@ 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;
+	int					sock;
+	struct sockaddr_in	localaddr;
+	struct sockaddr_in	remoteaddr;
+	socklen_t			addrsize;
+	fd_set				fdset;
+	struct timeval		timeout;
+	int					i,r;
+
+	/* 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;
+	}
+}
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 4fc4215..dd7ad5c 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -952,6 +952,8 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 #else
 		unsupauth = "cert";
 #endif
+	else if (strcmp(token, "radius")== 0)
+		parsedline->auth_method = uaRADIUS;
 	else
 	{
 		ereport(LOG,
@@ -1162,6 +1164,45 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 				else
 					parsedline->include_realm = false;
 			}
+			else if (strcmp(token, "radiusserver") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
+				if (inet_addr(c) == INADDR_NONE)
+				{
+					ereport(LOG,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("invalid RADIUS server IP address: \"%s\"", c),
+						   errcontext("line %d of configuration file \"%s\"",
+									  line_num, HbaFileName)));
+					return false;
+
+				}
+				parsedline->radiusserver = pstrdup(c);
+			}
+			else if (strcmp(token, "radiusport") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
+				parsedline->radiusport = atoi(c);
+				if (parsedline->radiusport == 0)
+				{
+					ereport(LOG,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("invalid RADIUS port number: \"%s\"", c),
+						   errcontext("line %d of configuration file \"%s\"",
+									  line_num, HbaFileName)));
+					return false;
+				}
+			}
+			else if (strcmp(token, "radiussecret") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
+				parsedline->radiussecret = pstrdup(c);
+			}
+			else if (strcmp(token, "radiusidentifier") == 0)
+			{
+				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
+				parsedline->radiusidentifier = pstrdup(c);
+			}
 			else
 			{
 				ereport(LOG,
@@ -1214,6 +1255,12 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline)
 		}
 	}
 
+	if (parsedline->auth_method == uaRADIUS)
+	{
+		MANDATORY_AUTH_ARG(parsedline->radiusserver, "radiusserver", "radius");
+		MANDATORY_AUTH_ARG(parsedline->radiussecret, "radiussecret", "radius");
+	}
+
 	/*
 	 * Enforce any parameters implied by other settings.
 	 */
diff --git a/src/backend/libpq/md5.c b/src/backend/libpq/md5.c
index 467d19c..35d5fb4 100644
--- a/src/backend/libpq/md5.c
+++ b/src/backend/libpq/md5.c
@@ -298,6 +298,12 @@ pg_md5_hash(const void *buff, size_t len, char *hexsum)
 	return true;
 }
 
+bool pg_md5_binary(const void *buff, size_t len, void *outbuf)
+{
+	if (!calculateDigestFromBuffer((uint8 *) buff, len, outbuf))
+		return false;
+	return true;
+}
 
 
 /*
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 54b369d..e614a17 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -38,8 +38,9 @@
 # that the server is directly connected to.
 #
 # METHOD can be "trust", "reject", "md5", "password", "gss", "sspi", "krb5",
-# "ident", "pam", "ldap" or "cert".  Note that "password" sends passwords
-# in clear text; "md5" is preferred since it sends encrypted passwords.
+# "ident", "pam", "ldap", "radius" or "cert".  Note that "password" sends
+# passwords in clear text; "md5" is preferred since it sends encrypted
+# passwords.
 #
 # OPTIONS are a set of options for the authentication in the format
 # NAME=VALUE. The available options depend on the different authentication
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 10561fe..76fefcb 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -27,7 +27,8 @@ typedef enum UserAuth
 	uaSSPI,
 	uaPAM,
 	uaLDAP,
-	uaCert
+	uaCert,
+	uaRADIUS
 } UserAuth;
 
 typedef enum IPCompareMethod
@@ -71,6 +72,10 @@ typedef struct
 	char	   *krb_server_hostname;
 	char	   *krb_realm;
 	bool		include_realm;
+	char	   *radiusserver;
+	char	   *radiussecret;
+	char	   *radiusidentifier;
+	int			radiusport;
 } HbaLine;
 
 /* kluge to avoid including libpq/libpq-be.h here */
diff --git a/src/include/libpq/md5.h b/src/include/libpq/md5.h
index 6e1c53b..b3d486a 100644
--- a/src/include/libpq/md5.h
+++ b/src/include/libpq/md5.h
@@ -23,6 +23,7 @@
 
 
 extern bool pg_md5_hash(const void *buff, size_t len, char *hexsum);
+extern bool pg_md5_binary(const void *buff, size_t len, void *outbuf);
 extern bool pg_md5_encrypt(const char *passwd, const char *salt,
 			   size_t salt_len, char *buf);
 