/*-
 * Copyright (c) 2003 Robert N. M. Watson
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD$
 */
#include <sys/types.h>
#include <sys/stat.h>

#include <net/bpf.h>
#include <net/ethernet.h>

#include <netinet/in.h>

#include <dev/ethercons/ethercons.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "bpf.h"

#define	ETHERCONS_LOGSUBDIR	"ethercons_log"
#define	ETHER_ADDRESS_MAX_STRING	(6*2+5+1)

#define	MIN(x, y)	((x) < (y) ? (x) : (y))

void
usage(void)
{

	fprintf(stderr, "ethercons log interface\n");
	fprintf(stderr, "ethercons send interface address string\n");
	fprintf(stderr, "ethercons sendcr interface address string\n");
	fprintf(stderr, "ethercons tail interface address\n");
	fprintf(stderr, "ethercons tailall interface\n");
	exit(0);
}

/*
 * Parse an ethernet address from a string: this is stolen from similar
 * kernel code in src/sys/dev/ethercons/ethercons.c, make sure to update
 * that too.
 */
static int
parse_ethernet_addr(char *string, u_char *target_addr)
{
	u_char temp_addr[ETHER_ADDR_LEN];
	char *cp, *endp;
	long val;
	int i;

	i = 0;
	while ((cp = strsep(&string, ":")) != NULL) {
		if (cp[0] == '\0')
			return (EINVAL);
		val = strtol(cp, &endp, 16);
		if (*endp != '\0' || val < 0 || val > 255)
			return (EINVAL);
		temp_addr[i] = val;     
		i++;
		if (i > ETHER_ADDR_LEN)
			return (EINVAL);
	}
	if (i != ETHER_ADDR_LEN)
		return (EINVAL);
	bcopy(temp_addr, target_addr, ETHER_ADDR_LEN);
	return (0);
}

/*
 * Given a packet from the wire, make sure it's a valid ethercons
 * packet, then return the source address (if desired), and pointer+len
 * to data.  Also, guarantee that the packet is nul-terminated, which
 * means at one byte of data.
 */
int
ethercons_from_packet(struct packet *pkt, u_char **address, char **buffer,
    int *len)
{
	struct ethercons_header *ech;
	struct ether_header *eh;

	if (pkt->p_len < sizeof(*eh) + sizeof(*ech) + 1)
		return (-1);
	eh = (struct ether_header *)pkt->p_data;
	if (eh->ether_type != htons(ETHERTYPE_ETHERCONS))
		return (-1);
	ech = (struct ethercons_header *)(eh + 1);
	if (ech->eh_version != htons(ETHERCONS_VERSION))
		return (-1);
	if (ntohs(ech->eh_flags) & ETHERCONS_FLAG_INPUT)
		return (-1);
	pkt->p_data[pkt->p_len - 1] = '\0';

	if (address != NULL)
		*address = eh->ether_shost;
	*buffer = (char *)(ech + 1);
	*len = pkt->p_len - sizeof(*ech);
	return (0);
}

void
ethercons_log(int argc, char *argv[])
{
	char filename[sizeof(ETHERCONS_LOGSUBDIR) + 1 +
	    ETHER_ADDRESS_MAX_STRING];
	struct packet *pkt;
	u_char *address;
	FILE *outfile;
	char *data;
	int len;

	if (argc != 1)
		usage();

	if (bpf_open(0, argv[0]) == -1) {
		perror(argv[0]);
		return;
	}

	(void)mkdir(ETHERCONS_LOGSUBDIR, 0600);
	while ((pkt = bpf_nextpacket()) != NULL) {
		if (ethercons_from_packet(pkt, &address, &data, &len) == -1) {
			bpf_freepacket(pkt);
			continue;
		}

		snprintf(filename, sizeof(ETHERCONS_LOGSUBDIR) + 1 +
		    ETHER_ADDRESS_MAX_STRING,
		    "%s/%02x:%02x:%02x:%02x:%02x:%02x",
		    ETHERCONS_LOGSUBDIR, address[0], address[1], address[2],
		    address[3], address[4], address[5]);

		outfile = fopen(filename, "a");
		if (outfile != NULL) {
			if (fprintf(outfile, "%s", data) == -1)
				perror(filename);
			fclose(outfile);
		} else
			perror(filename);

		bpf_freepacket(pkt);
	}
	perror(argv[0]);
	bpf_close();
}

