/*
 * lpr.c - Windows NT lpr
 *
 * by Eric W. Brown
 *    28 October '92
 */

#include <windows.h>
#include <winbase.h>
#include <winsock.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/locking.h>
#include <io.h>

#include "lp.h"

/*
 * #defines
 */
#define HOSTNAME_LEN	(MAX_COMPUTERNAME_LENGTH + 1)
#define USERNAME_LEN	(32 /* MAX_USERNAME_LENGTH */ + 1)


/*
 * Global Variables
 */
int		sockfd = -1;
char	hostname[HOSTNAME_LEN];
char	username[USERNAME_LEN];
char	*printer = NULL;
char	*server = NULL;
char	*temp_dir = NULL;
char	*class = NULL;
char	*job = NULL;
char	*title = NULL;
char	filter = 'f';
int		noburst = 0;
int		debug = 0;
int		dosfilter = 0;
int		width = 0;
int		copies = 1;
int		indent = -1;
int		tcp_initialized = 0;
int		seqno;
FILE	*cf_file = NULL;
char	tmp_cfname[33] = "";
char	tmp_dfname[33] = "";
char	tmp_stdin_fname[33] = "";


/*
 * Prototypes
 */
int usage();
void lpr_start_protocol();
void lpr_finish_protocol();
void lpr_print_file(char *filename, char *name);
void lpr_send_file(int fd, int length);
void lpr_send(char *buf, int cnt);
void lpr_check_ack();
void lpr_create_control_file(char *tmp_cfname);
void lpr_capture_stdin(char *fname);
long lpr_text_filelength(int fd);
void lpr_filter_dos(char *infname, char *outfname);
int  lpr_get_seqno(char *seq_fname);
void lpr_crash(int report, char *fmt, ...);
#define NO_REPORT		0
#define REPORT_SOCKERR	1
#define REPORT_FILEERR	2
#define REPORT_ERR		3

int usage()
{
	fprintf(stderr, "Usage: lpr [ -Pprinter ] [ -Sserver ] [ -#num ] [ -C class ] [ -J job ]\n");
	fprintf(stderr, "           [ -T title ] [ -i [ numcols ]] [ -wnum ] [ -pvcgdntlfhD ]\n");
	fprintf(stderr, "           [ -u ] [ -#n ] [ name ... ]\n");
	fprintf(stderr, "       -u : filter for unix (remove ^M and ^D characters)\n");
	exit(1);
	return 0;
} /* usage() */

