/*
 * Copyright (c) 1998-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * lp-gw.c, a line printer protocol gateway for TIS fwtk with lprNG support
 *          (strict RFC 1179 conformance)
 *
 * (C) Copyright 1998-2001 by ArkanoiD <ark@eltex.ru>
 *
 */

#include <stdio.h>
#include <syslog.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/signal.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

#include "firewall.h"
#include "firewall2.h"
#include "fwfunc.h"

static	char* moduleId ATTR_UNUSED = "$Id: lp-gw.c,v 1.13 2007/09/10 02:49:13 arkenoi Exp $";

typedef	struct {
	char	*name;
	void	(*handler)(char,char*,char*);
	int	flg;
} lp_action;

#define	LP_PORT	515

static  char    server_queue[512] = "none"; /* name of the server queue */

static  time_t  offtime;                       /* time when proxy is off */
static	int   	lprng_client = 0;
static	int   	anyport = 0;
static	int   	transparent = 0;
static	int   	extendperm = 0;
static	int   	force = 0;
static	int   	server_fd = -1;
static	int   	maxrequest = 1024;
static	int	cdscp = 0;
static	int	sdscp = 0;

static  Cfg     *cfp;                          /* pointer to configuration*/

static	char	**validqueues = (char **)0;
static	char	**validests = (char **)0;
static	char	**validusers = (char **)0;
static	char	**denyops = (char **)0;
static	char	**logops = (char **)0;
static	char	lp_cmd = 0;

static	void	do_restart(char,char*,char*);
static	void	do_print(char,char*,char*);
static	void	do_status(char,char*,char*);
static	void	do_remove(char,char*,char*);

static	lp_action	lp_actions[] = {
	{	"null",		0,		0	},
	{	"restart",	do_restart,	0	},
	{	"print",	do_print,	0	},
	{	"qstate_s",	do_status,	0	},
	{	"qstate_l",	do_status,	0	},
	{	"remove",	do_remove,	0	},
	{	0,		0,		0	} };

static	void	job_abort(char,char*,char*);
static	void	job_control(char,char*,char*);
static	void	job_data(char,char*,char*);

static	lp_action	job_actions[] = {
	{	"null",		0,		0	},
	{	"abort",	job_abort,	0	},
	{	"control",	job_control,	0	},
	{	"data	",	job_data,	0	},
	{	0,		0,		0	} };

static	fwparm	options[] = {
	{	FWPARM_BOOL,	"-lprng",	(char*) &lprng_client	},
	{	FWPARM_BOOL,	"-anyport",	(char*) &anyport	},
	{	FWPARM_BOOL,	"-force-user",	(char*) &force		},
	{	FWPARM_BOOL,	"-transparent",	(char*) &transparent	},
	{	FWPARM_BOOL,	"-extnd",	(char*) &extendperm	},
	{	FWPARM_STRING,	"-plug-to",	proxy_stats.dst		},
	{	FWPARM_STRING,	"-authuser",	proxy_stats.authuser	},
	{	FWPARM_LIST,	"-dest",	(char*) &validests	},
	{	FWPARM_LIST,	"-user",	(char*) &validusers	},
	{	FWPARM_LIST,	"-queue",	(char*) &validqueues	},
	{	FWPARM_LIST,	"-deny",	(char*) &denyops	},
	{	FWPARM_LIST,	"-log",		(char*) &logops		},
	{	FWPARM_DSCP,	"-client-dscp",	(char*) &cdscp		},
	{	FWPARM_DSCP,	"-server-dscp",	(char*) &sdscp		},
	{	0,		0,		0			} };

extern int lp_conn_server(char*,int,int,char*);

void 
lp_proxy_exit()
{
	time(&offtime);
	syslog(LLEV,"exit host=%.512s/%.20s cmd=%.9s queue=%.20s in=%u out=%u duration=%u",
	            proxy_stats.rladdr,proxy_stats.riaddr,
		    lp_actions[(int)lp_cmd].name,server_queue,
		    proxy_stats.inbytes,proxy_stats.outbytes,
		    (u_int)offtime - (u_int)proxy_stats.start_time);
	exit(1);
}

