/*
 * $Id: sendserver.c,v 1.30 2010/06/15 09:22:52 aland Exp $
 *
 * Copyright (C) 1995,1996,1997 Lars Fenneberg
 *
 * Copyright 1992 Livingston Enterprises, Inc.
 *
 * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan
 * and Merit Network, Inc. All Rights Reserved
 *
 * See the file COPYRIGHT for the respective terms and conditions.
 * If the file is missing contact me at lf@elemental.net
 * and I'll send you a copy.
 *
 */

//#include <poll.h>

#include <radius_config.h>
#include <includes.h>
#include <freeradius-client.h>
#include <pathnames.h>
#include "freeradius-client.h"
#include "util.h"
#include "radius_user.h"
#include "parameters.h"

#include "lwip/sockets.h"

#define	SA(p)	((struct sockaddr *)(p))

static void rc_random_vector (unsigned char *);
static int rc_check_reply (AUTH_HDR *, int, char const *, unsigned char const *, unsigned char);

/** Packs an attribute value pair list into a buffer
 *
 * @param vp a pointer to a #VALUE_PAIR.
 * @param secret the secret used by the server.
 * @param auth a pointer to #AUTH_HDR.
 * @return The number of octets packed.
 */
static int rc_pack_list (VALUE_PAIR *vp, char *secret, AUTH_HDR *auth)
{
	int             length, i, pc, padded_length;
	int             total_length = 0;
	size_t			secretlen;
	uint32_t           lvalue, vendor;
	unsigned char   passbuf[MAX(AUTH_PASS_LEN, CHAP_VALUE_LENGTH)];
	unsigned char   md5buf[256];
	unsigned char   *buf, *vector, *vsa_length_ptr;

	buf = auth->data;

	while (vp != NULL)
	{
		vsa_length_ptr = NULL;
		if (VENDOR(vp->attribute) != 0) {
			*buf++ = PW_VENDOR_SPECIFIC;
			vsa_length_ptr = buf;
			*buf++ = 6;
			vendor = htonl(VENDOR(vp->attribute));
			memcpy(buf, &vendor, sizeof(uint32_t));
			buf += 4;
			total_length += 6;
		}
		*buf++ = (vp->attribute & 0xff);

		switch (vp->attribute)
		{
		 case PW_USER_PASSWORD:

		  /* Encrypt the password */

		  /* Chop off password at AUTH_PASS_LEN */
		  length = vp->lvalue;
		  if (length > AUTH_PASS_LEN)
			length = AUTH_PASS_LEN;

		  /* Calculate the padded length */
		  padded_length = (length+(AUTH_VECTOR_LEN-1)) & ~(AUTH_VECTOR_LEN-1);

		  /* Record the attribute length */
		  *buf++ = padded_length + 2;
		  if (vsa_length_ptr != NULL) *vsa_length_ptr += padded_length + 2;

		  /* Pad the password with zeros */
		  memset ((char *) passbuf, '\0', AUTH_PASS_LEN);
		  memcpy ((char *) passbuf, vp->strvalue, (size_t) length);

		  secretlen = strlen (secret);
		  vector = (unsigned char *)auth->vector;
		  for(i = 0; i < padded_length; i += AUTH_VECTOR_LEN)
		  {
		  	/* Calculate the MD5 digest*/
		  	strcpy ((char *) md5buf, secret);
		  	memcpy ((char *) md5buf + secretlen, vector,
		  		  AUTH_VECTOR_LEN);
		  	rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN);

		        /* Remeber the start of the digest */
		  	vector = buf;

			/* Xor the password into the MD5 digest */
			for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++)
		  	{
				*buf++ ^= passbuf[pc];
		  	}
		  }

		  total_length += padded_length + 2;

		  break;
		 default:
		  switch (vp->type)
		  {
		    case PW_TYPE_STRING:
			length = vp->lvalue;
			*buf++ = length + 2;
			if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2;
			memcpy (buf, vp->strvalue, (size_t) length);
			buf += length;
			total_length += length + 2;
			break;

		    case PW_TYPE_IPV6ADDR:
			length = 16;
			*buf++ = length + 2;
			if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2;
			memcpy (buf, vp->strvalue, (size_t) length);
			buf += length;
			total_length += length + 2;
			break;

		    case PW_TYPE_IPV6PREFIX:
			length = vp->lvalue;
			*buf++ = length + 2;
			if (vsa_length_ptr != NULL) *vsa_length_ptr += length + 2;
			memcpy (buf, vp->strvalue, (size_t) length);
			buf += length;
			total_length += length + 2;
			break;

		    case PW_TYPE_INTEGER:
		    case PW_TYPE_IPADDR:
		    case PW_TYPE_DATE:
			*buf++ = sizeof (uint32_t) + 2;
			if (vsa_length_ptr != NULL) *vsa_length_ptr += sizeof(uint32_t) + 2;
			lvalue = htonl (vp->lvalue);
			memcpy (buf, (char *) &lvalue, sizeof (uint32_t));
			buf += sizeof (uint32_t);
			total_length += sizeof (uint32_t) + 2;
			break;

		    default:
			break;
		  }
		  break;
		}
		vp = vp->next;
	}
	return total_length;
}