void main(int argc, char **argv)
{
	char	*ptr;
	long	fileHandle;
	struct _finddata_t fileinfo;
	int		length;
	char	filename[256];
	
	/*
	 * Get environment variables:
	 *   server, printer, hostname, username & temp_dir
	 */
	server = getenv("SERVER");
	printer = getenv("PRINTER");
	if (!printer)
		printer = DEFAULT_PRINTER;
	
	length = HOSTNAME_LEN;
	if (!GetComputerName(hostname, &length) || length == 0)
		lpr_crash(REPORT_ERR, "lpr: No hostname, name your computer!!!\n");
	
	length = USERNAME_LEN;
	if (!GetUserName(username, &length))
		strncpy(username, hostname, USERNAME_LEN);
	
	temp_dir = getenv("TEMP");
	if (!temp_dir) {
		fprintf(stderr, "lpr: TEMP environment variable not set.\n");
		exit(1);
	}
	else if (_access(temp_dir, 06) < 0) {
		fprintf(stderr, "lpr: TEMP directory, %s, does not exist or %s\n", 
				temp_dir, "invalid permissions");
		exit(1);
	} /* else */
	
	
	/*
	 * Parse command line
	 */
#define NEXT_ARG()  (argc--, argv++, argc ? *argv : (char *)(usage(), NULL))
#define ARG_ARG()   (strlen(*argv) == 2 ? NEXT_ARG() : (*argv) + 2)
	while (argc > 1 && argv[1][0] == '-') {
		NEXT_ARG();
		switch(argv[0][1]) {
		case 'P':	printer = ARG_ARG();		break;
		case 'S':	server = ARG_ARG();			break;
		case 'C':	class = ARG_ARG();			break;
		case 'J':	job = ARG_ARG();			break;
		case 'T':	title = ARG_ARG();			break;
		case 'p':
		case 'v':
		case 'c':
		case 'g':
		case 'd':
		case 'n':
		case 't':
		case 'l':	filter = argv[0][1];		break;
		case 'f':	filter = 'r';				break;
		case 'h':	noburst = 1;				break;
		case 'D':	debug = 1;					break;
		case 'u':	dosfilter = 1;				break;
		
		case 'i':
			indent = (*argv[2] ? atoi(*argv + 2) : 8);
			if (indent < 0 || indent > 255) {
				fprintf(stderr, "lpr: %s: invalid indentation, %s",
						*argv, "please keep it between 0 and 255.\n");
				usage();
			} /* if */
			break;
			
		case 'w':
			if (sscanf(ARG_ARG(), "%d", &width) != 1) {
				fprintf(stderr, "lpr: invalid width\n");
				usage();
			} /* if */
			break;	
			
		case '#':
			if (sscanf(ARG_ARG(), "%d", &copies) != 1) {
				fprintf(stderr, "lpr: # of copies must be an integer.\n");
				exit(1);
			} /* if */
			break;
			
		default:
			fprintf(stderr, "lpr: '%s' invalid option\n", *argv);
			usage();
		} /* switch */
	} /* while argc */


	/*
	 * Loop over remaining arguments, (Print these files ...)
	 */
	while (argc > 1) {
		NEXT_ARG();
		
		/* For unix compatibility, replace '/'s with '\'s */
		ptr = *argv;
		while(*ptr)
			if (*ptr++ == '/')
				ptr[-1] = '\\';
				
		
		if ((fileHandle = _findfirst(*argv, &fileinfo)) != -1L) {
			do {
				if (!tcp_initialized)
					lpr_start_protocol();
				strcpy(filename, *argv);
				if (ptr = strrchr(filename, '\\'))
					ptr[1] = '\0';
				else if (ptr = strrchr(filename, ':'))
					ptr[1] = '\0';
				else
					filename[0] = '\0';
				strcat(filename, fileinfo.name);
				lpr_print_file(filename, filename);
			} while (_findnext(fileHandle, &fileinfo) == 0);
			_findclose(fileHandle);
		}
		else {
			fprintf(stderr, "lpr: %s: Invalid file spec.\n", *argv);
			if (tcp_initialized)
				break;
			else
				exit(1);
		} /* else */
	} /* while argc */
	

	/*
	 * See if we should take input from stdin
	 */
	if (!tcp_initialized) {
		lpr_start_protocol();
		sprintf(tmp_stdin_fname, "%s\\sin%03d.tmp", temp_dir, seqno);
		lpr_capture_stdin(tmp_stdin_fname);
		lpr_print_file(tmp_stdin_fname, "(standard input)");
		unlink(tmp_stdin_fname);
	} /* if */
	

	/*
	 * Clean up
	 */
	if (tcp_initialized) {
		lpr_finish_protocol();
		closesocket(sockfd);
		lp_tcp_shutdown();
		fclose(cf_file);
		unlink(tmp_cfname);
	} /* if */
	
	exit(0);
} /* main() */


void lpr_start_protocol()
{
	char seq_fname[33];
	char message[80];
	
	/*
	 * Do a lookup in the printcap file if necessary
	 */
	if (!server)
		if (!(server = lp_printcap_server_lookup(printer))) {
			fprintf(stderr, "lpr: No server, %s, and no printer, %s, %s",
					server, printer, "in printcap file!\n");
			usage();
		} /* if */
	
	
	/*
	 * Initialize tcp
	 */
	if (lp_tcp_startup())
		lpr_crash(REPORT_SOCKERR, "lpr: Couldn't initialize tcp/ip\n");
	tcp_initialized = 1;
	if (debug) fprintf(stderr, "lpr: Initialized TCP/IP\n");
	
	
	/*
	 * Open a socket
	 */
	if ((sockfd = lp_tcp_open(server)) < 0)
		lpr_crash(REPORT_SOCKERR, "lpr: Couldn't open connection to server, %s\n",
				  server);
	if (debug) fprintf(stderr, "lpr: Connected to server, %s\n", server);
	

	/*
	 * Get sequence number and increment it
	 */
	sprintf(seq_fname, "%s\\lpr.seq", temp_dir);
	seqno = lpr_get_seqno(seq_fname);
	
	
	/*
	 * Create control file
	 */
	sprintf(tmp_cfname, "%s\\cfA%03d.tmp", temp_dir, seqno);
	lpr_create_control_file(tmp_cfname);
	
	
	/*
	 * Tell LPD to receive a job
	 */
	sprintf(message, "%c%s\n", LPD_PRINT_JOB, printer);
	lpr_send(message, strlen(message));
	lpr_check_ack();
	if (debug) fprintf(stderr, "lpr: Server is accepting job.\n");
} /* lpr_start_protocol() */


