/*
 * Non Spooling LPD server (RFC1179)
 * http://www.cis.ohio-state.edu/htbin/rfc/rfc1179.html
 *
 * Written by Steve Flynn
 * Based on Sigma Designs "How to write LPD programs"
 * http://www.dd.sigma.se/network/lpd.html
 * For Final Semester WebGate Project
 * June 1999
 */

/* Usage: lpdsrv -n netmask -p port -r rawque -f feedque -o output
 * netmask : Network mask for job acceptance	(max 11 char)	- default: 192.168.0
 * port    : TCP/IP port in decimal								- default: 515
 * rawque  : Name of Raw print queue name		(max 32 char)	- default: nt
 * feedque : Name of FormFeed print queue name	(max 32 char)	- default: lp
 * output  : Name of Output File/Device name	(max 1024 char)	- default: /dev/lp
 */

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define TBUF_LEN 1024
#define QNAME_LEN 32


/* LPD server program. */
void main(argc, argv)
int argc;
char *argv[];
	{
	int i, sockfd, newsockfd, clilen;
	struct sockaddr_in CliAddr, ServAddr;
	int TCPPort = 515;
	struct timeval timeout;
	fd_set readfds, writefds;
	int reqlen, totreqlen, formfeed, controlfile;
	char tbuf[TBUF_LEN] = "1.1 By Steve Flynn, June 1999";
	char rbuf[TBUF_LEN];
	char *cli_ip;
	char ValidNet[12] = "192.168.0";
	char Printer[TBUF_LEN+1] = "/dev/lp";
	char FeedQue[QNAME_LEN+1] = "lp";
	char RawQue[QNAME_LEN+1] = "nt";
	char *PageFeed = "\f";
	int file_length = 0;
	int lpfd = -1;
			
	syslog(LOG_INFO, "%s V%s: started.", argv[0], tbuf);
	/* Arguments:
	 * -n netmask : Network mask for job acceptance
	 * -p port    : TCP/IP port in decimal
	 * -r rawque  : Name of Raw print queue name
	 * -f feedque : Name of FormFeed print queue name
	 * -o output  : Name of output file/device name
	 */
	for (i = 1; i < argc; i++)
		{
		if (!strncasecmp(argv[i], "-n", 2) && (++i < argc))
			{
			strncpy(ValidNet, argv[i], 12);
			continue;
			}
		if (!strncasecmp(argv[i], "-p", 2) && (++i < argc))
			{
			TCPPort = atoi(argv[i]);
			continue;
			}
		if (!strncasecmp(argv[i], "-r", 2) && (++i < argc))
			{
			strncpy(RawQue, argv[i], QNAME_LEN);
			continue;
			}
		if (!strncasecmp(argv[i], "-f", 2) && (++i < argc))
			{
			strncpy(FeedQue, argv[i], QNAME_LEN);
			continue;
			}
		if (!strncasecmp(argv[i], "-o", 2) && (++i < argc))
			{
			strncpy(Printer, argv[i], TBUF_LEN);
			continue;
			}
		}
	/* printf("Port: %d, Netmask: %s, RawQue: %s, FeedQue: %s, Output: %s", TCPPort, ValidNet, RawQue, FeedQue, Printer); */
	/* Make sure both ques have a different name */
	if (!strcasecmp(FeedQue, RawQue))
		{
		syslog(LOG_ERR, "%s: Both print queues have the same name!", argv[0]);
		exit(1);
		}
	/* Open a TCP stream socket. */
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		{
		syslog(LOG_ERR, "%s: Can't open stream socket, exiting!", argv[0]);
		exit(1);
		}
	/* Bind our local address so that the client can send to us. */
	memset((char *)&ServAddr, 0, sizeof(ServAddr));
	ServAddr.sin_family      = AF_INET;
	ServAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	ServAddr.sin_port        = htons((u_short)TCPPort);
	if (bind(sockfd, (struct sockaddr *)&ServAddr, sizeof(ServAddr)) < 0)
		{
		syslog(LOG_ERR, "%s: Can't bind local port or address, exiting!", argv[0]);
		exit(1);
		}
	/* Detach from parent */
	daemon(0, 0);
	/* Queue requests */
	listen(sockfd, 5);
	/* Main server infinite loop processing client commands */
	syslog(LOG_INFO, "%s: Port: %d, Netmask: %s, RawQue: %s, FeedQue: %s, Output: %s", argv[0], TCPPort, ValidNet, RawQue, FeedQue, Printer);
	for (;;)
		{
		/* Wait for a connection from a client process. */
		clilen = sizeof(CliAddr);
		if ((newsockfd = accept(sockfd, (struct sockaddr *)&CliAddr, &clilen)) < 0)
			{
			syslog(LOG_ERR, "%s: Socket error, exiting!", argv[0]);
			exit(1);
			}
		/* Identify connection client */
		if (((cli_ip = inet_ntoa(CliAddr.sin_addr)) == NULL)
			|| strncmp(cli_ip, ValidNet, strlen(ValidNet)))
			{
			syslog(LOG_INFO, "%s: Rejected connection from %s, port %d.", argv[0], cli_ip, ntohs(CliAddr.sin_port));
			close(newsockfd);
			continue;
			}
		syslog(LOG_INFO, "%s: Accepted connection from %s, port %d.", argv[0], cli_ip, ntohs(CliAddr.sin_port));
		/* Setup a timeout for read and write */
		timeout.tv_sec = 16;
		timeout.tv_usec = 0;
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);		
		FD_SET(newsockfd, &readfds);
		FD_SET(newsockfd, &writefds);
		/* Read a complete LPD daemon command from the connected client */
		if ((select(32, &readfds, NULL, NULL, &timeout) == 1)
			&& ((reqlen = recv(newsockfd, tbuf, TBUF_LEN-1, 0)) > 0) )
			{
			/* Interpret daemon command acording to LPD RFC1179 */
			switch (tbuf[0])
				{
				case 1:	/* Print any waiting jobs - do nothing */
					break;
				case 2: /* Receive subcommand */
					/* Accept connections to printers named by FeedQue and RawQue */
					rbuf[0] = 0x01;
					formfeed = 0;
					if (!strncasecmp(&tbuf[1], FeedQue, strlen(FeedQue)))
						{
						formfeed = 1;
						rbuf[0] = 0x00;
						}
					if (!strncasecmp(&tbuf[1], RawQue, strlen(RawQue)))
						{
						formfeed = 0;
						rbuf[0] = 0x00;
						}
					/* Send ack/nak */
					if (select(32, NULL, &writefds, NULL, &timeout) == 1)
						send(newsockfd, rbuf, 1, 0);
					else
						break;
					/* Check for invalid printer name request */
					if (rbuf[0] == 0x01)
						break;
					/* Deal with all job subcommands */
					while (select(32, &readfds, NULL, NULL, &timeout) == 1
						   && ((reqlen = recv(newsockfd, tbuf, TBUF_LEN-1, 0)) > 0) )
						   {
							controlfile = 0;
							switch (tbuf[0])
								{
								case 1: /* Abort job */
									/* Send subcommand ack */
									rbuf[0] = 0x00;
									if (select(32, NULL, &writefds, NULL, &timeout) == 1)
										send(newsockfd, rbuf, 1, 0);
									break;
								case 2: /* ReceiveReceive control file */
									controlfile = 1;
								case 3: /* Receive data file */
									/* Send subcommand ack */
									rbuf[0] = 0x00;
									if (select(32, NULL, &writefds, NULL, &timeout) == 1)
										send(newsockfd, rbuf, 1, 0);
									else
										break;
									/* Get file length - forget filename */
									tbuf[reqlen] = '\0';
									file_length = 0;
									if (!sscanf(&tbuf[1], "%d", &file_length))
										file_length = 0;
									if (lpfd >= 0)
										close(lpfd);
									lpfd = -1;
									/* Open Output Device/File with Wait Timeout */
									if (!controlfile)
										for (i = 1; ; i = i < 32 ? i << 1 : i)
											{
											lpfd = open(Printer, O_WRONLY);
											if (lpfd >= 0)
												break;
											if (errno == ENOENT)
												{
												syslog(LOG_ERR, "%s: Can't open /dev/lp, exiting!", argv[0]);
												exit(1);
												}
											if (i == 1)
												syslog(LOG_INFO, "%s: waiting for /dev/lp to become ready (offline ?)", argv[0]);
											sleep(i);
											}
									/* Read and Print Data */
									totreqlen = 0;
									while (select(32, &readfds, NULL, NULL, &timeout) == 1
										&& ((reqlen = recv(newsockfd, tbuf, TBUF_LEN-1, 0)) > 0))
										{
										if (!controlfile && (lpfd >= 0) && (write(lpfd, tbuf, reqlen) == -1))
											{
											syslog(LOG_ERR, "%s: Error writing to /dev/lp, exiting!", argv[0]);
											close(newsockfd);
											if (lpfd >= 0)
												close(lpfd);
											exit(1);
											}
										totreqlen += reqlen;
										/* Look for EOF */
										if (file_length <= 0)
											{
											/* No real file lenght available */
											if (reqlen > 0 && tbuf[reqlen-1] == 0x00)
												{
												/* Send ack for received complete file */
												rbuf[0] = 0x00;
												if (select(32, NULL, &writefds, NULL, &timeout) == 1)
													send(newsockfd, rbuf, 1, 0);
												if (formfeed && !controlfile && (lpfd >= 0) && (write(lpfd, PageFeed, 1) == -1))
													{
													syslog(LOG_ERR, "%s: Error writing to /dev/lp, exiting!", argv[0]);
													close(newsockfd);
													if (lpfd >= 0)
														close(lpfd);
													exit(1);
													}
												break;
												}
											}
										else
											{
											/* Real file lenght available */
											if ((totreqlen > file_length)
												&& (reqlen > 0)
												&& (tbuf[reqlen-1] == 0x00))
												{
												/* Send ack for received complete file */
												rbuf[0] = 0x00;
												if (select(32, NULL, &writefds, NULL, &timeout) == 1)
													send(newsockfd, rbuf, 1, 0);
												if (formfeed && !controlfile && (lpfd >= 0) && (write(lpfd, PageFeed, 1) == -1))
													{
													syslog(LOG_ERR, "%s: Error writing to /dev/lp, exiting!", argv[0]);
													close(newsockfd);
													if (lpfd >= 0)
														close(lpfd);
													exit(1);
													}
												break;
												}
											}
										} /* End of read file loop */
										if (lpfd >= 0)
											close(lpfd);
										lpfd = -1;
										break;
								default:
									syslog(LOG_INFO, "%s: Unknown subcommand %d received from %s.", argv[0], tbuf[0], cli_ip);
									/* Send subcommand nak */
									rbuf[0] = 0x01;
									if (select(32, NULL, &writefds, NULL, &timeout) == 1)
										send(newsockfd, rbuf, 1, 0);
									goto disconnect;
									break;
								}
							}
					break;
				case 3: /* Send queue state (short) */
				case 4:	/* Send queue state (long) */
					strcpy(rbuf, "no entries\n");
					if (select(32, NULL, &writefds, NULL, &timeout) == 1)
						send(newsockfd, rbuf, strlen(rbuf), 0);
					break;
				case 5:	/* Remove jobs - do nothing */
					break;
				default: /* Unknown command */
					syslog(LOG_INFO, "%s: Unknown command %d received from %s.", argv[0], tbuf[0], cli_ip);
					/* Send subcommand nak */
					rbuf[0] = 0x01;
					if (select(32, NULL, &writefds, NULL, &timeout) == 1)
						send(newsockfd, rbuf, 1, 0);
					goto disconnect;
					break;
				}
			} 
disconnect:
		/* Close connection to client process */
		close(newsockfd);
		} /* End of main server infinite loop */
	/* This point should never be reached! */
	syslog(LOG_ERR, "%s: Abnormal exit.", argv[0]);
	if (lpfd >= 0)
		close(lpfd);
	}
