/*
 * ASCEND: %W% (%E% %U%)
 *
 *      Copyright (c) 1993 Ascend Communications, Inc.
 *      All rights reserved.
 *      Use of copyright notice does not imply publication.
 *
 *
 *                      CONFIDENTIAL INFORMATION
 *                      -------------------------
 *	This Document contains Confidential Information or Trade Secrets,
 *	or both, which are the property of Ascend Communications, Inc.
 *	This document may not be copied, reproduced, reduced to any
 *	electronic medium or machine readable form or otherwise duplicated
 *	and the information herein may not be used, disseminated or
 *	otherwise disclosed, except with the prior written consent of
 *	Ascend Communications, Inc.
 *
 *	Ascend Communications, Inc. makes no representations about
 *	the suitability of this software for any purpose.  It is
 *	provided "as is" without express or implied warranty.
 */

#include	<sys/types.h>
#include	<sys/socket.h>
#include	<netinet/in.h>

#include	<stdio.h>
#include	<unistd.h>
#include	<netdb.h>
#include	<pwd.h>
#include	<stdlib.h>
#include	<sys/time.h>	/* gettimeofday() */

#include	"radius.h"
#include	"protos.h"


u_char		recv_buffer[4096];
u_char		send_buffer[4096];
u_char		*progname;
int		sockfd;
u_char		debug_flag;

static void usage P__((void));
        /* udp_port promoted due to old-style function definition */
static int result_recv P__((u_char *buffer, int length, u_char	*vector,
  				char	*secret));
static int make_pkt P__(( u_char *buffer, int maxlen, UINT4 sessionId,
		char *userName, UINT4 ipAddr,
		int newMsgId, char *secret,
		int sendChgFilterCmd,
		VALUE_PAIR *filterHdr ));
static int get_client_secret P__(( UINT4 rqstIpAddr, char *secret ));
int filterBinary P__((VALUE_PAIR *pair, char *valStr ));

int main P__((int argc, u_char **argv));