void lpr_finish_protocol()
{
	int length;
	char message[80];
	
	/*
	 * Send control file receive message
	 */
	fflush(cf_file);
	length = _filelength(_fileno(cf_file));
	sprintf(message, "%c%d cfA%03d%s\n", LPD_RECEIVE_CONTROL_FILE,
			length, seqno, hostname);
	lpr_send(message, strlen(message));
	lpr_check_ack();
	
	/*
	 * Send control file
	 */
	if (debug) fprintf(stderr, "lpr: Sending control file to %s\n", server);
	_lseek(_fileno(cf_file), 0, SEEK_SET);
	lpr_send_file(_fileno(cf_file), length);
} /* lpr_finish_protocol() */
	
	
void lpr_print_file(char *filename, char *name)
{
	static int	file_count = 0;
	static char	*df_prefix = "df?";
	char	dfname[65];
	int		fd;
	int		length;
	char	message[80];
	int		i;
	int		is_text;
	
	/*
	 * Create df filename
	 */
	if (file_count++ % 26 == 0)
		df_prefix[2]++;
	else if (file_count == 27)
		df_prefix = "dfA@";
	df_prefix[strlen(df_prefix) - 1]++;
	sprintf(dfname, "%s%03d%s", df_prefix, seqno, hostname);
	
	
	/*
	 * See if we need to make a temporary file
	 */
	if (dosfilter) {
		sprintf(tmp_dfname, "%s\\%s%03d.tmp", temp_dir, df_prefix, seqno);
		lpr_filter_dos(filename, tmp_dfname);
	} /* if */
	
	
	/*
	 * Add to control file
	 */
	if (width) fprintf(cf_file, "W%d\n", width);
	if (indent != -1) fprintf(cf_file, "I%d\n", indent);
	if (filter == 'p') fprintf(cf_file, "T%s\n", (title ? title : name));
	for(i = 0; i < copies; i++)
		fprintf(cf_file, "%c%s\n", filter, dfname);
	fprintf(cf_file, "U%s\n", dfname);
	fprintf(cf_file, "N%s\n", name);
	
	
	/*
	 * Open the file
	 */
	is_text = (filter == 'f' || filter == 'p' || filter == 'r' || dosfilter);
	fd = open(dosfilter ? tmp_dfname : filename, 
			  O_RDONLY | (is_text ? O_TEXT : O_BINARY));
	if (fd < 0)
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't open file, %s\n", filename);
	
	
	/*
	 * Try to send a file
	 */
	length = (is_text ? lpr_text_filelength(fd) : filelength(fd));
	sprintf(message, "%c%ld %s\n", LPD_RECEIVE_DATA_FILE, length, dfname);
	lpr_send(message, strlen(message));
	lpr_check_ack();
	
	
	/*
	 * Send the actual file
	 */
	if (debug) fprintf(stderr, "lpr: Sending %s to %s@%s.\n", name, printer, server);
	lpr_send_file(fd, length);
	close(fd);
	if (*tmp_dfname) {
		unlink(tmp_dfname);
		*tmp_dfname = 0;
	} /* if */
} /* lpr_print_file() */


void lpr_send_file(int fd, int length)
{
	int cnt;
	char buf[1024];
	
	/* 
	 * Send the file
	 */
	while(length) {
		cnt = min(length, 1024);
		if ((cnt = read(fd, buf, cnt)) <= 0)
			lpr_crash(REPORT_FILEERR, "lpr: Error reading file\n");
		
		lpr_send(buf, cnt);
		length -= cnt;
	} /* while */
	
	
	/*
	 * Send end mark & get acknowledgement
	 */
	sprintf(buf, "%c", LPD_END_TRANSFER);
	lpr_send(buf, 1);
	lpr_check_ack();
} /* lpr_send_file() */


void lpr_send(char *buf, int cnt)
{
	if (send(sockfd, buf, cnt, 0) != cnt)
		lpr_crash(REPORT_SOCKERR, "lpr: Error talking to server, %s\n", server);
} /* lpr_send() */


void lpr_check_ack()
{
	char	buf[80];
	int		len;
	
	while(1) {
		len = recv(sockfd, buf, 79, 0);
		if (len <= 0)
			lpr_crash(REPORT_SOCKERR, "lpr: Server not responding.\n");
		switch(buf[0]) {
		case LPD_OK:
			return;
		case LPD_ERROR:
			lpr_crash(NO_REPORT, "lpr: Server error\n");
		case LPD_NO_SPOOL_SPACE:
			lpr_crash(NO_REPORT, "lpr: Unable to accept job at this time.\n%s",
								 "Not enough spool space on server.\n");
		default:
			/* An error message? */
			buf[len] = 0;
			fprintf(stderr, "%s", buf);
		} /* switch */
	} /* while */
} /* check_ack() */