/** Appends a string to the provided buffer
 *
 * @param dest the destination buffer.
 * @param max_size the maximum size available in the destination buffer.
 * @param pos the current position in the dest buffer; initially must be zero.
 * @param src the source buffer to append.
 */
static void strappend(char *dest, unsigned max_size, int *pos, const char *src)
{
	unsigned len = strlen(src) + 1;

	if (*pos == -1)
		return;

	if (len + *pos > max_size) {
		*pos = -1;
		return;
	}

	memcpy(&dest[*pos], src, len);
	*pos += len-1;
	return;
}

/** Sends a request to a RADIUS server and waits for the reply
 *
 * @param rh a handle to parsed configuration
 * @param data a pointer to a #SEND_DATA structure
 * @param msg must be an array of %PW_MAX_MSG_SIZE or %NULL; will contain the concatenation of
 *	any %PW_REPLY_MESSAGE received.
 * @param flags must be %AUTH or %ACCT
 * @return %OK_RC (0) on success, %TIMEOUT_RC on timeout %REJECT_RC on acess reject, or negative
 *	on failure as return value.
 */


#define RECV_BUF_LEN    100
static char recv_buffer[RECV_BUF_LEN];

int rc_send_server (rc_handle *rh, SEND_DATA *data, char *msg, unsigned flags)
{
    struct sockaddr_in sa,ra;
    int             socket;
    fdsets          sets;
    AUTH_HDR*       auth;
    AUTH_HDR*       recv_auth;
    unsigned char   vector[AUTH_VECTOR_LEN];
    char            secret[MAX_SECRET_LENGTH + 1];
    int             total_length;
    int             sendLen, recvLen;
    int             length;
    int             pos;
    uint8_t*        attr;
    int             result = 0;
    VALUE_PAIR*     vp;
    
    char            rcNetParams[20];
    uint32_t        port;
    uint8_t         tmpLen;
    
    initFdsets(&sets);
    
	if(data->secret != NULL) {
		strlcpy(secret, data->secret, MAX_SECRET_LENGTH);
    }

    // Устанавливаем сетевые параметры
    memset(rcNetParams, 0, 20);
    GetRDSIpStr(rcNetParams, &tmpLen);
        
    // IP radius server
    memset(&ra, 0, sizeof(struct sockaddr_in));
    ra.sin_family = AF_INET;
    ra.sin_addr.s_addr = inet_addr(rcNetParams);
    
    // port
    memset(rcNetParams, 0, 20);
    GetRDSPortStr(rcNetParams, &tmpLen);
    port = atoi(rcNetParams);
    ra.sin_port = htons(port);
        
    socket = socket(PF_INET, SOCK_DGRAM, 0);
    if ( socket < 0 )
    {
        //printf("socket call failed");
        return -1;
    }
        
    // Build a request  (PW_ACCESS_REQUEST)
    auth = (AUTH_HDR *) msg;
	auth->code = data->code;
	auth->id = data->seq_nbr;

    rc_random_vector(vector);
	memcpy((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN);

	total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN;
	auth->length = htons ((unsigned short) total_length);
	        
    // Bind socket
    memset(rcNetParams, 0, 20);
    GetIpStr(rcNetParams, &tmpLen);
    
    memset(&sa, 0, sizeof(struct sockaddr_in));
    sa.sin_family = AF_INET;
    sa.sin_addr.s_addr = inet_addr(rcNetParams);
    sa.sin_port = htons(port);
    
    if (bind(socket, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) == -1)
    {
        //printf("Bind to Port Number %d ,IP address %s failed\n", DEVICE_PORT_NUM, DEVICE_IP_ADDR);
        close(socket);
        return -1;
    }
       
    sendLen = sendto(socket, (char*)auth, total_length, 0, (struct sockaddr*)&ra, sizeof(ra));
    if(sendLen < 0)
    {
        //printf("send failed\n");
        close(socket);
        return NET_ERR_RC;
    }
   
    // Подготовка буфера для приема
    memset(recv_buffer, 0, RECV_BUF_LEN);
    
    // Получение ответа, select
    if (!recvSelect(&sets, &socket, 2000)) {
        //printf("SOCK recv timeout!\r\n");
        close(socket);
        return NET_ERR_RC;
    }
    
    // Данные можно принимать
    socklen_t sl = sizeof(sa);
    recvLen = recvfrom(socket, recv_buffer, RECV_BUF_LEN, 0, (struct sockaddr*)&ra, &sl);

    recv_auth = (AUTH_HDR*)recv_buffer;
    
    // Проверки размера входящего сообщения
    if (recvLen < AUTH_HDR_LEN || recvLen < ntohs(recv_auth->length)) {
		//printf("radius_server: reply is too short\r\n");
		close(socket);
        return NET_ERR_RC;
	}
    
    if (recvLen > ntohs(recv_auth->length)) 
    {
        recvLen = ntohs(recv_auth->length);
    }
    
    // Verify that it's a valid RADIUS packet before doing ANYTHING with it.
	attr = recv_buffer + AUTH_HDR_LEN;
	while (attr < (recv_buffer + recvLen)) {
		if (attr[0] == 0) {
            //printf("radius_server: attribute zero is invalid\r\n");
            close(socket);
            return NET_ERR_RC;
		}

		if (attr[1] < 2) {
            //printf("radius_server: attribute length is too small\r\n");
            close(socket);
            return NET_ERR_RC;
		}

		if ((attr + attr[1]) > (recv_buffer + recvLen)) {
            //printf("radius_server: attribute overflows the packet\r\n");
            close(socket);
            return NET_ERR_RC;
		}

		attr += attr[1];
	}
    
    
    result = rc_check_reply(recv_auth, RECV_BUF_LEN, secret, vector, data->seq_nbr);
    
    length = ntohs(recv_auth->length)  - AUTH_HDR_LEN;
	if (length > 0) {
		data->receive_pairs = rc_avpair_gen(rh, NULL, recv_auth->data, length, 0);
	} else {
		data->receive_pairs = NULL;
	}

	if (result != OK_RC) {
		return result;
	}
    
    if (msg) {
		*msg = '\0';
		pos = 0;
		vp = data->receive_pairs;
		while (vp)
		{
			if ((vp = rc_avpair_get(vp, PW_REPLY_MESSAGE, 0)))
			{
				strappend(msg, PW_MAX_MSG_SIZE, &pos, vp->strvalue);
				strappend(msg, PW_MAX_MSG_SIZE, &pos, "\n");
				vp = vp->next;
			}
		}
	}

	if ((recv_auth->code == PW_ACCESS_ACCEPT) ||
		(recv_auth->code == PW_PASSWORD_ACK) ||
		(recv_auth->code == PW_ACCOUNTING_RESPONSE))
	{
        result = RC_GetAccessRights(recv_buffer);
	}
	else if ((recv_auth->code == PW_ACCESS_REJECT) ||
		(recv_auth->code == PW_PASSWORD_REJECT))
	{
		result = REJECT_RC;
	}
	else
	{
		rc_log(LOG_ERR, "rc_send_server: received RADIUS server response neither ACCEPT nor REJECT, invalid");
		result = BADRESP_RC;
	}
    
    //printf("\r\nRadius server end communication\r\n");
    close(socket);
    return result;
}

/** Verify items in returned packet
 *
 * @param auth a pointer to #AUTH_HDR.
 * @param bufferlen the available buffer length.
 * @param secret the secret used by the server.
 * @param vector a random vector of %AUTH_VECTOR_LEN.
 * @param seq_nbr a unique sequence number.
 * @return %OK_RC upon success, %BADRESP_RC if anything looks funny.
 */
static int rc_check_reply (AUTH_HDR *auth, int bufferlen, char const *secret, unsigned char const *vector, uint8_t seq_nbr)
{
	int             secretlen;
	int             totallen;
	unsigned char   calc_digest[AUTH_VECTOR_LEN];
	unsigned char   reply_digest[AUTH_VECTOR_LEN];
#ifdef DIGEST_DEBUG
	uint8_t		*ptr;
#endif

	totallen = ntohs (auth->length);
	secretlen = (int)strlen (secret);

	/* Do sanity checks on packet length */
	if ((totallen < 20) || (totallen > 4096))
	{
		rc_log(LOG_ERR, "rc_check_reply: received RADIUS server response with invalid length");
		return BADRESP_RC;
	}

	/* Verify buffer space, should never trigger with current buffer size and check above */
	if ((totallen + secretlen) > bufferlen)
	{
		rc_log(LOG_ERR, "rc_check_reply: not enough buffer space to verify RADIUS server response");
		return BADRESP_RC;
	}

	/* Verify that id (seq. number) matches what we sent */
	if (auth->id != seq_nbr)
	{
		rc_log(LOG_ERR, "rc_check_reply: received non-matching id in RADIUS server response");
		return BADRESP_RC;
	}

	/* Verify the reply digest */
	memcpy ((char *) reply_digest, (char *) auth->vector, AUTH_VECTOR_LEN);
	memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN);
	memcpy ((char *) auth + totallen, secret, secretlen);
#ifdef DIGEST_DEBUG
        rc_log(LOG_ERR, "Calculating digest on:");
        for (ptr = (u_char *)auth; ptr < ((u_char *)auth) + totallen + secretlen; ptr += 32) {
                char buf[65];
                int i;

                buf[0] = '\0';
                for (i = 0; i < 32; i++) {
                        if (ptr + i >= ((u_char *)auth) + totallen + secretlen)
                                break;
                        sprintf(buf + i * 2, "%.2X", ptr[i]);
                }
                rc_log(LOG_ERR, "  %s", buf);
        }
#endif
	rc_md5_calc (calc_digest, (unsigned char *) auth, totallen + secretlen);
#ifdef DIGEST_DEBUG
	rc_log(LOG_ERR, "Calculated digest is:");
        for (ptr = (u_char *)calc_digest; ptr < ((u_char *)calc_digest) + 16; ptr += 32) {
                char buf[65];
                int i;

                buf[0] = '\0';
                for (i = 0; i < 32; i++) {
                        if (ptr + i >= ((u_char *)calc_digest) + 16)
                                break;
                        sprintf(buf + i * 2, "%.2X", ptr[i]);
                }
                rc_log(LOG_ERR, "  %s", buf);
        }
	rc_log(LOG_ERR, "Reply digest is:");
        for (ptr = (u_char *)reply_digest; ptr < ((u_char *)reply_digest) + 16; ptr += 32) {
                char buf[65];
                int i;

                buf[0] = '\0';
                for (i = 0; i < 32; i++) {
                        if (ptr + i >= ((u_char *)reply_digest) + 16)
                                break;
                        sprintf(buf + i * 2, "%.2X", ptr[i]);
                }
                rc_log(LOG_ERR, "  %s", buf);
        }
#endif

	if (memcmp ((char *) reply_digest, (char *) calc_digest,
		    AUTH_VECTOR_LEN) != 0)
	{
		rc_log(LOG_ERR, "rc_check_reply: received invalid reply digest from RADIUS server");
		return BADRESP_RC;
	}

	return OK_RC;

}