int
main( argc, argv )
  int		argc;
  u_char	**argv;
{
	int			salen;
	int			result;
	struct	sockaddr	salocal;
	struct	sockaddr	saremote;
	struct	sockaddr_in	*sin;
	struct	servent		*svp;
        u_short                 svc_port;
	UINT4			serv_ipaddr;
	int			total_length;
	int			i;
	char			argval;
	char			anotherArg;

	UINT4			sessionId;
	char			*userName;
	UINT4			ipAddr;
	char			*psecret;
	AUTH_HDR		*auth;
	VALUE_PAIR		*pair;
	VALUE_PAIR		*filterPairHdr;
	VALUE_PAIR		*filterPairTail;
	int			sendChgFilterCmd;
	char			*pchar;
	char			secret[256];


	progname = *argv++;
	argc--;

	filterPairHdr = filterPairTail = NULL;
	sessionId = 0;
	userName = NULL;
	psecret = NULL;
	ipAddr = 0;
	debug_flag = 0;
	svc_port = 0;
	serv_ipaddr = 0;
	sendChgFilterCmd = 0;


	while(argc) {

	    if( **argv != '-' ) {
		printf("missing minus sign, got %c\n", **argv);
		usage();
	    }

	    argval = *(*argv + 1);
	    argc--;
	    argv++;

	    anotherArg = 1;

	    switch(argval) {

		case 'P':		/* UDP port of server */
		    if (argc == 0) {
			printf("missing port#\n");
			usage();
		    }
		    svc_port = atoi((char *)*argv);
		    break;

		case 'H':		/* IP addr of server */
		    if (argc == 0) {
			printf("missing server addr\n");
			usage();
		    }
		    serv_ipaddr = get_ipaddr( (char *)*argv );
		    break;

		case 'K':		/* shared secret */
		    if (argc == 0) {
			printf("missing secret\n");
			usage();
		    }
		    psecret = (char *) *argv;
		    break;

		case 's':		/* session ID# assoc w session */
		    if (argc == 0) {
			printf("missing session ID#\n");
			usage();
		    }
		    sessionId = atol((char *)*argv);
		    break;
		
		case 'u':		/* name of user assoc w session */
		    if (argc == 0) {
			printf("missing user name\n");
			usage();
		    }
		    userName = (char *) *argv;
		    break;
		
		case 'i':		/* IP addr assoc w session */
		    if (argc == 0) {
			printf("missing IP addr\n");
			usage();
		    }
		    ipAddr = get_ipaddr( (char *)*argv );
		    break;

		case 'c':		/* call filter */
		case 'd':		/* data filter */
		    if (argc == 0) {
			printf("missing filter string\n");
			usage();
		    }
		    sendChgFilterCmd = 1;
		    pchar = (char *)*argv;
		    if ( *pchar == 0 ) {	/* ...NULL filter??? */
			break;
		    }
 		    if((pair = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) ==
						(VALUE_PAIR *)NULL) {
			printf("%s: no memory\n", progname);
			exit(MEMORY_ERR);
		    }
		    if ( argval == 'd' ) {
			strcpy(pair->name, "Ascend-Data-Filter");
			pair->attribute = 242;
		    } else {
			strcpy(pair->name, "Ascend-Call-Filter");
			pair->attribute = 243;
		    }
		    pair->type = PW_TYPE_FILTER_BINARY;

		    if ( filterBinary( pair, (char *)*argv ) != -1 ) {
			pair->size = pair->lvalue;
			pair->next = NULL;
			if ( filterPairTail ) {
			    filterPairTail->next = pair;
			    filterPairTail = pair;
			} else {
			    filterPairHdr = filterPairTail = pair;
			}
		    }
		    break;

		case 'x':		/* debugging flag */
		    debug_flag = 1;
		    anotherArg = 0;
		    break;
		
		default:
		    printf("unknown argval = %c\n", argval);
		    usage();
	    }
	    if ( anotherArg ) {
		argc--;
		argv++;
	    }
	}/*while*/

	if ( serv_ipaddr && !psecret ) {
	    if ( get_client_secret( serv_ipaddr, secret ) ) {
		psecret = &secret[0];
	    }
	}

	if ( !psecret ||
	     !svc_port ||
	     !serv_ipaddr ) {
	    (void) perror ("missing server addr, port, or secret");
	    exit(-1);
	}

	sockfd = socket (AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0) {
	    (void) perror ("socket");
	    exit(-1);
	}

	sin = (struct sockaddr_in *) & salocal;
        memset ((char *) sin, '\0', sizeof (salocal));
	sin->sin_family = AF_INET;
	sin->sin_addr.s_addr = INADDR_ANY;
	sin->sin_port = htons(svc_port);

	if ( bind( sockfd, &salocal, sizeof (struct sockaddr_in) ) < 0 ) {
	    close(sockfd);
	    (void) perror ("bind");
	    exit(-1);
	}

	/* Build a session disconnect OR change filter request pkt... */
	total_length = make_pkt( send_buffer,
					sizeof(send_buffer),
					sessionId,
					userName,
					ipAddr,
					1,
					psecret,
					sendChgFilterCmd,
					filterPairHdr );

	if ( debug_flag ) {
	    printf( "sessionId = %lu, name=<%s>, IpAddr=%lx, len=%d\n",
			sessionId, userName, ipAddr, total_length );
	    pair = filterPairHdr;
	    while ( pair ) {
		printf( "filter len = %d\n", pair->size );
		pair = pair->next;
	    }
	}


	auth = (AUTH_HDR *) send_buffer;

	sin = (struct sockaddr_in *) & saremote;
        memset ((char *) sin, '\0', sizeof (saremote));
	sin->sin_family = AF_INET;
	/*** sin->sin_addr.s_addr = serv_ipaddr; ***/
	sin->sin_addr.s_addr = htonl(serv_ipaddr);
	sin->sin_port = htons(svc_port);

	if ( debug_flag ) {
	    printf( "sending rqst to %lx at port=%d\n",
			serv_ipaddr, svc_port );
	}
	sendto(sockfd, (char *)send_buffer, (int)total_length, (int)0,
			&saremote, sizeof(struct sockaddr_in));

	salen = sizeof (saremote);
	result = recvfrom (sockfd, (char *) recv_buffer,
			(int) sizeof(recv_buffer),
			(int) 0, & saremote, & salen);

	if(result > 0) {
	    result_recv( recv_buffer, result, auth->vector, psecret );
	    exit(0);
	}
	(void) perror ("recv");
	close(sockfd);
	return 0;
}

