/*	$OpenBSD: common.c,v 1.11 2003/06/28 20:37:29 deraadt Exp $	*/

/*
 * Copyright (c) 2000 Network Security Technologies, Inc. http://www.netsec.net
 *
 * 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 ``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 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.
 */

#include <stdio.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/ppp_defs.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <net/bpf.h>
#include <errno.h>
#include <string.h>
#include <err.h>
#include <fcntl.h>
#include <unistd.h>
#include <sysexits.h>
#include <stdlib.h>
#include <md5.h>

#include "pppoe.h"

#define PPP_PROG	"/usr/sbin/ppp"

void debugv(char *, struct iovec *, int);

int
runppp(bpffd, sysname)
	int bpffd;
	u_int8_t *sysname;
{
	int socks[2], fdm, fds, closeit;
	pid_t pid;

	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, socks) < 0)
		return (-1);

	fdm = socks[0];
	fds = socks[1];

	pid = fork();
	if (pid < 0) {
		close(fds);
		close(fdm);
		return (-1);
	}

	if (pid != 0) {
		/* Parent */
		close(fds);
		return (fdm);
	}

	if (setsid() < 0)
		_exit(99);

	/* Child */
	close(bpffd);
	close(fdm);
	closeit = 1;

	if (fds == STDIN_FILENO)
		closeit = 0;
	else
		dup2(fds, STDIN_FILENO);

	if (fds == STDOUT_FILENO)
		closeit = 0;
	else
		dup2(fds, STDOUT_FILENO);

	if (fds == STDERR_FILENO)
		closeit = 0;
	else
		dup2(fds, STDERR_FILENO);

	if (closeit)
		close(fds);

	execlp(PPP_PROG, "ppp", "-direct", sysname, (char *)NULL);
	perror("execlp");
	_exit(1);
	/*NOTREACHED*/
	return (-1);
}

int
bpf_to_ppp(pppfd, len, pkt)
	int pppfd;
	u_long len;
	u_int8_t *pkt;
{
	int r;
	u_int8_t hdr[2] = { PPP_ALLSTATIONS, PPP_UI };
	struct iovec iov[2];

	iov[0].iov_base = hdr;
	iov[0].iov_len = sizeof(hdr);
	iov[1].iov_base = pkt;
	iov[1].iov_len = len;

	r = writev(pppfd, iov, 2);
	if (r < 0) {
		if (errno == EINTR || errno == EPIPE || errno == ENOBUFS)
			return (0);
		return (-1);
	}
	return (1);
}

int
ppp_to_bpf(int bfd, int pppfd, struct ether_addr *myea,
    struct ether_addr *rmea, u_int16_t id)
{
	static u_int8_t *pktbuf = NULL;
	struct pppoe_header ph;
	struct iovec iov[5];
	u_int16_t etype;
	u_int8_t trash[2];
	int r;

	if (pktbuf == NULL) {
		pktbuf = (u_int8_t *)malloc(PPPOE_MTU);
		if (pktbuf == NULL)
			return (-1);
	}

	iov[0].iov_base = trash;
	iov[0].iov_len = 2;
	iov[1].iov_base = pktbuf;
	iov[1].iov_len = PPPOE_MTU;
	r = readv(pppfd, iov, 2);
	if (r <= 0)
		return (-1);
	r -= 2;

	ph.vertype = PPPOE_VERTYPE(1, 1);
	ph.code = PPPOE_CODE_SESSION;
	ph.len = htons(r);
	ph.sessionid = htons(id);
	etype = htons(ETHERTYPE_PPPOE);

	iov[0].iov_base = rmea;		iov[0].iov_len = ETHER_ADDR_LEN;
	iov[1].iov_base = myea;		iov[1].iov_len = ETHER_ADDR_LEN;
	iov[2].iov_base = &etype;	iov[2].iov_len = sizeof(etype);
	iov[3].iov_base = &ph;		iov[3].iov_len = sizeof(ph);
	iov[4].iov_base = pktbuf;	iov[4].iov_len = r;

	r = writev(bfd, iov, 5);

	return (r == -1 && errno == ENOBUFS ? 0 : r);
}

void
debugv(s, iov, cnt)
	char *s;
	struct iovec *iov;
	int cnt;
{
	int i, j;
	u_int8_t *p;

	printf("%s", s);
	for (i = 0; i < cnt; i++)
		for (j = 0; j < iov[i].iov_len; j++) {
			p = (u_int8_t *)iov[i].iov_base;
			printf("%02x:", p[j]);
		}
	printf("\n\n");
}

void
recv_debug(bpffd, ea, eh, ph, pktlen, pktbuf)
	int bpffd;
	struct ether_addr *ea;
	struct ether_header *eh;
	struct pppoe_header *ph;
	u_long pktlen;
	u_int8_t *pktbuf;
{
	struct tag_list tl;

	printf("dst %02x:%02x:%02x:%02x:%02x:%02x, "
	    "src %02x:%02x:%02x:%02x:%02x:%02x, type %04x\n",
	    eh->ether_dhost[0], eh->ether_dhost[1], eh->ether_dhost[2],
	    eh->ether_dhost[3], eh->ether_dhost[4], eh->ether_dhost[5],
	    eh->ether_shost[0], eh->ether_shost[1], eh->ether_shost[2],
	    eh->ether_shost[3], eh->ether_shost[4], eh->ether_shost[5],
	    eh->ether_type);
	printf("\tver %d, type %d, code %02x, id %04x, len %d\n",
	    PPPOE_VER(ph->vertype), PPPOE_TYPE(ph->vertype),
	    ph->code, ph->sessionid, ph->len);

	tag_init(&tl);
	if (tag_pkt(&tl, pktlen, pktbuf) < 0) {
		printf("bad tag pkt\n");
		goto out;
	}

	tag_show(&tl);
out:
	tag_destroy(&tl);
}

int
send_padt(int bpffd, struct ether_addr *src_ea,
    struct ether_addr *dst_ea, u_int16_t id)
{
	struct iovec iov[4];
	struct pppoe_header ph;
	u_int16_t etype = htons(ETHERTYPE_PPPOEDISC);

	iov[0].iov_base = dst_ea;
	iov[0].iov_len = ETHER_ADDR_LEN;
	iov[1].iov_base = src_ea;
	iov[1].iov_len = ETHER_ADDR_LEN;
	iov[2].iov_base = &etype;
	iov[2].iov_len = sizeof(etype);
	iov[3].iov_base = &ph;
	iov[3].iov_len = sizeof(ph);

	ph.vertype = PPPOE_VERTYPE(1, 1);
	ph.code = PPPOE_CODE_PADT;
	ph.len = 0;
	ph.sessionid = htons(id);

	return (writev(bpffd, iov, 4));
}

u_int32_t
cookie_bake(void)
{
	MD5_CTX ctx;
	unsigned char buf[40];
	u_int32_t x, y;

	x = arc4random();
	MD5Init(&ctx);
	MD5Update(&ctx, (unsigned char *)&x, sizeof(x));
	MD5Final((unsigned char *)buf, &ctx);
	bcopy(buf, &y, sizeof(y));
	x = x ^ y;
	bcopy(buf + 4, &y, sizeof(y));
	x = x ^ y;
	bcopy(buf + 8, &y, sizeof(y));
	x = x ^ y;
	bcopy(buf + 12, &y, sizeof(y));
	x = x ^ y;
	return (x);
}