/*************************************************************************/
/*                  VChat interactive IP-level chat system               */
/*-----------------------------------------------------------------------*/
/*  (c) '93/'94 by Andreas S. Wetzel (mickey@deadline.bln.sub.org)       */
/*                 All rights reserverd.                                 */ 
/*-----------------------------------------------------------------------*/
/* See the file COPYRIGHT in the top level directory of VChat for        */
/* copyright notices and further disclaimers.                            */ 
/*************************************************************************/

/****** includes ******/

#include "../config.h"
#include "../global.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/fcntl.h>

#ifdef SunOS
	#include <varargs.h>
#endif

#include "proto.h"
#include "../version.h"

extern int errno;

/****** Code ******/

void init(int argc, char *argv[])
{
	/* Schoen schoen .... diese Routine soll als erstes mal */
	/* so einige Daten in Erfahrung bringen, als da waeren  */
	/* z.B. unser eigener Hostname und unsere IP-Adresse    */
	/* Weiterhin wird der Serversocket initialisiert und    */
	/* ein Buffer zur Verwaltung der Client daten angelegt. */

	extern char *prog_name;
	extern char our_host[];
	extern struct in_addr our_ipaddr;
	extern int sock;
	extern char *cl_buf;
	extern char *optarg;
	extern int optind;
	extern int opterr;
	extern int optopt;
	extern struct sockaddr_in sock_in;
	extern int logsw;
	extern char x_usr[];
	extern char x_host[];
	struct hostent *h_ent;
	struct servent *sv;
	u_int port = 0;
	int i, sock_in_len, opt;
#ifdef SLIP_FIX
	struct timeval to;
#endif
	x_usr[0] = '\0';
	x_host[0] = '\0';

	port = htons(SERVER_PORT);

	/* Erstmal sehen was wir denn schoenes auf der */
	/* Commandline haben.                          */

#ifndef VSERVER_INETD
	while((opt = getopt(argc, argv, ":h:lp:u:")) != -1)
#else
	while((opt = getopt(argc, argv, ":h:lu:")) != -1)
#endif
	{
		switch(opt)
		{
			case 'h':	if((h_ent = gethostbyname(optarg)) != NULL)
					{
						strcpy(x_host, optarg);
					}
					else
					{
						usage("%s: host unknown.", optarg);
						exit(1);
					}
					break;
			case 'l':	logsw = 0xff;
					break;
#ifndef VSERVER_INETD
			case 'p':	port = htons(atoi(optarg));
					break;
#endif
			case 'u':	strcpy(x_usr, optarg);
					break;
			case ':':	usage("Option -%c requires an argument.", optopt);
					exit(1);
					break;
			case '?':	usage("Unrecognized option: -%c", optopt);
					exit(1);
					break;
		}
	}

	if(logsw)
	{
		openlog(prog_name, LOG_NDELAY, LOG_LOCAL0);
	}

	if((char *)gethostname(our_host, 32) != NULL)
	{
		log(LOG_ERR,"%m");
		exit(1);
	}

	if((h_ent = gethostbyname(our_host)) != NULL)
	{
		bzero(&our_ipaddr, sizeof(our_ipaddr));
		bcopy((char *)h_ent->h_addr_list[0], (char *)&our_ipaddr, h_ent->h_length);
	}
	else
	{
		log(LOG_ERR, "%m");
		exit(1);
	}

	bzero((char *)&sock_in, sizeof(sock_in));

#ifndef VSERVER_INETD
	sock_in.sin_family = AF_INET;
	sock_in.sin_port = port;
	sock_in.sin_addr.s_addr = INADDR_ANY;

	if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		log(LOG_ERR, "%m");
		exit(1);
	}

#ifdef SLIP_FIX
	to.tv_sec = RCVTMOUT_SEC;
	to.tv_usec = RCVTMOUT_USEC;

	if(setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *) &to, sizeof(to)) == -1)
	{
		log(LOG_ERR, "%m");
		exit(1);
	}
