/*
Copyright (c) 1999
The Regents of The University of Michigan
All rights reserved

Permission is granted to use, copy and redistribute this software
for noncommercial education and research purposes, so long as no
fee is charged, and so long as the copyright notice above, this
grant of permission, and the disclaimer below appear in all copies
made; and so long as the name of The University of Michigan is not
used in any advertising or publicity pertaining to the use or
distribution of this software without specific, written prior
authorization.  Permission to modify or otherwise create derivative
works of this software is not granted.

This software is provided as is, without representation as to its
fitness for any purpose, and without warranty of any kind, either
express or implied, including without limitation the implied
warranties of merchantability and fitness for a particular purpose.
The Regents of The University of Michigan shall not be liable for
any damages, including special, indirect, incidental, or consequential
damages, with respect to any claim arising out of or in connection
with the use of the software, even if it has been or is hereafter
advised of the possibility of such damages.

Contact: info@citi.umich.edu
*/

/*
 * Read packets from tunnel device and send them to a smartcard
 *
 * Command line options:
 * -[01]  serial port to use, 1 is default
 * -l     log (to stdout) incoming connection requests
 * -v     log (to stdout) all apdus (but not their contents)
 * -V     log (to stdout) incoming and outgoing packets
 * -F     filter incoming requests
 * -s size truncate packets to 'size' bytes
 * -A aid
 * -N     do not attempt to select applet (for very old cards)
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sectok.h>

#define IP_CLA 0xfe
#define IP_INS 0xfe
#define IP_P1  0x00
#define IP_P2  0x21

#define FL_PSH 0x8
#define FL_SYN 0x2
#define FL_FIN 0x1

#define TCBTIMEOUT 10

char tunneldevname[] = "/dev/tun0";
char *aid = "\374Webcard";

int vflag, lflag, Fflag, Vflag, Nflag;

void logsyn(unsigned char *p, int len);

int
main(int ac, char *av[])
{
    extern int optind;
    int scfd, ipfd, port = 0, i, n, af, len, sw, maxpkt = 248;
    struct sockaddr_in tcbaddr, srcaddr;
    unsigned short tcbport = 0, srcport;
    char buf[100];
    static unsigned char pkt[1504];
    time_t t, tcbtime;
    int ipproto;

    while ((i = getopt(ac, av, "01lvFVNs:A:")) != -1) {
	switch (i) {
	case '0':
	case '1':
	    port = i - '0';
	    break;
	case 'l':
	    lflag = 1;
	    break;
	case 'v':
	    vflag = 1;
	    break;
	case 'F':
	    Fflag = 1;
	    break;
	case 'V':
	    Vflag = 1;
	    break;
	case 's':
	    maxpkt = atoi(optarg);
	    break;
	case 'A':
	    aid = optarg;
	    break;
	case 'N':
	    Nflag = 1;
	    break;
	}
    }

    setlinebuf(stdout);

    /* open reader */
    scfd = sectok_open(port, 0, &sw);
    if (scfd < 0) {
	printf("sectok_open: %s\n", sectok_get_sw(sw));
	exit(1);
    }

    /* reset */
    n = sectok_reset(scfd, 0, NULL, &sw);
    if (!n) {
	printf("sectok_reset: %s\n", sectok_get_sw(sw));
	exit(2);
    }

    /* open tunnel device */
    if (optind < ac) {
	sprintf(buf, "/sbin/ifconfig tun0 %s", av[optind]);
	system(buf);
    }
    ipfd = open(tunneldevname, 2);
    if (ipfd < 0) {
	perror(tunneldevname);
	exit(1);
    }
    /* why is this necessary? */
    system("/sbin/ifconfig tun0 up");

    if (!Nflag) {
	/*
	 * Select the webcard applet
	 * Try both class 0 and f0, and ignore any errors
	 */
	sectok_apdu(scfd, 0x00, 0xa4, 4, 0, strlen(aid), aid, 0, NULL, &sw);
	sectok_apdu(scfd, 0xf0, 0xa4, 4, 0, strlen(aid), aid, 0, NULL, &sw);
    }

    while (1) {
	n = read(ipfd, pkt, sizeof pkt);
	if (n <= 0) {
	    printf("eof\n");
	    break;
	}

	/* Save the address family, move the rest of the packet up */
	memmove(&af, pkt, 4);
	memmove(pkt, pkt + 4, n - 4);

	if (pkt[9] == 1) {
	    if (vflag)
		printf("dropping ICMP packet.\n");
	    continue;
	}

	if (pkt[0] != 0x45) {
	    printf("bad version or IHL %02x\n", pkt[0]);
	    continue;
	}

	/* Get len, source addr, port (if tcp) */
	len = (pkt[2] << 8) | pkt[3];
	if (len < 20 || len > 1500 || len != n - 4) {
	    printf("bad len %02x\n", len);
	    continue;
	}
	memmove(&srcaddr.sin_addr.s_addr, &pkt[12], 4);
	ipproto = pkt[9];
	if (ipproto == IPPROTO_TCP) {
	    memmove(&srcport, &pkt[20], 2);
	    srcport = ntohs(srcport);
	}

	if (Vflag) {
	    printf ("received pkt from %s:\n", inet_ntoa(srcaddr.sin_addr));
	    for (i = 0 ; i < len ; i ++ ) printf ("%02x ", pkt[i]);
	    printf ("\n");
	}

	if (ipproto == IPPROTO_TCP && (pkt[33] & FL_SYN)) {
	    /* Beginning of connection */
	    if (Fflag) {
		if (tcbport) {
		    time(&t);
		    if (t - tcbtime > TCBTIMEOUT)
			/* time out old connection */
			tcbport = 0;
		}
		if (!tcbport) {
		    /* No connection open; start one */
		    tcbaddr = srcaddr;
		    tcbport = srcport;
		} else
		    /* Already have a connection open; reject it */
		    continue;
	    }
	    if (lflag)
		logsyn(pkt, len);
	} else if (Fflag) {
	    if (tcbport && (srcaddr.sin_addr.s_addr != tcbaddr.sin_addr.s_addr
			    || srcport != tcbport))
		/* Packet does not belong to current connection; drop it */
		continue;
	}
	time(&tcbtime);

	if (len > maxpkt)
	    len = maxpkt;

	n = sectok_apdu(scfd, IP_CLA, IP_INS, IP_P1, IP_P2, len, pkt, maxpkt, pkt, &sw);
	if (!sectok_swOK(sw)) {
	    printf("sectok_apdu: %s\n", sectok_get_sw(sw));
	    continue;
	}

	if (vflag)
	    printf("sent pkt len %d status %s\n", len, sectok_get_sw(sw));
	if (n > 0 && vflag)
	    printf("rcvd pkt len %d\n", n);
	if (n <= 0)
	    continue;
	len = n;

	if (!(pkt[33] & (FL_SYN | FL_PSH | FL_FIN))) {
	    /* End of connection */
	    tcbport = 0;
	}

	if (Vflag) {
	    printf ("send pkt to network:\n");
	    for (i = 0 ; i < len ; i ++ )
		printf("%02x ", pkt[i]);
	    printf("\n");
	}

	/* Insert address family and write to tunnel */
	memmove(pkt + 4, pkt, len);
	memmove(pkt, &af, 4);
	write(ipfd, pkt, len + 4);
    }

    sectok_close(scfd);
    close(ipfd);
    exit(0);
}

void
logsyn(unsigned char *p, int len)
{
    time_t now;
    struct tm *tmp;
    char dbuf[40];
    struct sockaddr_in srcaddr;
    unsigned short dstport;

    time(&now);
    tmp = localtime(&now);
    if (!strftime(dbuf, sizeof dbuf, "%d/%b/%Y:%T %Z", tmp))
	return;

    /* Get source address and destination port */
    memmove(&srcaddr.sin_addr.s_addr, &p[12], 4);
    memmove(&dstport, &p[22], 2);
    dstport = ntohs(dstport);

    printf("%s - - [%s] port %d\n", inet_ntoa(srcaddr.sin_addr), dbuf, dstport);
}
