/*
 * brocas.c - IP Messenger 1.20 protocol
 * Copyright (C) 1996 by candy
 */
char rcsid_brocas[] = "$Id: brocas.c,v 3.1 1996/11/21 13:28:23 candy Exp candy $";
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <unistd.h>
#include <sys/time.h> /* setitimer() */

/* includes below are order dependent */
#include <sys/param.h> /* htons() */
#include <sys/types.h> /* socket() */
#include <sys/socket.h> /* socket() */
#include <netinet/in.h> /* inet_addr() INADDR_ANY */
#include <arpa/inet.h> /* inet_addr() */
#include <netdb.h> /* gethostbyname() */

#ifdef SOCKS
#include <socks.h>
#endif

#include "xipmsg.h"
#include "kanji.h"
#include "brocas.h"
#include "dyna.h"

#if defined BSD4_4
#define SENDTO(s,msg,len,flags,to,tolen) sendto(s,msg,len,flags,to,tolen)
#define SETSOCKOPT(s,level,optname,optval,optlen) setsockopt(s,level,optname,optval,optlen)
#else
#define SENDTO(s,msg,len,flags,to,tolen) sendto(s,msg,len,flags,(struct sockaddr *)to,tolen)
#define SETSOCKOPT(s,level,optname,optval,optlen) setsockopt(s,level,optname,(char *)optval,optlen)
#endif

static int debug_flag;

static int bro_socket;
static int bro_port;
static char *bro_user;
static const char *bro_host;
static int verbose;

static char *myname;

#define lan_width 32
#define lan_height 32
static unsigned char lan_bits[] = {
 0xe0,0x00,0x00,0x1f,0x50,0x07,0xc0,0x35,0xb0,0xfa,0xbf,0x2a,0x58,0x55,0x55,
 0x35,0xa8,0xaa,0xaa,0x2a,0x58,0x55,0x55,0x55,0xa8,0xaa,0xaa,0x6a,0x58,0x55,
 0x55,0x55,0xa8,0xaa,0xaa,0x6a,0x58,0x01,0x55,0x50,0xa8,0x00,0x08,0x60,0x58,
 0x18,0x00,0x46,0x28,0x18,0x00,0x46,0x50,0x00,0x00,0x40,0x30,0x30,0x00,0x46,
 0x30,0x78,0x00,0x4f,0x30,0x78,0x00,0x4f,0x20,0x30,0x00,0x46,0x20,0x00,0x00,
 0x40,0x58,0x00,0x00,0x40,0x6c,0x00,0x00,0x40,0xd6,0x00,0x60,0x40,0xab,0x00,
 0xf0,0x40,0x55,0x01,0x60,0x20,0xaa,0x02,0x00,0x20,0x55,0x05,0x00,0x30,0xaa,
 0x1a,0x00,0x38,0x55,0x61,0xf8,0x6f,0xaa,0x80,0x27,0x55,0x55,0x00,0x04,0x6b,
 0xaa,0x00,0x88,0xd0,0x55,0x00,0x70,0xa8
};

static unsigned char *icon = lan_bits;

static unsigned long
atoaddr(const char *host)
{
	unsigned long ret = INADDR_NONE;
	struct hostent *he = gethostbyname(host);
	if (he == NULL) {
		fprintf(stderr, "%s: %s: unknown host.\n", myname, host);
	}
	if (he != NULL) {
		if (he->h_addrtype == AF_INET && he->h_length <= sizeof(ret)) {
			memcpy(&ret, he->h_addr_list[0], he->h_length);
		}
	}
	return ret;
}/* atoaddr */

static size_t
iov_total(const struct iov_t *v)
{
	size_t total = 0;
	while (v != NULL) {
		total += v->iov_len;
		v = v->iov_next;
	}/* while */
	return total;
}/* iov_total */

static void *
iov_gather(void *buf_, const struct iov_t *v)
{
	char *buf = buf_;
	while (v != NULL) {
		memcpy(buf, v->iov_base, v->iov_len);
		buf += v->iov_len;
		v = v->iov_next;
	}/* while */
	return buf_;
}/* iov_gather */

static ssize_t
sendtov(int so, int flags, const struct sockaddr *to, int tolen, const struct iov_t *iov)
{
	ssize_t err = -1;
	size_t total = iov_total(iov);
	char *buf = malloc(total);
	if (buf != NULL) {
		iov_gather(buf, iov);
		err = SENDTO(so, buf, total, flags, to, tolen);
		if (err < 0)
			perror("sendto");
		free(buf);
	}
	return err;
}/* sendtov */



static unsigned long msg_number;

static int
send_msgv(int so, const union saddr *to, unsigned long command, int retryflag, const struct iov_t *iov)
{
	char lbuf[2 + 12 + USERNAME_MAX + 1 + HOSTNAME_MAX + 1 + 12];
	int err = -1, flags = 0;
	struct iov_t v;
	sprintf(lbuf, "1:%ld:%.*s:%.*s:%lu:", msg_number++, USERNAME_MAX, bro_user, HOSTNAME_MAX, bro_host, command);
	SET_IOV(&v, (void *)iov, lbuf, strlen(lbuf));
	if (retryflag) {
		err = sendtov(so, flags, &to->sa, sizeof(to->sin), &v);
	}
	return err;
}/* send_msgv */

