
/*
    IPX routing daemon

    Copyright (C) 1994, 1995  Ales Dryak <e-mail: A.Dryak@sh.cvut.cz>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
/* done: filter my broadcasts for proper aging */
/* done: age each iface separately */
/* done: DONTROUTE ioctl */
/* done: shutdown */
/* done: logovani ... */
/* done: full daemonize */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/ipx.h>
#include "ipxutil.h"
#include "ipxkern.h"
#include "ipxrip.h"

#define TIMER_RATE	(30)
#define EXPIRE_TIME	(180)
#define BROADCAST_TIME	(60)
#define MAX_IFACE	(32)

typedef unsigned char ifc_timer;

typedef struct tagRipTable
{
	IPXNet network;
	hop_t hops;
	tick_t ticks;
	IPXNet router_network;
	IPXNode router_node;
	ifc_timer timers[MAX_IFACE]; /* 0<timer<EXPIRE_TIME => net is reachable via iface */
				     /* timer>=EXPIPE_TIME => net is not r. */
	struct tagRipTable* next;
} RipTable;

typedef struct tagRipInterface
{
	IPXNode ifnode;
	RipOutput output;
} RipInterface;

#define ifnet output.dest_addr.sipx_network

int sock;
RipPacket* in_buf;
RipTable* rtable;
int n_of_ifaces;
RipInterface ifaces[MAX_IFACE];
int time_since_last_bcast=0;

volatile int dump_request=0;
volatile int timer_request=0;
volatile int terminate=0;

int new_log_entry=1;
int debug_option=0;
FILE* log_file=stderr;

#define LOG_ENTRY	{new_log_entry=1;}
#define LOG_START	{if (new_log_entry) {print_time(); new_log_entry=0;}
#define LOG_END		fflush(log_file);}
#define DL_ENTRY	{if (debug_option) LOG_ENTRY}
#define DL_START	{if (debug_option) LOG_START
#define DL_END		LOG_END }
#define DEFAULT_LOGNAME "/dev/null"

void print_time()
{
	time_t t;
	time(&t);
	fprintf(log_file,"\n%s",ctime(&t));
}

RipInterface* get_iface(IPXNet net)
{
  RipInterface* ifc;
  for(ifc=ifaces;ifc<ifaces+n_of_ifaces;ifc++)
  {
    if (ifc->ifnet==net) return ifc;
  }
  return NULL;
}

int ifc_get_index(RipInterface* ifc)
{
  if (ifc==NULL) return -1;
  return ifc-ifaces;
}

int is_expired(ifc_timer* tm)
{
	return *tm>=EXPIRE_TIME;
}

void setup_timers(RipTable* rt,RipInterface* ifc)
{
	ifc_timer* tm;
	
	for(tm=rt->timers;tm<rt->timers+MAX_IFACE;tm++) *tm=EXPIRE_TIME;
	rt->timers[ifc_get_index(ifc)]=0;
}

RipOutput* get_output(int index)
{
  return &(ifaces[index].output);
}

void output_flushall()
{
	int index;
	for(index=0;index<n_of_ifaces;index++)
	{
		ipx_rip_output_flush(get_output(index));
	}
}

void output_set_destination(IPXNode dest_node,IPXPort dest_port)
{
	int index;
	for(index=0;index<n_of_ifaces;index++)
	{
		ipx_rip_output_set_destination(get_output(index),dest_node,dest_port);
	}
}

void output_broadcast(RipTable* rt,int down_allow)
{
	RipInterface* ifc;
	ifc_timer* tm;
	for(ifc=ifaces,tm=rt->timers;ifc<ifaces+n_of_ifaces;ifc++,tm++)
	{
		if (is_expired(tm))
			ipx_rip_output_response(&(ifc->output),rt->network,
					rt->hops,rt->ticks,down_allow);
		
	}
}

