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;

#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,
				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(&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);

	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_OPENSSL
	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(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));
#ifdef HAVE_IPV6
	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);
#else
	localaddr.sin_family = serveraddrs[0].ai_family;
	localaddr.sin_addr.s_addr = INADDR_ANY;
	addrsize = sizeof(struct sockaddr_in);
#endif
	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.
	 */
	gettimeofday(&endtime, NULL);
	endtime.tv_sec += RADIUS_TIMEOUT;

	while (true)
	{
		struct timeval timeout;
		struct timeval now;
		int64		timeoutval;

		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")));
			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")));
			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")));
			return STATUS_ERROR;
		}

#ifdef HAVE_IPV6
		if (remoteaddr.sin6_port != htons(port->hba->radiusport))
#else
		if (remoteaddr.sin_port != htons(port->hba->radiusport))
#endif
		{
#ifdef HAVE_IPV6
			ereport(LOG,
				  (errmsg("RADIUS response was sent from incorrect port: %d",
						  ntohs(remoteaddr.sin6_port))));
#else
			ereport(LOG,
				  (errmsg("RADIUS response was sent from incorrect port: %d",
						  ntohs(remoteaddr.sin_port))));
#endif
			continue;
		}

		if (packetlength < RADIUS_HEADER_LENGTH)
		{
			ereport(LOG,
					(errmsg("RADIUS response too short: %d", packetlength)));
			continue;
		}

		if (packetlength != ntohs(receivepacket->length))
		{
			ereport(LOG,
					(errmsg("RADIUS response has corrupt length: %d (actual length %d)",
							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)));
			continue;
		}

		/*
		 * 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);
			continue;
		}
		pfree(cryptvector);

		if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
		{
			ereport(LOG,
					(errmsg("RADIUS response has incorrect MD5 signature")));
			continue;
		}

		if (receivepacket->code == RADIUS_ACCESS_ACCEPT)
		{
			closesocket(sock);
			return STATUS_OK;
		}
		else if (receivepacket->code == RADIUS_ACCESS_REJECT)
		{
			closesocket(sock);
			return STATUS_ERROR;
		}
		else
		{
			ereport(LOG,
			 (errmsg("RADIUS response has invalid code (%d) for user \"%s\"",
					 receivepacket->code, port->user_name)));
			continue;
		}
	}							/* while (true) */
}