#endif

	if(bind(sock, (struct sockaddr *)&sock_in, sizeof(sock_in)) != 0)
	{
		log(LOG_ERR, "%m");
		close(sock);		
		exit(1);
	}
#else
	sock_in_len = sizeof(sock_in);

	sock = 0;

	if(getpeername(sock, (struct sockaddr *)&sock_in, &sock_in_len) < 0)
	{
		log(LOG_ERR, "%m");
		exit(1);
	}
#endif
	if((cl_buf = calloc(MAXCLIENTS, sizeof(struct client_data))) == NULL)
	{
		log(LOG_ERR, "%m");
		close(sock);
		exit(1);
	}

#ifndef VSERVER_INETD
	if(listen(sock, 1))
	{
		log(LOG_ERR, "%m");
		close(sock);
		exit(1);
	}
#endif
	log(LOG_INFO, "VChat Internet Server -- %s",VERSION);
	log(LOG_INFO, "(c) '93 by mickey@deadline  (Andreas Wetzel)");
	log(LOG_INFO, "============================================");
}

void assign_client(void)
{
	/* Akzeptiert das Verbindungsgesuch eines neuen Client */
	/* Processes und empfaengt dessen Identifikationspaket */
	/* um es dann in die Liste der Client Process data     */
	/* Strukturen einzutragen.                             */

	int	tmp_fd;
	int	i;
	char	c, d;
	extern char *prog_name;
	extern int clients;
	extern int sock;
	struct	client_data *tmp_ptr = cl_addr(clients);

	if((tmp_fd = accept(sock, NULL, 0)) == -1)
	{
		log(LOG_ERR, "accept: %m");
	}
	else
	{
		i = 0;
	
		if(read(tmp_fd, tmp_ptr, sizeof(struct client_data)) == sizeof(struct client_data))
		{
			if(nick_ok(tmp_ptr, tmp_ptr->nick))
			{
				write(tmp_fd, tmp_ptr, sizeof(struct client_data));

				tmp_ptr->fd = tmp_fd;
				clients++;
				signon(tmp_ptr);	
			}
			else
			{
				strcpy(tmp_ptr->nick, "--invalid");
				write(tmp_fd, tmp_ptr, sizeof(struct client_data));
				log(LOG_INFO, "INFO: Signon from %s@%s [%s] refused due to invalid nickname.",
					tmp_ptr->user, tmp_ptr->host, inet_ntoa(tmp_ptr->ip_addr));
			}
		}
		else
		{
			bzero(tmp_ptr, sizeof(struct client_data));
			write(tmp_fd, tmp_ptr, sizeof(struct client_data));
			log(LOG_NOTICE, "WARNING: Vchat client process failed server initialization sequence.");
		}
	}
}

void delete_client(int cl_fd)
{
	/* Loescht eine client process data structure aus */
	/* der Liste der client processes und schliesst   */
	/* dessen socket descriptor.                      */

	int i;
	extern int clients;
	struct client_data *tmp_ptr;

	for(i = 0; i<clients; i++)
	{
		tmp_ptr = cl_addr(i);		

		if(tmp_ptr->fd == cl_fd)
		{
			if(clients > 1)
				bcopy(cl_addr(clients-1), tmp_ptr, sizeof(struct client_data));
			else
				bzero(tmp_ptr, sizeof(struct client_data));
			clients--;
			close(cl_fd);
		}
	}
}

struct client_data *cl_addr(int num)
{
	/* Berechnen der Adresse der client process data */
	/* structure.                                    */

	extern char *cl_buf;

	return (struct client_data *)((num * sizeof(struct client_data)) + cl_buf);
}

void log(int pri, char *fmt, ...)
{
	va_list ap;

	extern int logsw;

#ifdef SunOS
	va_start(ap);
#else
	va_start(ap, fmt);
#endif
	if(logsw)
	{
		vsyslog(pri, fmt, ap);
	}
	va_end(ap);
}