static void output_sendto(void* buffer,int size,struct sockaddr_ipx* daddr)
{
	int res;
	
	DL_ENTRY
	DL_START
	fprintf(log_file,"Sending RIP to ");
	ipx_fprint_saddr(log_file,daddr);
	fprintf(log_file,"\n");
	ipx_rip_fdump(log_file,buffer,size);
	DL_END

	res=sendto(sock,(void*)buffer,size,0,
		(struct sockaddr*)daddr,sizeof(*daddr));
	if (res==-1)
	{
		LOG_START
		fprintf(log_file,"sendto: %s\n",strerror(errno));
		LOG_END
	}
}

void fprint_route(FILE* file,RipTable* rt)
{
	ifc_timer* tm;
	
	LOG_START
	fprintf(file,"dst:");
	ipx_fprint_network(file,rt->network);
	fprintf(file," h:%d t:%d rtr:",rt->hops,rt->ticks);
	ipx_fprint_network(file,rt->router_network);
	fprintf(file,":");
	ipx_fprint_node(file,rt->router_node);
	fprintf(file," ");
	for(tm=rt->timers;tm<rt->timers+n_of_ifaces;tm++)
	{
		fprintf(file,"%i",is_expired(tm)?0:1);
		DL_START
		fprintf(file,"(%i)",(int)*tm);
		DL_END
	}
	LOG_END
}

void fdump_routes(FILE* file)
{
	RipTable* rt;
	
	LOG_START
	fprintf(file,"IPX routing database:\n");
	for(rt=rtable;rt!=NULL;rt=rt->next)
	{
		fprint_route(file,rt);
		fprintf(file,"\n");
	}
	LOG_END
}


RipTable* add_route(IPXNet to,hop_t h,tick_t t,IPXNet rt_net,IPXNode rt_node)
{
	RipTable* rt;
	RipInterface* ifc;
	
	if ((ifc=get_iface(rt_net))==NULL) return NULL;
	if ((rt=(RipTable*)malloc(sizeof(RipTable)))==NULL)
	{
		LOG_START
		fprintf(log_file,"ipxripd: out of memory in add_route\n");
		LOG_END
		return NULL;
	}
	rt->network=to;
	rt->hops=h;
	rt->ticks=t;
	rt->router_network=rt_net;
	ipx_assign_node(rt->router_node,rt_node);
	setup_timers(rt,ifc);
	rt->next=rtable;
	rtable=rt;
	return rt;
}

void delete_route(RipTable* d)
{
	RipTable** r;
	for(r=&rtable;*r!=NULL;r=&((*r)->next))
	{
		if (*r==d)
		{
			*r=d->next;
			free(d);
			return;
		}
	}
}

void ipx_rip_rt_add(RipTable* rt)
{
	if(ipx_kern_route_add(sock,rt->network,rt->router_network,rt->router_node)<0)
	{
		LOG_START
		fprintf(log_file,"Cannot add route to network ");
		ipx_fprint_network(log_file,rt->network);
		fprintf(log_file,"\n%s\n",ipx_err_string);
		LOG_END
	}
}

void ipx_rip_rt_del(IPXNet net)
{
	if (ipx_kern_route_delete(sock,net)<0)
	{
		LOG_START
		fprintf(log_file,"Cannot delete route to network ");
		ipx_fprint_network(log_file,net);
		fprintf(log_file,"\n%s\n",ipx_err_string);
		LOG_END
	}
}

void handle_rip_request(RipEntry* re,RipInterface* src_ifc)
{
	RipTable* cur;
	if (re->network==IPX_RIP_GENERAL_RQ)
	{
		for(cur=rtable;cur!=NULL;cur=cur->next)
		{
			if (is_expired(cur->timers+ifc_get_index(src_ifc)))
				ipx_rip_output_response(&(src_ifc->output),cur->network,
					cur->hops,cur->ticks,0);
		}
	}
	else
	{
		for(cur=rtable;cur!=NULL;cur=cur->next)
		{
			if (re->network==cur->network)
			if (is_expired(cur->timers+ifc_get_index(src_ifc)) || 
				(src_ifc->ifnet==re->network && 
				get_iface(re->network)!=NULL))
				ipx_rip_output_response(&(src_ifc->output),cur->network,
					cur->hops,cur->ticks,0);
		}
	}
}