/*
 * ネットワークの登録
 */

struct dynet_t STRUCT_DYNA(union saddr);
struct dynet_t dynet;

static int
network_add(const char *broad)
{
	unsigned long addr = atoaddr(broad);
	if (addr != INADDR_NONE) {
		union saddr *net;
		net = DYNA_NEXT(&dynet);
		if (net != NULL) {
			memset(net, '\0', sizeof(*net));
			net->sin.sin_family = AF_INET;
			net->sin.sin_addr.s_addr = atoaddr(broad);
			net->sin.sin_port = htons(bro_port);
		}
	}
	return 0;
}/* network_add */

static int
network_bro_send(int so, const char *msg)
{
	union saddr *sin = DYNA_BUF(&dynet);
	if (sin != NULL) {
		char *sjis = malloc(strlen(msg) + 1);
		if (sjis != NULL) {
			unsigned long cmd = IPMSG_SENDMSG | IPMSG_SENDCHECKOPT;
			int retry = 1;
			int i, err;
			struct iov_t v[2];
			stretos(sjis, msg);
			SET_IOV(&v[0], &v[1], sjis, strlen(sjis) + 1);
			SET_IOV(&v[1], NULL, (void *)icon, 128);
			for (i = 0; i < DYNA_USED(&dynet); i++) {
				err = send_msgv(so, sin, cmd, retry, v);
				sin++;
			}/* for */
			free(sjis);
		}
	}
	return 0;
}/* network_bro_send */

/*
 *
 */
int
bro_init(int port, const char *user, const char *host, const char * const *bros)
{
	int so = socket(PF_INET, SOCK_DGRAM, 0);
	DYNA_IZ(&dynet, 32);
	bro_port = port;
	bro_user = malloc(strlen(user) * 2 + 1);
	if (bro_user != NULL)
		stretos(bro_user, user);
	else
		bro_user = (char *)user;
	bro_host = host;
	msg_number = time(NULL);
	while (*bros != NULL)
		network_add(*bros++);
	if (so >= 0) {
		int optval = 1;
		int err = SETSOCKOPT(so, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
		if (err < 0) {
			close(so);
			so = -1;
		}
	}
	bro_socket = so;
	return so;
}/* bro_init */

static int
fnain(int so, FILE *fp)
{
	char buf[5120];
	size_t size = fread(buf, 1, sizeof(buf) - 1, fp);
	buf[size] = '\0';
	return network_bro_send(so, buf);
}/* fnain */

static int
nain(int so, const char *name)
{
	FILE *fp = fopen(name, "r");
	int err = -1;
	if (fp != NULL) {
		err = fnain(so, fp);
		fclose(fp);
	}
	return err;
}/* nain */

static char *
get_env(const char *e)
{
	char *ret = NULL, *p = getenv(e);
	if (p != NULL) {
		ret = malloc(strlen(p) + 1);
		if (ret != NULL)
			strcpy(ret, p);
	}
	return p;
}/* get_env */


static char usage_msg[] =
	"usage: %s [-v][-d n] {[-f file] | [-m message]} [-p port] [-u user] address address...\n"
	;

int
main(int argc, char *argv[])
{
	int ex, show_usage = 0, ch;
	int port = 2425;
	char *user = get_env("USER"), *filename = NULL, *message = NULL;
	myname = argv[0];
	while ((ch = getopt(argc, argv, "d:f:m:p:u:vV")) != EOF) {
		switch (ch) {
		default:
		case 'V':
			show_usage++;
			break;
		case 'd':
			debug_flag = strtol(optarg, NULL, 0);
			break;
		case 'f':
			filename = optarg;
			break;
		case 'm':
			message = optarg;
			break;
		case 'p':
			port = strtol(optarg, NULL, 0);
			break;
		case 'u':
			user = optarg;
			break;
		case 'v':
			verbose++;
			break;
		}/* switch */
	}/* while */
	if (argc - optind < 1)
		show_usage++;
	ex = 1;
	if (show_usage) {
		fprintf(stderr, usage_msg, myname);
	}
	else {
		const char * const *v = (const char * const *)&argv[optind];
		char hostname[64];
		int so;
		if (user == NULL)
			user = "anonymous";
		gethostname(hostname, sizeof(hostname));
		if (strchr(hostname, '.') != NULL)
			*strchr(hostname, '.') = '\0';
		so = bro_init(port, user, hostname, v);
		if (so >= 0) {
			int err = 0;
			if (message != NULL)
				err = network_bro_send(so, message);
			else if (filename != NULL)
				err = nain(so, filename);
			else
				err = fnain(so, stdin);
			ex = err != 0;
		}
	}
	return ex;
}/* main */
