/*
 * radius_module.cpp
 *
 *  Created on: 10.8.2012
 *      Author: Petr Kramolis
 */
// This program is free software; you can redistribute it and/or
// modify it under the terms of the BUT OPEN SOURCE LICENCE
// Version 1 as published by the Brno University of Technology, Antonínská 548/1,
// 601 90, Czech Republic.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// BUT OPEN SOURCE LICENCE (BUTOSL) for more details.
// 
// You should have received a copy of the BUT OPEN SOURCE LICENCE (BUTOSL)
// along with this program; if not, write to the Brno University of Technology,
// Antonínská 548/1, 601 90, Czech Republic.



#define _LIB

#include <iostream>
#include <sstream>
#include <queue>


extern "C"{
#include <errno.h>
#include <time.h>
#include <sys/un.h>
#include <pcap.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
}

#include <string>

#include "ndwatch/common.h"
#include "ndwatch/ethernet.h"
#include "ndwatch/udp.h"
#include "ndwatch/tcp.h"
#include "ndwatch/ipv6.h"
#include "ndwatch/ipv4.h"
#include "ndwatch/icmpv6.h"

#include "radius.h"
#include "iricore_socket.h"
#include "lease_table.h"

int debug = 0;
char int_cvt_buf[32];
const char *xdigits = "0123456789abcdef";

unix_socket *usocket;
Lease_Table *aip_table;

struct ItemAndTime_t{
	uint64_t time;
	Lease_table_item item;
};

typedef std::map<int, struct ItemAndTime_t> transID_table;

void print_help(){
	std::cout << "RADIUS iri module:" << std::endl;
	std::cout << "radius [-f filter] [-i interface] [-p file] [-hs]" << std::endl;
	std::cout << "        -f filter         pcap_filter default is 'udp port 1812'" << std::endl;
	std::cout << "        -i interfacei     capture interface" << std::endl;
	std::cout << "        -p file           load pcap file instead of live capture" << std::endl;
	std::cout << "        -h                show this help" << std::endl;
	std::cout << "        -s                stand-alone - do not connect to iri-core" << std::endl;
}

void handle_msg(Octet &msg_type, int &transaction_id,
		Short status_code, Lease_table_item &item){

	static transID_table transIDs;
	std::map<int, struct ItemAndTime_t>::iterator it;
	std::vector<uint64_t> code_vector;
	uint64_t cur_time;
	cur_time = (uint64_t)time(NULL);
	uint64_t clean_time =0;

	struct ItemAndTime_t ItemAndTime;
	ItemAndTime.time = time(NULL);
	ItemAndTime.item = item;

	Lease_table_item reqest_item;

	switch(msg_type){
	case Radius::ACCESS_REQUEST:
		//std::cout << "Radius ACCESS_REQUEST" << std::endl;
		transIDs[transaction_id] = ItemAndTime;
		usocket->send_report( item, cur_time, "REPORT", "client requested access");
		//SEND REPORT HERE
		break;
	case Radius::ACCESS_ACCEPT:
		//std::cout << "Radius ACCESS_ACCEPT" << std::endl;
		//SEND CONTINUE OR REPORT HERE
		if (transIDs.count(transaction_id)>0){
			reqest_item = transIDs[transaction_id].item;
			reqest_item.append(item.ident_line());
			if(item.IPRecord()){
				usocket->send_report( reqest_item, cur_time, "BEGIN","access granted and IP address assigned");
			}else{
				usocket->send_report( reqest_item, cur_time, "BEGIN","access granted but no IP address assigned");
			}
		}

		break;
	case Radius::ACCESS_REJECT:
		//std::cout << "Radius ACCESS_REJECT" << std::endl;
		//SEND REPORT HERE
		if (transIDs.count(transaction_id)>0){
			reqest_item = transIDs[transaction_id].item;
			reqest_item.append(item.ident_line());
			usocket->send_report( reqest_item, cur_time, "REPORT", "access rejected");
		}
		break;
	case Radius::ACCOUNTING_REQUEST:
		//std::cout << "Radius ACCOUNTING_REQUEST" << std::endl;
		break;
	case Radius::ACCOUNTING_RESPONSE:
		//std::cout << "Radius ACCOUNTING_RESPONSE" << std::endl;
		break;
	case Radius::ACCESS_CHALLENGE:
		//std::cout << "Radius ACCESS_CHALLENGE" << std::endl;
		break;
	case Radius::STATUS_SERVER:
		//std::cout << "Radius STATUS_SERVER" << std::endl;
		break;
	case Radius::STATUS_CLIENT:
		//std::cout << "Radius STATUS_CLIENT" << std::endl;
		break;
	default:
		//std::cout << "DHCPv6 OTHER RADIUS" << std::endl;
		break;
	}

	//sometimes clean transaction id table... (remove record older than one min)
	if (cur_time > clean_time){
		clean_time = cur_time + 60;
		for ( it= transIDs.begin() ; it != transIDs.end(); it++ ){
			if(((*it).second).time + 60  < cur_time){
				transIDs.erase(it);
			}
		}
	}
}