void
ethercons_send(int argc, char *argv[])
{
	int copylen, error, i, len, left;
	u_char address[ETHER_ADDR_LEN];
	struct ethercons_header *ech;
	struct ether_header *eh;
	char *packetdata;

	if (argc < 3)
		usage();

	error = parse_ethernet_addr(argv[1], address);
	if (error != 0) {
		fprintf(stderr, "ethercons_send: %s\n", strerror(error));
		return;
	}

	if (bpf_open(0, argv[0]) == -1) {
		perror(argv[0]);
		return;
	}

	argv += 2;
	argc -= 2;

	len = 0;
	for (i = 0; i < argc; i++)
		len += strlen(argv[i]);

	if (len > 1024 || len < 1) {
		fprintf(stderr, "ethercons_send: bad length\n");
		return;
	}
	len += 1;

	packetdata = malloc(sizeof(*eh) + sizeof(*ech) + len);
	if (packetdata == NULL) {
		perror("ethercons_send");
		return;
	}

	eh = (struct ether_header *)packetdata;
	bzero(eh, sizeof(*eh));
	bcopy(address, eh->ether_dhost, ETHER_ADDR_LEN);
	eh->ether_type = htons(ETHERTYPE_ETHERCONS);
	ech = (struct ethercons_header *)(eh + 1);
	ech->eh_version = htons(ETHERCONS_VERSION);
	ech->eh_flags = htons(ETHERCONS_FLAG_INPUT);
	packetdata = (char *)(ech + 1);

	left = len;
	for (i = 0; i < argc; i++) {
		copylen = MIN(strlen(argv[i]), left);

		bcopy(argv[i], packetdata + len - left, copylen);
		left -= copylen;
	}
	packetdata[len - 1] = '\0';

	if (bpf_sendpacket((char *)eh, len + sizeof(*eh) + sizeof(*ech)) == -1)
		perror("bpf_sendpacket");

	bpf_close();
}

void
ethercons_sendcr(int argc, char *argv[])
{
	char *myargv[3];

	if (argc != 2)
		usage();

	myargv[0] = argv[0];
	myargv[1] = argv[1];
	myargv[2] = "\n";

	ethercons_send(3, myargv);
}

void
ethercons_tail(int argc, char *argv[])
{
	u_char address[ETHER_ADDR_LEN], *pktaddress;
	struct packet *pkt;
	int error, len;
	char *data;

	if (argc != 2)
		usage();

	error = parse_ethernet_addr(argv[1], address);
	if (error != 0) {
		fprintf(stderr, "ethercons_tail: %s\n", strerror(error));
		return;
	}

	if (bpf_open(0, argv[0]) == -1) {
		perror(argv[0]);
		return;
	}

	while ((pkt = bpf_nextpacket()) != NULL) {
		if (ethercons_from_packet(pkt, &pktaddress, &data, &len)
		    == -1) {
			bpf_freepacket(pkt);
			continue;
		}

		if (bcmp(address, pktaddress, ETHER_ADDR_LEN) == 0)
			printf("%s", data);
		bpf_freepacket(pkt);
	}
	perror(argv[0]);
	bpf_close();
}

void
ethercons_tailall(int argc, char *argv[])
{
	struct packet *pkt;
	char *data;
	int len;

	if (argc != 1)
		usage();

	if (bpf_open(0, argv[0]) == -1) {
		perror(argv[0]);
		return;
	}

	while ((pkt = bpf_nextpacket()) != NULL) {
		if (ethercons_from_packet(pkt, NULL, &data, &len) == -1) {
			bpf_freepacket(pkt);
			continue;
		}
		printf("%s", data);
		fflush(stdout);
		bpf_freepacket(pkt);
	}
	perror(argv[0]);
	bpf_close();
}

int
main(int argc, char *argv[])
{

	if (argc < 2)
		usage();

	if (strcmp(argv[1], "log") == 0)
		ethercons_log(argc - 2, argv + 2);
	else if (strcmp(argv[1], "send") == 0)
		ethercons_send(argc - 2, argv + 2);
	else if (strcmp(argv[1], "sendcr") == 0)
		ethercons_sendcr(argc - 2, argv + 2);
	else if (strcmp(argv[1], "tail") == 0)
		ethercons_tail(argc - 2, argv + 2);
	else if (strcmp(argv[1], "tailall") == 0)
		ethercons_tailall(argc - 2, argv + 2);
	else
		usage();

	exit(0);
}