void lpr_create_control_file(char *tmp_cfname)
{
	/*
	 * Open the control file
	 */
	cf_file = fopen(tmp_cfname, "w+b");
	if (!cf_file)
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't open file, %s\n", tmp_cfname);
	
	
	/*
	 * Start building the control file
	 */
	fprintf(cf_file, "H%s\n", hostname);
	fprintf(cf_file, "P%s\n", username);
	if (!noburst) {
		if (job)
			fprintf(cf_file, "J%s\n", job);
		fprintf(cf_file, "C%s\n", class ? class : hostname);
		fprintf(cf_file, "L%s\n", username);
	} /* if */
} /* lpr_create_control_file() */


void lpr_capture_stdin(char *fname)
{
	char buf[1024];
	int  len;
	int  fd;
	
	if (debug) fprintf(stderr, "lpr: Capturing stdin to '%s'\n", fname);
	
	fd = open(fname, _O_CREAT | _O_WRONLY | _O_TRUNC | _O_BINARY, 0666);
	if (fd < 0)
		lpr_crash(NO_REPORT, "lpr: Error creating temporary file, %s\n", fname);
	
	while((len = read(0, buf, 1024)) > 0)
		if (_write(fd, buf, len) != len)
			lpr_crash(NO_REPORT, "lpr: Error writing to tmp file, %s\n", fname);

	close(fd);
	
} /* lpr_capture_stdin() */


int lpr_get_seqno(char *seq_fname)
{
	int seqno;
	int fd;
	int	cnt;
	char buf[80];
	
	/*
	 * Open the sequence file, if it doesn't exist, create it.
	 */
	if ((fd = _open(seq_fname, _O_RDWR | _O_CREAT | _O_TEXT)) < 0)
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't open (or create) sequence file (%s)\n",
				  seq_fname);
	
	
	/*
	 * Lock the file (the first byte of it anyway)
	 */
	if (_locking(fd, _LK_LOCK, 1))
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't lock sequence file, %s\n", seq_fname);


	/*
	 * Get the sequence number
	 */
	cnt = read(fd, buf, 79);
	buf[cnt] = 0;
	if (sscanf(buf, "%d", &seqno) != 1)
		seqno = 1;
	
	
	/*
	 * Write new sequence number
	 */
	_lseek(fd, 0, SEEK_SET);
	sprintf(buf, "%03d\n", (seqno + 1) % 1000);
	write(fd, buf, strlen(buf));

	
	/*
	 * Unlock & close file
	 */
	_lseek(fd, 0, SEEK_SET);
	_locking(fd, _LK_UNLCK, 1);	
	_close(fd);
	
	return (seqno);
} /* lpr_get_seqno() */


void lpr_crash(int report, char *fmt, ...)
{
	va_list	marker;
	
	if (fmt) {
		va_start(marker, fmt);
		vfprintf(stderr, fmt, marker);
		va_end(marker);
	} /* if */
	
	if (tcp_initialized) {
		if (sockfd != -1)	closesocket(sockfd);
							lp_tcp_shutdown();
		if (cf_file)		fclose(cf_file);
		if (*tmp_dfname)	unlink(tmp_dfname);
		if (*tmp_stdin_fname) unlink(tmp_stdin_fname);
	} /* if */
	
	exit(1);
} /* lpr_crash() */


long lpr_text_filelength(int fd)
{
	char	buf[1024];
	int		length = 0;
	int		cnt;
	
	/* Read the whole file */
	while((cnt = read(fd, buf, 1024)) > 0)
		length += cnt;
	
	/* Rewind to the beginning */
	lseek(fd, 0, SEEK_SET);

	return length;
} /* lpr_text_filelength() */


void lpr_filter_dos(char *infname, char *outfname)
{
	FILE	*in_file;
	FILE	*out_file;
	char	buf[1024];
	char	*ptr;
	
	if (debug) fprintf(stderr, "lpr: Filtering ^D's from '%s' to '%s'\n",
					   infname, outfname);
	
	in_file = fopen(infname, "rt");
	if (!in_file)
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't open file '%s'\n", infname);
	
	out_file = fopen(outfname, "wt");
	if (!out_file)
		lpr_crash(REPORT_FILEERR, "lpr: Couldn't create file '%s'\n", outfname);
	
	while(!feof(in_file)) {
		/* Try to get one line of text */
		fgets(ptr = buf, 1023, in_file);

		/* Exclude lines that begin with ^D or ^Z */
		if (buf[0] == 4 || buf[0] == 26)
			if (*++ptr == '\n')
				continue;
		
		while(ptr[0] && !feof(in_file)) {
			if (fputs(ptr, out_file) == EOF)
				lpr_crash(REPORT_FILEERR, "lpr: Couldn't write to file '%s'\n", 
						  outfname);
						  
			if (ptr[strlen(ptr) - 1] == '\n')
				break;
			
			fgets(ptr = buf, 1023, in_file);
		} /* while */
	} /* while */
	
	fclose(in_file);
	fclose(out_file);
} /* lpr_filter_dos() */

