/* Really simple radius authenticator
 *
 * Copyright (c) 2004 Michael Gernoth <michael@gernoth.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "radius.h"
#include "wireless.h"
#include "wlioctl.h"
#include "bcmnvram.h"

#define WAIT	300	/* Seconds until a STA expires */

#define DEBUG 1

struct  sta {
	        unsigned char mac[6];
		unsigned char accepted;
		unsigned char changed;
		struct sta *next; /* Pointer to next STA in linked list */
		time_t lastseen;
};

char *server, *secret;
short port;
short macfmt;

int authmac(unsigned char *mac)
{
	char macstr[32];
	if (macfmt == 1) {
		sprintf(macstr,"%2.2x%2.2x%2.2x-%2.2x%2.2x%2.2x",\
				mac[0],mac[1],mac[2],mac[3],\
				mac[4],mac[5]);
	} else {
		sprintf(macstr,"%2.2X-%2.2X-%2.2X-%2.2X-%2.2X-%2.2X",\
				mac[0],mac[1],mac[2],mac[3],\
				mac[4],mac[5]);
	}

	return radius(server, port, macstr, secret, secret);
	
}

int main(int argc, char** argv)
{
	int num,i;
	unsigned char buf[WLC_IOCTL_MAXLEN];	/* Buffer for wireless-ioctls MAC lists */
	unsigned char *pos;
	char *iface;
	struct maclist *maclist;
	struct ether_addr *ea;
	struct sta *first;	/* Pointer to first element in linked STA list */
	int val;
	int lastcnt;		/* Number of blacklisted cards in the last loop */
	int statechange;	/* Do we need to push the new blacklist/reset the card? */
	time_t step;


	if ( argc < 2 )
	{
		fprintf(stderr,"wrt-radauth - A simple radius authenticator\n");
		fprintf(stderr,"(C) 2004 Michael Gernoth, some modifications by Sebastian Gottschall\n");
		fprintf(stderr,"Usage: %s [-n] interface\n",argv[0]);
		fprintf(stderr,"\t-n\tUse new MAC address format 'aabbcc-ddeeff' instead of 'AA-BB-CC-DD-EE-FF'\n");
		exit(1);
	}

#ifdef DEBUG
	printf("$Id: wrt-radauth.c,v 1.19 2005/02/12 15:01:49 simigern Exp $ coming up...\n");
#endif

	if (argc>2 && (strcmp(argv[1],"-n") == 0))
	{
		macfmt=1;
		iface=argv[2];
	} else {
		macfmt=0;
		iface=argv[1];
	}

	if (wl_probe(iface))
	{
		printf("Interface %s is not broadcom wireless!\n",iface);
	}

	/* Get configuration from nvram */
	server=strdup(nvram_get("wl0_radius_ipaddr"));
	port=atoi(nvram_get("wl0_radius_port"));
	secret=strdup(nvram_get("wl0_radius_key"));
#ifdef DEBUG
	printf("Server: %s:%d, Secret: %s\n",server,port,secret);
#endif

	/* Initialize vars */
	lastcnt=0;

	/* Disable MAC security on card */
	memset(buf,0,sizeof(buf));
	wl_ioctl(iface, WLC_SET_MACLIST, buf, sizeof(buf));
	val = WLC_MACMODE_DISABLED;
	wl_ioctl(iface,WLC_SET_MACMODE, &val, sizeof(val));

	/* No STAs in list */
	first = NULL;

	while(1)
	{
		struct sta *currsta, *prev;

		step=time(NULL);

		/* Initialize vars */
		statechange=0;

		/* Query card for currently associated STAs */
		memset(buf,0,sizeof(buf));
		getassoclist(iface,buf);
		pos=buf;
		memcpy(&num,pos,4);	/* TODO: This really is struct maclist */
		pos+=4;

		
		/* Look at the associated STAs */
		for(i=0; i<num; i++)
		{
			currsta = first;
			prev = NULL;

			/* Have we already seen this STA */
			while ( currsta != NULL )
			{
				if (memcmp(currsta->mac,pos,6) == 0)
				{
					if ( currsta->lastseen+WAIT < step )
					{
						currsta->changed=1;
						if (authmac(pos)>0)
						{
							currsta->accepted=1;
#ifdef DEBUG
							printf("Reauthenticating STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
									currsta->mac[0], currsta->mac[1],
									currsta->mac[2], currsta->mac[3],
									currsta->mac[4], currsta->mac[5]);
#endif
						} else {
							currsta->accepted=0;
						}
						currsta->lastseen = step;
					}

					if ( ! currsta->accepted )
					{
						currsta->changed=1;
#ifdef DEBUG
						printf("Rejecting STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
								currsta->mac[0], currsta->mac[1],
								currsta->mac[2], currsta->mac[3],
								currsta->mac[4], currsta->mac[5]);
#endif
					}
					break;
				}
				prev = currsta;
				currsta = currsta->next;
			}
			
			/* Or is it new? */
			if ( currsta == NULL )
			{
				/* Alloc mem for new STA */
				currsta = malloc(sizeof(struct sta));
				if ( currsta == NULL )
				{
					perror("malloc");
					exit(1);
				}

				if ( first == NULL )
				{
					first = currsta;
				} else {
					prev->next = currsta;
				}

				currsta->next = NULL;
				currsta->changed = 1;

				memcpy(currsta->mac,pos,6);
				currsta->lastseen = step;
				if (authmac(currsta->mac)==1)
				{
					currsta->accepted = 1;
#ifdef DEBUG
					printf("Accepting STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
							currsta->mac[0], currsta->mac[1],
							currsta->mac[2], currsta->mac[3],
							currsta->mac[4], currsta->mac[5]);
#endif
				} else {
					currsta->accepted = 0;
#ifdef DEBUG
					printf("Rejecting STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
							currsta->mac[0], currsta->mac[1],
							currsta->mac[2], currsta->mac[3],
							currsta->mac[4], currsta->mac[5]);
#endif
				}
			}
			pos+=6;	/* Jump to next MAC in list */
		}

		/* Expire old STAs from list, free memory */
		currsta = first;
		prev = NULL;
		while ( currsta != NULL )
		{
			if ( currsta->lastseen+WAIT < step )
			{
				struct sta *tmpsta;

				statechange=1;
#ifdef DEBUG
				printf("Expiring STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
						currsta->mac[0], currsta->mac[1],
						currsta->mac[2], currsta->mac[3],
						currsta->mac[4], currsta->mac[5]);
#endif
				
				if ( currsta == first )
				{
					tmpsta = first;
					first = first->next;
					free(tmpsta);
					currsta = first;
				} else {
					tmpsta = currsta;
					prev->next = currsta->next;
					free(currsta);
					currsta = prev->next;
				}

			} else {
				prev = currsta;
				currsta = currsta->next;
			}
		}

		/* Find STAs to kick off */
		memset(buf,0,sizeof(buf));
		maclist = (struct maclist *) buf;
		maclist->count=0;
		ea = maclist->ea;
		currsta = first;
		while ( currsta != NULL )
		{
			if ( ! currsta->accepted )
			{
				memcpy(ea,currsta->mac,6);
				ea++;
				maclist->count++;
			}
			if ( currsta->changed )
			{
				statechange=1;
				currsta->changed=0;
			}
			currsta = currsta->next;
		}

		/* statechange = Previously unseen/denied STA seen or STA expired */
		if ( statechange )
		{
			if ( maclist->count )
			{
				unsigned char mac[6];

				wl_ioctl(iface, WLC_SET_MACLIST, buf, sizeof(buf));
				val = WLC_MACMODE_DENY;
				wl_ioctl(iface,WLC_SET_MACMODE, &val, sizeof(val));

				ea = maclist->ea;
				for(i=0; i < maclist->count; i++)
				{
					memcpy(mac,ea,6);
#ifdef DEBUG
					printf("Disassociating STA %2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x\n",
							mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
#endif
					wl_ioctl(iface, 0x8f, mac, 6);	/* Kick station off AP */
					ea++;
				}
			} else {
				if ( lastcnt != 0 )
				{
					/* The card does not accept any association with an empty
					 * deny-list, so disable MAC-security */
#ifdef DEBUG
					printf("Resetting security\n");
#endif
					val = WLC_MACMODE_DISABLED;
					wl_ioctl(iface,WLC_SET_MACMODE, &val, sizeof(val));
				}
			}
			lastcnt = maclist->count;
		}

		/* Immediately continue after a statechange */
		if ( ! statechange )
			sleep(1);

	}
	
	exit(0);
}