void handle_rip_response(RipEntry* re,RipInterface* src_ifc,IPXNode src_node)
{
	RipTable* cur;
	re->hops=ntohs(re->hops)+1;
	re->ticks=ntohs(re->ticks)+1;
	for(cur=rtable;cur!=NULL;cur=cur->next)
	{
		if (cur->network==re->network)
		{
			/* don't change routes to ifaces */
			if (cur->network==cur->router_network) return;
			if (re->hops<=IPX_RIP_NET_DOWN)
			{
				if (re->ticks<cur->ticks || 
					(re->ticks==cur->ticks && re->hops<=cur->hops)) 
				/* route is at least good as existing one */
				{
					if (re->ticks==cur->ticks && re->hops==cur->hops) 
					/* new route has the same cost - reset timer*/
					{
						if (!(src_ifc->ifnet==cur->router_network &&
						    !ipx_node_equal(cur->router_node,src_node)))
							cur->timers[ifc_get_index(src_ifc)]=0;
					}
					else
					/* new route is better */
					{
						LOG_START
						fprintf(log_file,"CHANGE ");
						fprint_route(log_file,cur);
						LOG_END
						
						/* update table */
						cur->ticks=re->ticks;
						cur->hops=re->hops;
						cur->router_network=src_ifc->ifnet;
						ipx_assign_node(cur->router_node,
								src_node);
						setup_timers(cur,src_ifc);
												
						LOG_START
						fprintf(log_file,"\nto     ");
						fprint_route(log_file,cur);
						fprintf(log_file,"\n");
						LOG_END
	
						/* update kernel table */
						ipx_rip_rt_add(cur);
						/* send info bcast */
						output_broadcast(cur,0);
					}	
				}	
				else
				{
					/* route is worse */
				}
			}
			else
			{
				/* network down response */
				if (src_ifc->ifnet==cur->router_network)
				{
					if (ipx_node_equal(cur->router_node,
							src_node))
					{
						LOG_START
						fprintf(log_file,"DELETE ");
						fprint_route(log_file,cur);
						fprintf(log_file," (net down)\n");
						LOG_END;
						/* update kernel table */
						ipx_rip_rt_del(cur->network);
						/* send info bcast */
						cur->hops=IPX_RIP_NET_DOWN;
						output_broadcast(cur,1);
						/* update table */
						delete_route(cur);
					}
					else
					{ /* my router alive */
					}
				}
				else
				{
					cur->timers[ifc_get_index(src_ifc)]=EXPIRE_TIME;
				}
			}
			return;
		}
	}
	if (re->hops<=IPX_RIP_NET_DOWN)
	{
		RipTable* rt=add_route(re->network,re->hops,re->ticks,
			src_ifc->ifnet,src_node);
		if (rt!=NULL)
		{
			/* update kernel table */
			ipx_rip_rt_add(rt);
			/* send info bcast */
			output_broadcast(rt,0);
			LOG_START
			fprintf(log_file,"ADD ");	
			fprint_route(log_file,rt);
			fprintf(log_file,"\n");
			LOG_END
		}
	}
}

void handle_rip(RipPacket* pkt,int len,struct sockaddr_ipx* sipx)
{
	RipEntry* re=pkt->rip_entries;
	int nent=(len-2)/8;
	RipInterface* src_ifc;

	if ((src_ifc=get_iface(sipx->sipx_network))==NULL)
	{
		LOG_START
		fprintf(log_file,"RIP from non-local net \n");
                ipx_fprint_network(log_file,sipx->sipx_network);
		fprintf(log_file," (ignored)\n");
		LOG_END
		return;
	}
	if (len<ipx_rip_size(1))
	{
		LOG_START
		fprintf(log_file,"RIP packet too small len=%i (ignored)\n",len);
		LOG_END
		return;
	}
	if (ipx_rip_size(nent)!=len)
	{
		LOG_START
		fprintf(log_file,"RIP packet bad size (ignored)\n");
		LOG_END
		return;
	}
	if (ipx_node_equal(src_ifc->ifnode,sipx->sipx_node) && 
		(unsigned short)sipx->sipx_port==htons(IPX_RIP_PORT))
	{
		DL_START
		fprintf(log_file,"My packet (ignored)\n");
		DL_END
		return;
	}
	switch (ntohs(pkt->operation))
	{
	case IPX_RIP_OP_REQUEST:
		output_set_destination(sipx->sipx_node,ntohs(sipx->sipx_port));
		for(;nent--;re++) handle_rip_request(re,src_ifc);
		output_flushall();
		break;
	case IPX_RIP_OP_RESPONSE:
	/* option: ignore responses from non RIP ports
		if (sipx->sipx_port!=htons(IPX_RIP_PORT)) return;
	*/
		output_set_destination(IPX_BROADCAST,IPX_RIP_PORT);
		for(;nent--;re++) handle_rip_response(re,src_ifc,sipx->sipx_node);
		output_flushall();
		break;
	default:
		LOG_START
		fprintf(log_file,"Unknown RIP operation\n");
		LOG_END
		break;
	} 
}