int
result_recv( buffer, length, vector, secret )
  u_char	*buffer;
  int		length;
  u_char	*vector;
  char		*secret;
{
	AUTH_HDR	*auth;
	int		totallen;
	u_char		reply_digest[AUTH_VECTOR_LEN];
	u_char		calc_digest[AUTH_VECTOR_LEN];
	int		secretlen;

	auth = (AUTH_HDR *)buffer;
	totallen = ntohs(auth->length);

	if (totallen > length) {
	    printf("invalid reply length from server!\n");
	    return(-1);
	}

	/* check the reply digest... */
	memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
	memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
	secretlen = strlen(secret);
	memcpy(buffer + totallen, secret, secretlen);
	md5_calc( calc_digest, (u_char *)auth, totallen + secretlen );

	if(memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0) {
	    printf("invalid reply digest from server!\n");
	    return(-2);
	}

	switch (auth->code) {
	    case PW_ASCEND_DISCONNECT_ACK:
		printf("Session disconnect accepted!\n");
		break;
	    case PW_ASCEND_DISCONNECT_NAK:
		printf("Session disconnect rejected!\n");
		break;
	    case PW_ASCEND_CHG_FILTERS_ACK:
		printf("Filter change accepted!\n");
		break;
	    case PW_ASCEND_CHG_FILTERS_NAK:
		printf("Filter change rejected!\n");
		break;
	    default:
		printf("Received unknown code: %u!\n", auth->code);
		break;
	}
	return( 0 );
}

void
usage()
{
	printf("Usage: %s -H servAddr -P port -K secret "
		"[-s sessID] [-u name] [-i ipAddr] [-x]\n",
				progname);
	exit(-1);
}


    /*
     * make_pkt
     *
     *	Build an Ascend RADIUS disconnect request OR
     *	change filter packet.
     *	Function returns length of packet if successfully
     *	built otherwise returns 0.
     *
     *	buffer		Buffer to hold request packet.
     *
     *	maxlen		Max length of 'buffer'.
     *
     *	sessionId	Reference number of the session to
     *			disconnect or change filters for.
     *			Sent only if NOT 0.
     *
     *	userName	User name associated with the session
     *			to disconnect or change filters for.
     *			Sent only if NOT NULL AND NOT the NULL
     *			string.
     *
     *	ipAddr		IP address of the session to disconnect
     *			or change filters for.
     *			Sent only if NOT 0.
     *
     *	newMsgId	The message ID value to load.
     *
     *	secret		Pointer to the shared secret used in
     *			doing MD5 calculations.
     *
     *	filterPairHdr	List of filters to send.  If NULL then
     *			this is a disconnect request packet.
     */