void process_RADIUS(UDP *udp){
	Buffer mbuf;
	mbuf.buf(udp->get_data(), udp->length());
	Radius radius_pkt;
	radius_pkt.decode(mbuf);
	Radius_Attribute *rad_atr = NULL;
	int transaction_id = 0;
	int i = 0;
	Octet code;
	Lease_table_item item;

	code = radius_pkt.code();
	transaction_id = radius_pkt.identifier();
	while((rad_atr = radius_pkt.attribute(i)) != NULL){
		i++;
		if(rad_atr->type() == Radius_Attribute::USER_NAME ){
			Radius_Attr_User_Name *usr_atr;
			usr_atr = (Radius_Attr_User_Name *) rad_atr;
			//cout << "PARSED: "<< usr_atr->user_name() << std::endl;
			item.append("Radius Login",usr_atr->user_name());
		}
		else if(rad_atr->type() == Radius_Attribute::CALLING_STATION_ID ){
			Radius_Attr_Calling_Station_Id *station_atr;
			station_atr = (Radius_Attr_Calling_Station_Id *) rad_atr;
			//cout << "PARSED: "<< station_atr->station_id() << std::endl;
			item.append("MAC",station_atr->station_id());

		}
		else if(rad_atr->type() == Radius_Attribute::FRAMED_IP_ADDRESS ){
			Radius_Attr_Framed_IP_Address *ip_atr;
			ip_atr = (Radius_Attr_Framed_IP_Address *) rad_atr;
			//cout << "PARSED: "<< ip_atr->address()->to_string() << std::endl;
			item.append("IPv4",ip_atr->address()->to_string());
		}
		else if(rad_atr->type() == Radius_Attribute::FRAMED_IPV6_PREFIX ){
			Radius_Attr_Framed_IPv6_Prefix *ip_atr;
			ip_atr = (Radius_Attr_Framed_IPv6_Prefix *) rad_atr;
			//cout << "PARSED: "<< ip_atr->address()->to_string() << std::endl;
			item.append("IPv6",ip_atr->address()->to_string());
		}
	}

	handle_msg(code, transaction_id, 5, item);
}


int main(int argc, char **argv)
{
	Ethernet *eth_ptr = new Ethernet();
	Ethernet *eth = eth_ptr;
	Buffer pkt;
	string dev;
	char errbuf[PCAP_ERRBUF_SIZE];
	pcap_t *handle;
	struct pcap_pkthdr *header;
	const u_char *tmp;
	int stop=0;
	struct bpf_program fp;
	std::string pcap_file = "";
	std::string interface = "any";
	bool connect_to_iri = true;
        char filter_exp[] = "udp port 1812";
        char *ptr_filter;
	ptr_filter = filter_exp;

	int c,status;

	Ethernet::add_proto(0x0800, IPv4_factory);
	Ethernet::add_proto(0x86dd, IPv6_factory);
	IPv4::add_proto(ProtoTCP, TCP_factory);
	IPv6::add_proto(ProtoTCP, TCP_factory);
	IPv4::add_proto(ProtoUDP, UDP_factory);
	IPv6::add_proto(ProtoUDP, UDP_factory);
	IPv6::add_proto(ProtoICMPv6, ICMPv6_factory);


	while ((c = getopt (argc, argv, "f:p:i:hs")) != -1){
		switch (c)
		{
                case 'f':
                        ptr_filter = optarg;
                        break;
		case 'i':
			interface = optarg;
			break;
		case 'p':
			pcap_file = optarg;
			break;
		case 's':
			connect_to_iri = false;
			break;
		case 'h':
			print_help();
			return 0;
			break;
		default:
			break;
		}
	}


	usocket= new unix_socket("RADIUS",connect_to_iri);
	aip_table = new Lease_Table(usocket);

	if(pcap_file != ""){
		handle = pcap_open_offline(pcap_file.c_str(), errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open file: %s\n", errbuf);
			return(1);
		}
	} else {
		handle = pcap_open_live(interface.c_str(), BUFSIZ, 1, 0, errbuf);
		if (handle == NULL) {
			fprintf(stderr, "Couldn't open device: %s\n", errbuf);
			return(1);
		}
	}

        if (pcap_compile(handle, &fp, ptr_filter, 0, 0) == -1) {
                 fprintf(stderr, "Couldn't parse filter %s: %s\n", ptr_filter, pcap_geterr(handle));
                 return(1);
        }
        if (pcap_setfilter(handle, &fp) == -1) {
                fprintf(stderr, "Couldn't install filter %s: %s\n", ptr_filter, pcap_geterr(handle));
                return(1);
        }


	while(!stop){
		//read packet
		status = pcap_next_ex(handle, &header, &tmp);
		if(status == -2){
			stop = 1;
			continue;
		}else if(status == -1){
			pcap_perror(handle,NULL);
		}

		//store packet in buffer
		pkt.buf((const Octet *) tmp, header->len);

		//parse buffer data and get Eth layer
		if(!eth->decode(pkt)) {
			//cout << "cant decode packet! (skiped)\n";
			continue;
		}
		if (!eth->payload()){
			//cout  << "ethernet with no payload (skiped)\n" ;
			continue;
		}
		UDP *udp=NULL;
		if (dynamic_cast<IPv6 *>(eth->payload()) != NULL){
			IPv6 *ipv6 = dynamic_cast<IPv6 *>(eth->payload());
			if (ipv6->is_fragment()){
				//cout <<"IPv6 fragment (skiped)\n";
				continue;
			}

			if (dynamic_cast<UDP *>(ipv6->payload()) != NULL){
				udp = dynamic_cast<UDP *>(ipv6->payload());

			}
		}
		if (dynamic_cast<IPv4 *>(eth->payload()) != NULL){
			IPv4 *ipv4 = dynamic_cast<IPv4 *>(eth->payload());
			if (ipv4->is_fragment()){
				//cout << "IPv4 fragment (skiped)\n";
				continue;
			}
			if (dynamic_cast<UDP *>(ipv4->payload()) != NULL){
				udp = dynamic_cast<UDP *>(ipv4->payload());
			}
		}
		if(udp != NULL){
			if(udp->src_port()==1812 or udp->dst_port() == 1812){ //ports for radius
				process_RADIUS(udp);
			}
		}
	}
	pcap_close(handle);
	delete eth_ptr;
	delete usocket;
	delete aip_table;
}