void do_aging()
{
	RipTable* cur;
	int do_broadcast=0;
	
	DL_START
	fprintf(log_file,"DO AGING\n");
	DL_END
	time_since_last_bcast+=TIMER_RATE;
	if (time_since_last_bcast>=BROADCAST_TIME)
	{
		do_broadcast=1;
		time_since_last_bcast-=BROADCAST_TIME;
	}
	output_set_destination(IPX_BROADCAST,IPX_RIP_PORT);
	for(cur=rtable;cur!=NULL;cur=cur->next)
	{
		ifc_timer* tm;
		
		for(tm=cur->timers;tm<cur->timers+n_of_ifaces;tm++)
		{
			if (!is_expired(tm)) (*tm)+=TIMER_RATE;
		}
		tm=cur->timers+ifc_get_index(get_iface(cur->router_network));
		/* cannot timeout iface !*/
		if (cur->network==cur->router_network) *tm=0;
		if (is_expired(tm))
		{
			/* net is down */
			LOG_START
			fprintf(log_file,"DELETE ");
			fprint_route(log_file,cur);
			fprintf(log_file," (timed out)\n");
			LOG_END
			/* update kernel table */
			ipx_rip_rt_del(cur->network);
			/* send info bcast */
			cur->hops=IPX_RIP_NET_DOWN;
			output_broadcast(cur,1);
			/* update table */
			delete_route(cur);
		}
		else
		{
			if (do_broadcast) output_broadcast(cur,0);
		}
	}
	output_flushall();
}

struct ipx_rip_scan_info
{
	RipInterface* ifc;
	int old_routes;
};

static int ipx_rip_scan_rt(IPXNet net,IPXNet rt_net,IPXNode node,int type,void* data)
{
	RipTable* rt;
	struct ipx_rip_scan_info* si=(struct ipx_rip_scan_info*)data;
	
	if ((type & IPX_KRT_ROUTE)==0)
	{
		if (!(si->ifc<ifaces+MAX_IFACE))
		{
			LOG_START
			fprintf(log_file,"too many interfaces (max. %i)\n",(int)MAX_IFACE);
			LOG_END
			exit(1);
		}
		ipx_assign_node(si->ifc->ifnode,node);
		if (!ipx_rip_output_rip_output(&(si->ifc->output),net))
		{
			LOG_START
			fprintf(log_file,"out of memory allocating output buffer\n");
			LOG_END
			exit(1);
		}
		if (net==0)
		{
			LOG_START
			fprintf(log_file,"interface bound to net 0\n");
			LOG_END
			exit(1);
		}
		n_of_ifaces++;
		if ((rt=add_route(net,1,2,net,node))==NULL)
		{
			LOG_START
			fprintf(log_file,"cannot add route to iface (out of memory)\n");
			LOG_END
			exit(1);
		}
		LOG_START
		ipx_fprint_network(log_file,net);
		fprintf(log_file,":");
		ipx_fprint_node(log_file,node);
		fprintf(log_file,"\n");
		LOG_END
		si->ifc++;
	}
	else
	{
		/* remove old routes */
		ipx_rip_rt_del(net);
		si->old_routes++;
	}
	return 1;	
}