void
server_puts(line)
char	*line;
{
	int	x;
	x = strlen(line);
	if (write(server_fd,line,x) != x) {
		syslog(LLEV,"error writing server data");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	proxy_stats.outbytes += x+1;
	proxy_update_status();
}

int
get_server_ack()
{
	int	x;

	x = 0;
	if (soread(server_fd,(char*)&x,1)!=1) { 
		syslog(LLEV,"error reading server data");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	proxy_stats.inbytes += 1;
	if (write(0,&x,1)!=1) {
		syslog(LLEV,"error writing client data");
		lp_proxy_exit();
	}
	proxy_update_status();
	return(x);
}


void
client_puts(line,cmd,queue)
char	*line;
int	cmd;
char	*queue;
{
	int	x;
	x = strlen(line);
	if (write(0,line,x) != x) {
		syslog(LLEV,"error writing client data");
		lp_proxy_exit();
	}
	proxy_stats.inbytes += x;
	proxy_update_status();
}

int
check_string(s)
char	*s;
{
	char	*c;
	for (c = s; *c; c++) {
		if ( ((*c < 'a') || (*c > 'z')) &&
                     ((*c < 'A') || (*c > 'Z')) &&
                     ((*c < '0') || (*c > '9')) &&
                      (*c!= '.') && (*c!= '-')  )  
			return(1);
	}
	return(0);
}

/*
 *	First level protocol command handling functions
 */

static void
do_restart(char cmd,char *queue,char *args)
{
	char	lp_cmd_buf[104];
	if (*args) {
		syslog(LLEV,"lp protocol error in RESTART command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	snprintf(lp_cmd_buf,sizeof(lp_cmd_buf),"%c%.32s\n", 001, queue);
	server_puts(lp_cmd_buf);
}

static void
do_print(char cmd,char *queue,char *args)
{
	static 	char	buf[1024]; 
	int		x;  
	if (*args) {
		syslog(LLEV,"lp protocol error in PRINT command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	snprintf(buf,sizeof(buf),"%c%.32s\n", 002, queue);
	server_puts(buf);
#ifdef	DEBUG
	syslog(LLEV,"waiting to acknowledge PRINT command");
#endif
	if (get_server_ack())
		syslog(LLEV,"PRINT command failed");
#ifdef	DEBUG
	syslog(LLEV,"got ack");
#endif

	while ((x=sogets(0,buf,sizeof(buf)-1)) > 0) {
		if ((buf[0] < 1) || (buf[0] > 3)) {
			syslog(LLEV,"lp protocol error in PRINT command");
			write(0,"\001", 1);
			lp_proxy_exit();
		}
		if (buf[x-1] != 0x0a) {
			syslog(LLEV,"error reading line printer protocol");
			lp_proxy_exit();
		} else
			buf[x-1] = 0;
		proxy_update_status();
		job_actions[(int)buf[0]].handler(buf[0],queue,buf);
	}
}

static void
do_status(char cmd,char* queue,char* args)
{
	char	buf[1024]; 
	char	tokbuf[1024];
	char	*tokav[56];
	int 	tokac;
	char 	**user; 
	int	x;

/*	The way we handle allowed user lists:
 *	If no user list is supplied within status query we do build new
 *	one from our list of allowed users.
 *	If one does exist we permit entries that match the list to pass
 *	through. Non-matching ones are discarded and deny message is 
 *	recorded.
 */  

	if (!*args) {
		if (force) {
			snprintf(buf,sizeof(buf),"%c%.32s %.32s\n", cmd, queue, 
				proxy_stats.authuser);
			server_puts(buf);
		} else if (validusers) {
			snprintf(buf,sizeof(buf),"%c%.32s", cmd, queue);
			x = strlen(buf);
			server_puts(buf);
			for (user = validusers; *user; user++) {
				if ((x += strlen(*user) + 1) >= maxrequest) {
					syslog(LLEV,"fwtkcfgerr: user list exceeds request size limit");
					lp_proxy_exit();
				}
				server_puts(" ");
				server_puts(*user);
			}
			server_puts("\n");
		} else {
			snprintf(buf,sizeof(buf),"%c%.32s\n", cmd, queue); 
			server_puts(buf);
		}
	} else {
		snprintf(buf,sizeof(buf),"%c%.32s", cmd, queue);
		server_puts(buf);
		tokac = enargv(args,tokav,56,tokbuf,sizeof(tokbuf));
/*
 *	XXX
 *	Note that we don't know if a job with given numeric id is permitted.
 *	(belongs to allowed user). Do we need a workaround?
 *	Hope not. We just do not care, it is not a major security risk.
 */
		for (x = 0; x < tokac ; x++) {
			if (searchlist(tokav[x],validusers) && 
			    !isalldigits(tokav[x])) {
				syslog(LLEV,"deny host=%.512s/%.20s status request for user %.64s",proxy_stats.rladdr,proxy_stats.riaddr,tokav[x]);
			} else {
				server_puts(" ");
				server_puts(tokav[x]);
			}
		}
		server_puts("\n");
	}
	while (sogets(server_fd,buf,sizeof(buf)-1) > 0) 
		client_puts(buf,cmd,queue);
}

static void
do_remove(char cmd,char *queue,char *args)
{
	char	buf[1024];   
	char 	*agent;

	if (!*args) {
		syslog(LLEV,"lp protocol error in REMOVE command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	agent = args;
	if ((args = index(args,' '))) 
		*args++ = '\0';
	if (force)
		agent = proxy_stats.authuser;
	if (searchlist(agent,validusers)) {
		syslog(LLEV,"deny host=%.512s/%.20s remove request for user %.64s",proxy_stats.rladdr,proxy_stats.riaddr,agent);
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	if (args)
		snprintf(buf,sizeof(buf),"%c%.32s %.64s %.512s\n", 005, queue, agent, args);
	else
		snprintf(buf,sizeof(buf),"%c%.32s %.64s\n", 005, queue, agent);
	
	server_puts(buf);
}

/*	
 *	Job control (PRINT command, 0x02) subcommands
 */

static void
job_abort(char cmd,char *queue,char *buf)
{
/*
 *	From rfc1179:
 *	Abort job
 *
 *     +----+----+
 *     | 01 | LF |
 *     +----+----+
 *
 *  No operands should be supplied.  This subcommand will remove any
 *  files which have been created during this "Receive job" command.
 *
 *  NOTE: we do not process this locally, though we can, but just have
 *  no reason to.
 */

	if (buf[1]) {
		syslog(LLEV,"lp protocol error in PRINT command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	server_puts("\001\n");
	if (get_server_ack())
		syslog(LLEV,"PRINT ABORT failed");
}

static void
job_control(char cmd,char *queue,char *buf)
{
	unsigned	int	cfAsize;
			char*	cfAname;
			char*	cfAhname;
	unsigned	short	jobnum;
			char	jobnumb[3];
			int	x;
	unsigned	int	icount, ocount ;
			char	cfAline[1024];
			char	buf1[1024];
			char**  cfAdata = NULL;
			char*	Hname = NULL;
			char*	Pname = NULL;
			char*	Nname = "stdin";
			int	num;
			char**	cfAp;
			char r;

/*
 *	From rfc1179:
 *	Receive control file
 *
 *     +----+-------+----+------+----+
 *     | 02 | Count | SP | Name | LF |
 *     +----+-------+----+------+----+
 *     Command code - 2
 *     Operand 1 - Number of bytes in control file
 *     Operand 2 - Name of control file
 *
 *  The control file must be an ASCII stream with the ends of lines
 *  indicated by ASCII LF.  The total number of bytes in the stream is
 *  sent as the first operand.  The name of the control file is sent as
 *  the second.
 */
	if ((!(cfAname = index(buf,' '))) || (!(*(++cfAname)))) {	
		syslog(LLEV,"lp protocol error in PRINT command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}

	*(cfAname - 1) = '\0';

	if (!(cfAsize = atoi(buf+1))) {
		syslog(LLEV,"lp protocol error in PRINT command: invalid control file size %s",buf+1);
		write(0,"\001", 1);
		lp_proxy_exit();
	}
/*
 *	Check if control file name is correct.
 *	According to rfc1179, it should have cfAxxxhostname syntax,
 *	where xxx is 3-digit job number (decimal) and hostname
 *	should not be longer than 31 octet.
 *	Some implementations use other than cfA... names but "cf" should
 *	always be there.
 */
	if ((strncmp(cfAname,"cf",2)) || ((x = strlen(cfAname)) < 7) ||
	    (x > 38) || check_string(cfAname)) {
		syslog(LLEV,"lp protocol error in PRINT command: invalid control file name %s",cfAname);
		write(0,"\001", 1);
		lp_proxy_exit();
	}
/*	
 *	We already performed length checks, right?	
 */
	strncpy(jobnumb,cfAname+3,3);
	jobnum = atoi(jobnumb);
	cfAhname = cfAname + 6;
/*
 *	Spoof acknowledgement
 */
	write(0,"\0",1);
/*
 *	Receive and process control file itself
 */
	icount = ocount = 0;
	while (icount < cfAsize) {
		if ((x = sogets(0,cfAline,sizeof(cfAline))) < 1) {
			syslog(LLEV,"error reading client data");
			lp_proxy_exit();
		}
		if (cfAline[x-1] != 0x0a) {
			syslog(LLEV,"error receiving control file");
			lp_proxy_exit();
		} else
			cfAline[x-1] = 0;
		if (!cfAline[1] || (x >128)) {
			syslog(LLEV,"bad line in control file %.40s",cfAname);
			write(0,"\001", 1);
			lp_proxy_exit();
		}

		switch (cfAline[0]) {
		case 'C':
			if (x > 33) {
				syslog(LLEV,"securityalert: too long Class line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			break;
		case 'H':
			if (x > 33) {
				syslog(LLEV,"securityalert: too long Host line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			if (check_string(cfAline+1)) {
				syslog(LLEV,"lp protocol error in PRINT command: invalid Host line in control file, %s",cfAline+1);
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			Hname = strdup(cfAline+1);
			break;
		case 'I':
		case 'W':
			if ((num = atoi(cfAline+1)) <= 0) {
				syslog(LLEV,"bad line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			} 
			snprintf(cfAline,sizeof(cfAline),"%c%d",cfAline[1],num);
			break;
		case 'J':
			if (x > 101) {
				syslog(LLEV,"securityalert: too long Job line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			break;
		case 'N':
			if (x > 133) {
				syslog(LLEV,"securityalert: too long Name line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			Nname = strdup(cfAline+1);
			break;
		case 'P':
			if (x > 33) {
				syslog(LLEV,"securityalert: too long Person line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			Pname = strdup(cfAline+1);
			if (searchlist(Pname,validusers)) {
				syslog(LLEV,"deny host=%.512s/%.20s print request for user %.64s",proxy_stats.rladdr,proxy_stats.riaddr,Pname);
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			if (force)
				snprintf(cfAline,sizeof(cfAline),"P%.31s",proxy_stats.authuser);
			break;
		case 'T':
			if (x > 81) {
				syslog(LLEV,"securityalert: too long Title line in control file");
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			break;
		case 'U':
		case 'c':
		case 'd':
		case 'f':
		case 'g':
		case 'l':
		case 'n':
		case 'o':
		case 'p':
		case 'r':
		case 't':
		case 'v':
			if ((strncmp(cfAline+1,"df",2)) || 
			    (x < 8) || (x > 39) || check_string(cfAline)) {
				syslog(LLEV,"lp protocol error in PRINT command: invalid data file name in %c control",cfAline[0]);
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			if (strcmp(cfAline+7,cfAhname)) {
				syslog(LLEV,"securityalert: host names do not match for control file and its contents, command %c: %.64s vs %.64s",cfAline[0],cfAhname,cfAline+7);
				write(0,"\001", 1);
				lp_proxy_exit();
			}
			break;
		case 'S':
			{
				unsigned long	dev,inode;
					 char	*p;
				if (!(dev = strtol(cfAline+1,NULL,10)) ||
				    !(p = index(cfAline,' ')) || !(*(++p)) ||
				    !(inode = strtol(p,NULL,10))) {
					syslog(LLEV,"lp protocol error in PRINT command: Symlink syntax error");
					write(0,"\001", 1);
					lp_proxy_exit();
				}
			}
			break;
		case '1':
		case '2':
		case '3':
		case '4':
/*
 *	Have no idea on what restrictions are useful here
 */
			break;
		default:
			syslog(LLEV,"lp protocol error in PRINT command: unknown control %c",cfAline[0]);
			write(0,"\001", 1);
			lp_proxy_exit();
		}
		icount += x;
		ocount += strlen(cfAline) + 1;
		addlist(cfAline,&cfAdata);
	}
	if (!Pname || !Hname) {
		syslog(LLEV,"lp protocol error in PRINT command: incomplete user identification in control file");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
			
/*
 *	Reconstuct the command
 */
	snprintf(buf1,sizeof(buf1),"%c%u cf%c%03u%.31s\n",'\02',ocount,cfAname[2],jobnum,
	 	cfAhname);
	syslog(LLEV,"control accepted: print job %03u (file=%s) for %s@%s",
	       jobnum,Nname,Pname,Hname);
	server_puts(buf1);
	num = 0;
	if ((x = soread(server_fd,&r,1))!=1) { 
		syslog(LLEV,"error reading server data %d %s",x, strerror(errno));
		write(0,"\001", 1);
		lp_proxy_exit();
	}
	proxy_stats.inbytes += 1;
	proxy_update_status();
	if (r)
		syslog(LLEV,"PRINT CONTROL failed");

#ifdef	DEBUG
	syslog(LLEV,"print control header accepted, outbytes now %u",proxy_stats.outbytes);
#endif

	for (cfAp = cfAdata; *cfAp; cfAp++) {
		server_puts(*cfAp);
		server_puts("\n");
	}
#ifdef	DEBUG
	syslog(LLEV,"print control data, outbytes now %u, ocount was %u",proxy_stats.outbytes,ocount);
#endif
	write(server_fd,"\0",1); 
	if (get_server_ack())
		syslog(LLEV,"PRINT CONTROL failed");
#ifdef	DEBUG
	syslog(LLEV,"print control data accepted");
#endif
}


static void
job_data(char cmd,char *queue,char *buf)
{
	unsigned	int	dfAsize;
			char*	dfAname;
			char*	dfAhname;
	unsigned	short	jobnum;
			char	jobnumb[3];
			int	x;
	unsigned	int	count;
			char	buf1[4096];

/*    +----+-------+----+------+----+
 *    | 03 | Count | SP | Name | LF |
 *    +----+-------+----+------+----+
 *    Command code - 3
 *    Operand 1 - Number of bytes in data file
 *    Operand 2 - Name of data file
 *
 *  The data file may contain any 8 bit values at all.  The total number
 *  of bytes in the stream may be sent as the first operand, otherwise
 *  the field should be cleared to 0.
 *
 *  We do not handle this case, though, because print data may
 *  contain \0 so size parameter is mandatory. Please let me know
 *  if it breaks something.
 */
	if (!(dfAname = index(buf,' ')) || !(*(++dfAname))) {	
		syslog(LLEV,"lp protocol error in PRINT command");
		write(0,"\001", 1);
		lp_proxy_exit();
	}

	*(dfAname-1) = '\0';

	if (!(dfAsize = atoi(buf+1))) {
		syslog(LLEV,"lp protocol error in PRINT command: invalid data file size");
		write(0,"\001", 1);
		lp_proxy_exit();
	}
/*
 *	Check if data file name is correct.
 *	According to rfc1179, it should have dfAxxxhostname syntax,
 *	where xxx is 3-digit job number (decimal) and hostname
 *	should not be longer than 31 octet.
 *	Some implementations use other than dfA... names but "df" should
 *	always be there.
 */
	if ((strncmp(dfAname,"df",2)) || ((x = strlen(dfAname)) < 7) ||
	    (x > 38) || check_string(dfAname)) {
		syslog(LLEV,"lp protocol error in PRINT command: invalid data file name %s",dfAname);
		write(0,"\001", 1);
		lp_proxy_exit();
	}
/*	
 *	We already performed length checks, right?	
 */
	strncpy(jobnumb,dfAname+3,3);
	jobnum = atoi(jobnumb);
	dfAhname = dfAname + 6;
	snprintf(buf1,sizeof(buf1),"%c%u df%c%03u%.31s\n",'\03',dfAsize,dfAname[2],jobnum,
	 	dfAhname);
	server_puts(buf1);
#ifdef	DEBUG
	syslog(LLEV,"waiting to acknowledge dfA header");
#endif
	if (get_server_ack())
		syslog(LLEV,"PRINT COMMAND failed");
#ifdef	DEBUG
	syslog(LLEV,"got ack");
#endif
/*
 *	Receive and send data file itself
 */
	count = 0;
	while (count <= dfAsize) {
		if ((x = soread(0,buf1,sizeof(buf1))) < 0) {
			syslog(LLEV,"error reading client data");
			lp_proxy_exit();
		}
		if (write(server_fd,buf1,x) != x) {
			syslog(LLEV,"error writing server data");
			lp_proxy_exit();
		}
		count += x;
		proxy_stats.outbytes += x;
		proxy_update_status();
	}
	write(server_fd,'\0',1);
#ifdef	DEBUG
	syslog(LLEV,"waiting to acknowledge dfA data");
#endif
	if ((x = get_server_ack()))
		syslog(LLEV,"PRINT DATA failed %x",x);
#ifdef	DEBUG
	syslog(LLEV,"got ack");
#endif
}

void portnum_check(fd)
int 	fd;
{
	struct	sockaddr_in	c_addr;
		socklen_t	l;
		uint16_t	x;

	if (anyport)
		return;

	l = sizeof(c_addr);
	if (getpeername(fd, (struct sockaddr *)&c_addr, &l) < 0) {
		syslog(LOG_ERR, "fwtksyserr: getsockname(): %s", strerror(errno));
		exit(1);
	}
	if ((x=ntohs(c_addr.sin_port)) > 1023) {
		syslog(LLEV,"deny host=%.512s/%.20s connect from unprivileged port %d",proxy_stats.rladdr,proxy_stats.riaddr,x);
		write(0,"\001", 1);
		exit(1);
	}
	if (lprng_client && ((x>731) || (x<721))) { 
		syslog(LLEV,"deny host=%.512s/%.20s connect by non-lprNG client, source port %d",proxy_stats.rladdr,proxy_stats.riaddr,x);
		write(0,"\001", 1);
		exit(1);
	}
}

int
permit_queue(queue_name)
char *queue_name;
{
	if(validqueues != (char **)0) {
		char    **xp;

		for(xp = validqueues; *xp != (char *)0; xp++) {
			if(**xp == '!' && strcmp(*xp + 1,queue_name)) {
				syslog(LLEV,"deny host=%.512s/%.20s access to queue %.64s",proxy_stats.rladdr,proxy_stats.riaddr,queue_name);
				return(0);
			} else if (!strcmp(*xp,queue_name) || (**xp == '*')) {
				break;
			}
			syslog(LLEV,"deny host=%.512s/%.20s access to queue %.64s",proxy_stats.rladdr,proxy_stats.riaddr,queue_name);
			return(0);
		}
	}
	return(1);
}

static	void
setflag(list,c,flag)
char	**list;
int	c;
int	flag;
{
	lp_action	*act;
	char		**entry;

	if (!list)
		return;

	for (entry = list; *entry; entry++) {
		for (act = lp_actions; act->name ; act++)
			if(!strcasecmp(*entry,act->name)) {
				act->flg |= flag;
				break;
			}
		if (!act->name) {
			syslog(LLEV,"fwtkcfgerr: config line %d: bad operand %.100s",c,*entry);
			exit(1);
		}
	}
}


void
get_printer(client_queue)
char *client_queue;
{
	Cfg	*cf;
	int	x;
	char *host = NULL;
	char *myqueue = NULL;

	cf=cfg_get("printer",cfp);
	while (cf != (Cfg *)0) {
		if((cf->argc < 1) || (cf->argv[0][0] == '-')) {
			syslog(LLEV,"fwtkcfgerr: missing parameter, line %d",cf->ln);
			lp_proxy_exit();
		}
		if (!(strcmp(cf->argv[0],client_queue)) || !(strcmp(cf->argv[0],"*"))) 
				goto match;
		cf=cfg_get("printer",(Cfg *)0); 
	}
	syslog(LLEV,"fwtkcfgerr: cannot map client queue %.512s to server", client_queue);
	lp_proxy_exit();

match:  x=1;
	while(x < cf->argc)

	if (x >= cf->argc) {
		syslog(LLEV,"fwtkcfgerr: %.512s queue has no mapping", client_queue);
		lp_proxy_exit();
	} else
	while (x < cf->argc) {
		if(!strcmp(cf->argv[x], "-host")) {
			if (host) {
				syslog(LLEV,"fwtkcfgerr: destintation host repeated, line %d",cf->ln);
				lp_proxy_exit();
			}
			x++;
			if((x >= cf->argc) || (cf->argv[x][0] == '-')) {
				syslog(LLEV,"fwtkcfgerr: -host missing parameter, line %d",cf->ln);
				lp_proxy_exit();
			}
			host=cf->argv[x];
		} else if (!strcmp(cf->argv[x], "-printer")) {
			if (myqueue) {
				syslog(LLEV,"fwtkcfgerr: destintation queue repeated, line %d",cf->ln);
				lp_proxy_exit();
			}
			x++;
			if((x >= cf->argc) || (cf->argv[x][0] == '-')) {
				syslog(LLEV,"fwtkcfgerr: -printer missing parameter, line %d",cf->ln);
				lp_proxy_exit();
			}
			myqueue=cf->argv[x];
		} else {
			syslog(LLEV,"fwtkcfgerr: invalid option %.128s, line %d",cf->argv[x],cf->ln);
			lp_proxy_exit();
		}
		x++;
	}
	if (myqueue)
		strncpy(server_queue,myqueue,sizeof(server_queue));

	if (!host) {
	   syslog(LLEV,"server name not present in request from %s", proxy_stats.rladdr);
	   lp_proxy_exit();
	}
	if (myqueue)
		strncpy(proxy_stats.dst,host,sizeof(proxy_stats.dst));
}

int
main(int argc, char *argv[])
{
	char	buf1[1024];   
       	char	*lp_queue = NULL ;
       	char  	*lp_param = NULL ;
	int   	xx = 0;
	int   	i = 0;

	proxy_stats.port = str_to_port("printer");
	proxy_init(argc,argv);
	proxy_update_operation("INIT");
	maxrequest = proxy_conf_int(proxy_confp,"maxrequest",16,1024,
				    maxrequest);

	if((cfp=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr,
				   proxy_stats.riaddr))) 
		proxy_parse_options(cfp,options);
	else {
	  	write (0, "\001", 1);
		lp_proxy_exit();
	}

	if (cdscp)
		proxy_set_dscp(0,cdscp);

	sotimeout(proxy_timeout);
	sosetline(3);
	signal(SIGPIPE, SIG_IGN);
	portnum_check(0);

	setflag(logops,  cfp->ln, OP_LOG);
	setflag(denyops, cfp->ln, OP_DENY);

	memset(buf1,0,1024); 			/* Just in case.. */	
	if ((xx = sogets(0,buf1, 1023)) <=0) {
		syslog(LLEV,"error reading line printer protocol");
		lp_proxy_exit();
	} else {
		if (buf1[xx-1] != 0x0a) {
			syslog(LLEV,"error reading line printer protocol");
			lp_proxy_exit();
		} else
			buf1[xx-1] = 0;
		lp_cmd = buf1[0];
		for (i=1; ((i < xx-1) && (buf1[i] != ' ')); i++); 
		if (buf1[i] == ' ')
			buf1[i++] = 0;
		lp_queue = (char *)(&buf1[1]);
	   	lp_param = (char *)(&buf1[i]);
	}
	
	strlcpy(server_queue, lp_queue, sizeof(server_queue));

	if(!permit_queue(lp_queue)) {
		lp_proxy_exit();
	}

	if ((lp_cmd <1) || (lp_cmd >5)) {
		syslog(LLEV,"unknown lp command : %o", lp_cmd);
		lp_proxy_exit();
	}

	if(lp_actions[(int)lp_cmd].flg & OP_DENY) {
		syslog(LLEV,"deny host=%.512s/%.20s access to queue %.32s cmd=%.10s",proxy_stats.rladdr,proxy_stats.riaddr,lp_queue,lp_actions[(int)lp_cmd].name);
		lp_proxy_exit();
	}

	if (transparent)
		proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);

	if (!(*proxy_stats.dst))
		get_printer(lp_queue);
	else 
		strncpy(server_queue,lp_queue,sizeof(server_queue)-1);

	if (proxy_check_dest(validests,extendperm)) 
		lp_proxy_exit();

	syslog(LLEV,"permit host=%.512s/%.20s access to queue %.32s/%.32s@%.32s cmd=%.10s", proxy_stats.rladdr,proxy_stats.riaddr,lp_queue,server_queue,proxy_stats.dst,lp_actions[(int)lp_cmd].name);

	proxy_update_operation("CONNECTING");
	if((server_fd = lp_conn_server(proxy_stats.dst, proxy_stats.port, 1, (char *)0)) < 0) {
		syslog(LLEV,"cannot connect to server %.256s lp port: %s", proxy_stats.dst, strerror(errno));
		lp_proxy_exit();
	}
	proxy_chroot_setugid();
	if (sdscp)
                proxy_set_dscp(server_fd,sdscp);

	proxy_update_operation(lp_actions[(int)lp_cmd].name);
	lp_actions[(int)lp_cmd].handler(lp_cmd,server_queue,lp_param);
	lp_proxy_exit();
	return(0);
} 