void snd_svmsg(int fd, char *fmt, ...)
{
	va_list xp;
	static char sndbuf[SVMSGBUF];
	struct serv_msg *svmsg = (struct serv_msg *)&sndbuf[0];
	char *text = (char *)&sndbuf[sizeof(struct serv_msg)];
	int c, i;

#ifdef SunOS
	va_start(xp);
#else
	va_start(xp, fmt);
#endif

	for(i=0; i<SVMSGBUF; i++)
		sndbuf[i] = '\0';

	vsprintf(text, fmt, xp);

	svmsg->cmd = SERV_MSG;
	svmsg->len = strlen(text);

	write(fd, &sndbuf[0],(sizeof(struct serv_msg) + svmsg->len));

	va_end(xp);
}

void flush_buf(int fd)
{
	int c;

	while(read(fd, &c, 1) == 1);
}

long chatters(long chan)
{
	struct client_data *cl;
	extern int clients;
	int i;
	unsigned long n = 0;
 
	for(i=0; i<clients; i++)
	{
		cl = cl_addr(i);

		if(cl->channel == chan)
			n++;
	}
	return(n);
}

int ucheck(char *user, struct in_addr *uip)
{
	struct client_data *cl;
	extern int clients;
	int i;
	int us = 0;

	for(i=0; i<clients; i++)
	{
		cl = cl_addr(i);

		if(strcmp(cl->user, user) == 0 && bcmp(&cl->ip_addr, uip, sizeof(struct in_addr)) == 0)
			us++;
	}
	return(us);
}

void usage(char *fmt, ...)
{
	va_list ap;
	extern char *prog_name;

#ifdef SunOS
	va_start(ap);
#else
	va_start(ap, fmt);
#endif

	fprintf(stderr, "\n");
	fprintf(stderr, "===================================================================\n");
	fprintf(stderr, "   Vchat interactive IP level chat system -- %s\n", VERSION);
	fprintf(stderr, "        (c) 1993/94 by mickey@deadline.uucp (Andreas S. Wetzel)\n");
	fprintf(stderr, "===================================================================\n\n");
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "\n\n");
	fprintf(stderr, "USAGE:     %s [-h <host>] [-l] [-p <port>] [-u <user>]\n\n", prog_name);
	fprintf(stderr, "OPTIONS:   -h <host>    Hostname of user with special privileges.\n");
	fprintf(stderr, "           -l           Enable logging via syslog.\n");
#ifndef VSERVER_INETD
	fprintf(stderr, "           -p <port>    Use TCP port <port>.\n");
#endif
	fprintf(stderr, "           -u <user>    User who gets special privileges.\n");
	fprintf(stderr, "\n");

	va_end(ap);
}

int privileged(struct client_data *ud)
{
	extern char x_usr[];
	extern char x_host[];
	int ret=0;

	if(strlen(x_host) == 0 && strlen(x_usr) == 0)	     /* Kein super-user. */
	{
		ret = 0;
	}
	else if(strlen(x_host) == 0 && strlen(x_usr) > 0)    /* Nur Username check */
	{
		ret = (strcmp(ud->user, x_usr) == 0) ? 0xff : 0;
	}
	else if(strlen(x_host) > 0 && strlen(x_usr) > 0)     /* Username und Host */
	{
		ret = (strcmp(ud->user, x_usr) == 0 && strcmp(ud->host, x_host) == 0) ? 0xff : 0;
	}
	else
	{
		ret = 0;
	}

	return(ret);
}

int nick_ok(struct client_data *cl, char *nick)
{
	int ret = 0xff;
	int i;
	struct client_data *tmp;
	extern int clients;

	for(i = 0; i<clients; i++)
	{
		tmp = cl_addr(i);

		if((strcmp(tmp->nick, nick) == 0)
		&& !(strcmp(tmp->user, cl->user) == 0 && strcmp(tmp->host, cl->host) == 0 && bcmp(&tmp->ip_addr, &cl->ip_addr, sizeof(tmp->ip_addr)) == 0))
		{
			ret = 0;
		}
	}

	return(ret);
}

char *pr_time(time_t tim, int len)
{
	static char prt[30];

	strncpy(prt, ctime((time_t *)&tim), len);

	return((char *)&prt);
}