void ipx_rip_get_ifaces()
{
	struct ipx_rip_scan_info si;

	si.ifc=ifaces;
	si.old_routes=0;
	LOG_START
	fprintf(log_file,"Interface list\n");
	LOG_END
	if (ipx_kern_scan_rtable(ipx_rip_scan_rt,(void*)&si)<0)
	{
		LOG_START
		fprintf(log_file,"Cannot read interface information from kernel\n");
		fprintf(log_file,"%s\n",ipx_err_string);
		LOG_END
		exit(1);
	}
  	LOG_START
	fprintf(log_file,"total %d interfaces\n",n_of_ifaces);
	fprintf(log_file,"total %d old routes deleted\n",si.old_routes);
	LOG_END
}

void int_handler()
{
	signal(SIGINT,int_handler);
	dump_request=1;
}

void timer_handler()
{
	signal(SIGALRM,timer_handler);
	timer_request=1;
}

void terminate_handler()
{
	signal(SIGTERM,terminate_handler);
	terminate=1;
}

void hup_handler()
{
	signal(SIGHUP,hup_handler);
	terminate=2;
}

void init()
{
	struct itimerval itval;
	struct sockaddr_ipx my_addr;
	
	LOG_ENTRY
	LOG_START
	fprintf(log_file,"Init start\n");
	LOG_END
	
	ipx_rip_output_func=output_sendto;
	rtable=NULL;
	in_buf=ipx_rip_alloc(IPX_RIP_MAX_ENTRIES);
	if (in_buf==NULL) 
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: out of memory\n");
		LOG_END
		exit(1);
	}

	sock=socket(AF_IPX,SOCK_DGRAM,PF_IPX);
	if(sock==-1)
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: can't create socket: %s\n",strerror(errno));
		LOG_END
		exit(1);
	}


	if (ipx_kern_get_internet_addr(&my_addr)>=0)
	{
		LOG_START
		fprintf(log_file,"My address: ");
		ipx_fprint_saddr(log_file,&my_addr);
		fprintf(log_file,"\n");
		LOG_END
	}
	my_addr.sipx_family=AF_IPX;
	my_addr.sipx_network=IPX_THIS_NET;
	ipx_assign_node(my_addr.sipx_node,IPX_THIS_NODE);
	my_addr.sipx_port=htons(IPX_RIP_PORT);
	my_addr.sipx_type=IPX_RIP_PTYPE;

	/* Permit broadcast output */
	if(ipx_kern_enable_broadcast(sock)!=0)
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: can't enable broadcasting: %s\n",ipx_err_string);
		LOG_END
		exit(1);
	}

	/* Disable routing and set src address generation by iface */
	if(ipx_kern_dont_route(sock)!=0)
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: can't disable routing: %s\n",ipx_err_string);
		LOG_END
		exit(1);
	}

	if(bind(sock,(struct sockaddr*)&my_addr,sizeof(my_addr))==-1)
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: can't bind socket: %s\n",strerror(errno));
		LOG_END
		exit(1);
	}
	
	ipx_rip_get_ifaces();

	itval.it_interval.tv_sec = TIMER_RATE;
	itval.it_value.tv_sec = TIMER_RATE;
	itval.it_interval.tv_usec = 0;
	itval.it_value.tv_usec = 0;
	if (setitimer(ITIMER_REAL, &itval, (struct itimerval *)NULL) < 0)
	{
		LOG_START
		fprintf(log_file,"FATAL ERROR: can't set itimer: %s\n",strerror(errno));
		LOG_END
		exit(1);
	}
	
	signal(SIGINT,int_handler);
	signal(SIGALRM,timer_handler);
	signal(SIGTERM,terminate_handler);
	signal(SIGHUP,hup_handler);
	
	{
		RipTable* rt;
		output_set_destination(IPX_BROADCAST,IPX_RIP_PORT);
		for(rt=rtable;rt!=NULL;rt=rt->next)
		{
			output_broadcast(rt,0);
		}
		output_flushall();
	}
	{
		RipInterface* ifc;
		for(ifc=ifaces;ifc<ifaces+n_of_ifaces;ifc++)
		{
			ipx_rip_output_request(&(ifc->output),IPX_RIP_GENERAL_RQ);
		}
		output_flushall();
	}
	LOG_ENTRY
	LOG_START
	fprintf(log_file,"Init end\n");
	LOG_END
}