int
make_pkt( buffer, maxlen, sessionId,
		userName, ipAddr, newMsgId, secret,
		sendChgFilterCmd, filterPairHdr )
  u_char	*buffer;
  int		maxlen;
  UINT4		sessionId;
  char		*userName;
  UINT4		ipAddr;
  int		newMsgId;
  char		*secret;
  int		sendChgFilterCmd;
  VALUE_PAIR	*filterPairHdr;
{
    AUTH_HDR	*auth;			/* ptr to packet header fields */
    int		pktLen;			/* packet length */
    int		md5_len;		/* #bytes for MD5 calculations */
    u_char	*ptr;			/* working ptr */

    char	sessBuf[12];		/* to convert sessionId to a str */
    int		sessBufLen;		/* length of 'sessBuf' */
    int		userNameLen;		/* length of 'userName' */
    int		secretLen;		/* length of 'secret' */
    UINT4	lvalue;
    u_char	digest[ AUTH_VECTOR_LEN ];	/* to hold results of the */
						/*   MD5 calculations */
    VALUE_PAIR	*p;			/* for traversing filter list */


	/* determine the length of the packet... */
	pktLen = AUTH_HDR_LEN;

	if ( sessionId ) {
	    sprintf( sessBuf, "%lu", sessionId );
	    sessBufLen = strlen( sessBuf );
	    pktLen += 2 + sessBufLen;
	}

	if ( userName && *userName ) {
	    userNameLen = strlen( userName );
	    pktLen += 2 + userNameLen;
	} else {
	    userNameLen = 0;
	}

	if ( ipAddr ) {
	    pktLen += 2 + sizeof(UINT4);
	}

	/* make sure caller provided at least 1 session specifier... */
	/* quit if none were given... */
	if ( pktLen == AUTH_HDR_LEN ) {
	    fprintf(stderr, "make_pkt: no session specifier!\n");
	    return(0);
	}

	if ( sendChgFilterCmd ) {
	    for( p = filterPairHdr; p; p=p->next ) {
		pktLen += 2 + p->lvalue;
	    }
	}

	secretLen = strlen(secret);

	/* we calculate the MD5 digest over the entire packet */
	/* with the shared secret appended so make sure buffer */
	/* is large enough... */
	md5_len = pktLen + secretLen;
	if ( md5_len > maxlen ) {
	    fprintf(stderr, "make_pkt: buffer too short!\n");
	    return(0);
	}

	memset( buffer, 0, pktLen );

	/* load packet header EXCEPT authenticator field... */
	auth = (AUTH_HDR *)send_buffer;
	auth->code   = (sendChgFilterCmd ? PW_ASCEND_CHG_FILTERS_REQUEST :
					   PW_ASCEND_DISCONNECT_REQUEST);
	auth->id     = (u_char) newMsgId;
	auth->length = htons(pktLen);

	/* now load the attributes... */

	ptr = auth->data;

	/* add the session ID if it was given... */
	if ( sessionId ) {
	    *ptr++ = PW_ACCT_SESSION_ID;
	    *ptr++ = 2 + sessBufLen;
	    memcpy( ptr, sessBuf, sessBufLen );
	    ptr += sessBufLen;
	}

	/* add the user name if it was given... */
	if ( userNameLen ) {
	    *ptr++ = PW_USER_NAME;
	    *ptr++ = 2 + userNameLen;
	    memcpy( ptr, userName, userNameLen );
	    ptr += userNameLen;
	}

	/* add the IP address if it was given... */
	if ( ipAddr ) {
	    *ptr++ = PW_FRAMED_ADDRESS;
	    *ptr++ = 2 + sizeof(UINT4);
	    lvalue = htonl(ipAddr);
	    memcpy( ptr, (u_char *)&lvalue, sizeof(UINT4) );
	    ptr += sizeof(UINT4);
	}

	if ( sendChgFilterCmd ) {
	    for( p = filterPairHdr; p; p=p->next ) {
		*ptr++ = p->attribute;
		*ptr++ = 2 + p->lvalue;
		memcpy( ptr, p->strvalue, p->lvalue );
		ptr += p->lvalue;
	    }
	}

	/* append the shared secret... */
	memcpy( ptr, secret, secretLen );

	/* calculate the MD5 digest over the entire packet */
	/* with the shared secret appended...note that the */
	/* authenticator field is all 0's at this point... */
	md5_calc( digest, buffer, md5_len );

	/* copy resulting digest into authenticator field... */
	memcpy( auth->vector, digest, AUTH_VECTOR_LEN );

	/* for security reasons, zero out the secret from buffer... */
	memset( ptr, 0, secretLen );

	return( pktLen );				/* A-OK */
}

    /* Dummy stub module needed by filterBinary() */
    /* or one of its subordinates. */
int
log_err(msg)
  char *msg;
{
}

int
get_client_secret( rqstIpAddr, secret )
  UINT4   rqstIpAddr;
  char    *secret;
{
	FILE    *clientfd;
	u_char  buf[1024];
	UINT4   ipaddr;
	char	hostnm[256];

	secret[0] = '\0';

	/* Find the client in the database */
	sprintf((char *)buf, "%s/%s", RADIUS_DIR, RADIUS_CLIENTS);
	if((clientfd = fopen((CONST char *)buf, "r")) == (FILE *)NULL) {
		printf( "%s: Couldn't open %s to find client\n",
				progname, buf);
		return( 0 );
	}
	while(fgets((char *)buf, sizeof(buf), clientfd) != (char *)NULL) {
		if(*buf == '#') {
			continue;
		}
		if(sscanf((CONST char *)buf, "%s%s", hostnm, secret) != 2) {
			continue;
		}

		/*
		 * Validate the requesting IP address -
		 * Not secure, but worth the check for accidental requests
		 */
		ipaddr = get_ipaddr(hostnm);
		if(ipaddr == rqstIpAddr) {
			break;
		}
		secret[0] = '\0';
	}
	fclose(clientfd);

	return ( ipaddr ? 1 : 0 );
}