/** Generates a random vector of AUTH_VECTOR_LEN octets
 *
 * @param vector a buffer with at least %AUTH_VECTOR_LEN bytes.
 */
static void rc_random_vector (unsigned char *vector)
{
	int             randno;
	int             i;
#if defined(HAVE_GETENTROPY)
	if (getentropy(vector, AUTH_VECTOR_LEN) >= 0) {
		return;
	} /* else fall through */
#elif defined(HAVE_DEV_URANDOM)
	int		fd;

/* well, I added this to increase the security for user passwords.
   we use /dev/urandom here, as /dev/random might block and we don't
   need that much randomness. BTW, great idea, Ted!     -lf, 03/18/95	*/

	if ((fd = open(_PATH_DEV_URANDOM, O_RDONLY)) >= 0)
	{
		unsigned char *pos;
		int readcount;

		i = AUTH_VECTOR_LEN;
		pos = vector;
		while (i > 0)
		{
			readcount = read(fd, (char *)pos, i);
			if (readcount >= 0) {
				pos += readcount;
				i -= readcount;
			} else {
				if (errno != EINTR && errno != EAGAIN)
					goto fallback;
			}
		}

		close(fd);
		return;
	} /* else fall through */
#endif
 fallback:
	for (i = 0; i < AUTH_VECTOR_LEN;)
	{
		randno = random ();
		memcpy ((char *) vector, (char *) &randno, sizeof (int));
		vector += sizeof (int);
		i += sizeof (int);
	}

	return;
}