void run()
{
	struct sockaddr_ipx sipx;
	while(1)
	{
		int addr_len=sizeof(sipx);
		int size;

		LOG_ENTRY
		size=recvfrom(sock,(void*)in_buf,ipx_rip_size(IPX_RIP_MAX_ENTRIES),
				0,(struct sockaddr*)&sipx,&addr_len);
		if(size==-1 && errno!=EINTR)
		{
			LOG_START
			fprintf(log_file,"recvfrom error: %s\n",strerror(errno));
			LOG_END
			continue;
		}
		if (dump_request)
		{
			fdump_routes(log_file);
			dump_request=0;
		}
		if (timer_request)
		{
			do_aging();
			timer_request=0;
		}
		if (terminate)
		{
			LOG_START
			if (terminate==1)
				fprintf(log_file,"exiting on signal 15 (SIGTERM)\n");
			else
				fprintf(log_file,"exiting on signal 1 (SIGHUP)\n");
			LOG_END
			return;
		}
		if (size==-1) continue;
		DL_START
		fprintf(log_file,"RIP from: ");
		ipx_fprint_saddr(log_file,&sipx);
		fprintf(log_file,"\n");
		ipx_rip_fdump(log_file,in_buf,size);
		DL_END
		handle_rip(in_buf,size,&sipx);
	}
}

void done()
{
	RipTable* cur;
	
	LOG_ENTRY
	LOG_START
	fprintf(log_file,"Shutdown start\n");
	LOG_END
	
	output_set_destination(IPX_BROADCAST,IPX_RIP_PORT);
	for(cur=rtable;cur!=NULL;cur=cur->next)
	{
		/* update kernel table (don't delete ifaces)*/
		if (cur->network!=cur->router_network) 
		{
			ipx_rip_rt_del(cur->network);
			LOG_START
			fprintf(log_file,"DELETE ");
			fprint_route(log_file,cur);
			fprintf(log_file," (shutdown)\n");
			LOG_END
		}
		/* send info bcast */
		cur->hops=IPX_RIP_NET_DOWN;
		output_broadcast(cur,1);
		/* update table */
		delete_route(cur);
	}
	output_flushall();
	if (close(sock)!=0)
	{
		LOG_START
		fprintf(log_file,"close error: %s\n",strerror(errno));
		LOG_END
	}
	
	LOG_ENTRY
	LOG_START
	fprintf(log_file,"Shutdown end\n");
	LOG_END
}

void help()
{
	fprintf(stderr,
		"IPX routing daemon v0.9, (c) Ales Dryak, 1995\n"
		"Usage: ipxripd [-d] [<path to log file>]\n");
}

int parse_cmdline(int argc,char** argv)
{
	char* log_name;
	argc--,argv++;
	for(;**argv=='-';argc--,argv++)
	{
		if (*(*argv+1)=='d')
			debug_option=1;
		else
		{
			fprintf(stderr,"Unknown command line option %s\n",*argv);
			help();
			exit(1);
		}
	}
	if (argc>1)
	{
		fprintf(stderr,"Too many parameters");
		help();
		exit(1);
	}
	if (argc==0 && debug_option)
	{
		log_file=stdout;
		return 0;
	}
	log_name=DEFAULT_LOGNAME;
	if (argc==1) log_name=*argv;
	if ((log_file=fopen(log_name,"a"))==NULL)
	{
		perror(log_name);
		exit(1);
	}
	return 1;
}

static void daemonize()
{
	int fd,c;
	
	if ((c = fork()) > 0) exit(0);
  	if (c < 0)
  	{
		fprintf(stderr, "ipxripd: can't fork: %s\n",strerror(errno));
		exit(1);
	}

	close(0);
	close(1);
	close(2);
	if ((fd = open("/dev/tty", O_RDWR)) >= 0) 
	{
		ioctl(fd, TIOCNOTTY, NULL);
		close(fd);
	}
}

int main(int argc,char** argv)
{
	if (parse_cmdline(argc,argv)) daemonize();
	init();
	run();
	done();
	return 0;
}
