/* -------------------------------------------------------------------
lpr - line printer
Used to print files on remote printers using the LPD protocol.
Built on top of the NCSA TCP/IP package (version 2.2tn for MS-DOS).
Paul Hilchey May 1989
Copyright (C) 1989 The University of British Columbia
All rights reserved.
history:
8/17/89 Relax option parsing to allow spaces
8/21/89 Expand wildcards in filenames
10/23/89 Default title to filename with -p format
1/6/90 Microsoft Port by Heeren Pathak (NCSA)
-------------------------------------------------------------------
*/
#ifdef __TURBOC__
#include "turboc.h"
#include <dir.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dos.h>
#include <stdarg.h>
#include <io.h>
#include <time.h>
#ifdef MSC
#define EXIT_SUCCESS 0
#ifndef __TURBOC__
#define ffblk find_t
#define ff_name name
#define findnext _dos_findnext
#define findfirst(path,fileblock,attribute) _dos_findfirst((path),(attribute),(fileblock))
#endif
#include <signal.h>
#ifndef __TURBOC__
#include <direct.h>
#endif
#include <malloc.h>
#else
#include <dir.h>
#endif
#define WINMASTER
#ifdef MEMORY_DEBUG
#include "memdebug.h"
#endif
#include "whatami.h"
#include "hostform.h"
#include "windat.h"
#include "lp.h"
#include "externs.h"
#define MAX_COPIES 10
#define DEFAULT_INDENT 8
#define CONTROL_FILE_SIZE 1024 /* max size in bytes */
/* Function prototypes */
void main(int argc,char * *argv);
static void start_protocol(char *host,char *rname);
static void finish_protocol(void );
static void print_file(char *filename);
static void print_one_file(char *filename);
static void send_file(int connection_id,FILE *data_file,char *spool_name,int is_text);
static void check_ack(int connection_id);
static long get_size_lpr(FILE *data_file,int is_text);
#ifndef __TURBOC__
static void randomize(void );
#endif
/* global variables */
char *remote_name = NULL, /* printer name on remote system */
*remote_host = NULL, /* address of remote host */
*class = NULL, /* job classification */
*job = NULL, /* job name */
*title = NULL, /* job title */
filter = 'f'; /* default filter */
int copies = 1, /* number of copies */
indent = -1, /* indent, -1 means not set */
width = 0, /* width, 0 means not set */
noburst = 0; /* 1 = skip burst page */
int connection_id;
char control_file[CONTROL_FILE_SIZE]; /* control file to be sent after data files, built in a string */
int cf_length = 0; /* current length of control_file */
int sequence_number; /* sequence number for spooled file names */
struct config *cp; /* configuration information */
char username[9]; /* name of user */
int debug = 0; /* 1 = print debugging info; set with -D option */
int ftppassword, /* not used; just to avoid unresolved external */
bypass_passwd=0; /* whether to bypass the password check */
unsigned char path_name[_MAX_DRIVE+_MAX_DIR], /* character storage for the path name */
temp_str[20],s[_MAX_DIR],temp_data[30];
/****************************************************************
* Main program. *
* lpr [option ...] file1 ... *
****************************************************************/
void main(int argc,char *argv[])
{
int i,first_time, temp;
char *ptr;
#ifdef __TURBOC__
fnsplit(argv[0],path_name,s,temp_str,temp_data); /* split the full path name of telbin.exe into it's components */
#else
_splitpath(argv[0],path_name,s,temp_str,temp_data); /* split the full path name of telbin.exe into it's components */
#endif
strcat(path_name,s); /* append the real path name to the drive specifier */
#if defined(MSC) && !defined(__TURBOC__)
signal(SIGINT,breakstop); /* Microsoft intercept of break */
#else
ctrlbrk(breakstop); /* set up ctrl-c handler */
#endif
/* Do session initialization. Snetinit reads config file. */
ptr = getenv("CONFIG.TEL");
if(ptr!=NULL)
Shostfile(ptr);
if(argc>1) {
if(!(strcmp(argv[1],"-h")))
Shostfile(argv[2]);
}
if(i=Snetinit()) {
if(i==-3) /* check for BOOTP server not responding */
netshut(); /* release network */
crash("network initialization failed.");
} /* end if */
first_time = 1; /* reset once we've found something to print */
/* select default printer */
remote_name = getenv("PRINTER");
#ifdef AUX /* RMG */
fprintf(stdaux, " remote printer name is %s ",remote_name);
#endif
if (remote_name == NULL) remote_name = DEFAULT_PRINTER;
#ifdef AUX /* RMG */
fprintf(stdaux, " remote printer name set to %s ",remote_name);
#endif
remote_host = getenv("SERVER");
/* Files sent to the remote system need to have a name of the
form df<letter><sequence number><host name> (data files)
or cf<letter><sequence number><host name> (control files).
It isn't convenient for to keep track of the sequence number
from when lpr was last used, so we just use a random number.
It is unlikely but possible that we could get into trouble
doing this. */
randomize();
sequence_number = rand() % 1000;
/* get info from configuration file */
cp = (struct config *)malloc(sizeof(struct config));
Sgetconfig(cp);
/* check that the machine name was set in the configuration file */
if (0 == strlen(cp->me)) crash("`myname' not set in config file.");
/* set user name. use first part of machine name if nothing else. */
ptr = getenv("USER");
if (NULL != ptr) {
strncpy(username,ptr,8);
username[8]='\0';
}
else {
i = min(strcspn(cp->me,"."),sizeof(username)-1);
strncpy(username,cp->me,i);
username[i]='\0';
}
/* Loop through command line arguments */
for (i=1; i<argc; ++i)
if (argv[i][0] == '-') /* setting an option */
switch(argv[i][1]) {
case '#': /* set number of copies */
if (argv[i][2])
temp = atoi(&argv[i][2]);
else if (i+1 < argc)
temp = atoi(argv[++i]);
else
temp = 1;
if (temp < 1 || temp > MAX_COPIES) {
fprintf(stderr,"Unreasonable number of copies requested. Reset to 1.\n");
temp = 1;
}
copies = temp;
break;
case 'P': /* set printer name */
if (argv[i][2])
remote_name = &argv[i][2];
else if (i+1 < argc)
remote_name = argv[++i];
break;
case 'S': /* select server */
if (argv[i][2])
remote_host = &argv[i][2];
else if (i+1 < argc)
remote_host = argv[++i];
break;
case 'C': /* set job classification */
if (argv[i][2])
class = &argv[i][2];
else if (i+1 < argc)
class = argv[++i];
break;
case 'J': /* set job name */
if (argv[i][2])
job = &argv[i][2];
else if (i+1 < argc)
job = argv[++i];
break;
case 'T': /* set job title */
if (argv[i][2])
title = &argv[i][2];
else if (i+1 < argc)
title = argv[++i];
break;
case 'i': /* set indent */
temp = argv[i][2] ? atoi(&argv[i][2]) : DEFAULT_INDENT;
if (temp < -1 || temp > 255)
fprintf(stderr,"Unreasonable indent requested. Indent not changed.\n");
else
indent = temp;
break;
case 'w': /* set page width */
if (argv[i][2])
temp = atoi(&argv[i][2]);
else if (i+1 < argc)
temp = atoi(argv[++i]);
else
break;
if (temp < 0 || temp > 512)
fprintf(stderr,"Unreasonable page width requested. Width not changed.\n");
else
width = temp;
break;
case 'p': /* pr */
case 'v': /* raster */
case 'c': /* cifplot */
case 'g': /* graph */
case 'd': /* dvi */
case 'n': /* ditroff */
case 't': /* troff */
case 'l': /* long (with control characters) */
filter = argv[i][1];
break;
case 'f': /* fortran */
filter = 'r'; /* f selects filter r */
break;
case 'h': /* suppress burst page */
noburst = 1;
break;
case 'D': /* turn debugging output on */
debug = 1;
break;
default:
fprintf (stderr,"Unrecognized option: %s ignored.\n",argv[i]);
break;
}
else { /* name of file to print */
if(!n_findfirst(argv[i],0)) { /* check whether the file exists before trying to print it */
if (first_time) { /* open connection on first job */
if (NULL == remote_host) crash("server not specified.");
if (NULL == job) job=argv[i]; /* default job name */
start_protocol(remote_host,remote_name);
first_time = 0;
}
print_file(argv[i]);
} /* end if */
else {
fprintf (stderr,"Cannot access: %s.\n",argv[i]);
} /* end else */
}
if (first_time) {
puts("Name: lpr - send a job to a remote printer; version 1.3");
puts("Usage: lpr [ options ... ] file1 ... ");
puts("Options are:");
puts(" -P<printer> -S<server> -C<class> -J<job>");
puts(" -T<title> -i<indent> -w<width> -#<copies>");
puts(" -p -l -d -f");
puts(" -h");
}
else {
finish_protocol();
puts("Done.");
}
netshut();
exit(EXIT_SUCCESS);
}
/*****************************************************************
* start_protocol *
* Open the TCP connection and start building the control file. *
* Aborts with an error message if someting goes wrong. *
* parameters: null terminated name or IP address of the server *
* name of the printer on the server *
*****************************************************************/
static void start_protocol(char *host,char *rname)
{
struct machinfo *server_info_record;
server_info_record = lookup(host);
if (0 == server_info_record) crash("domain lookup failed for %s.",host);
netfromport(rand() % 1023); /* source should be a privileged port */
connection_id = open_connection(server_info_record, rand() % MAX_PRIV_PORT, PRINTER_PORT);
if (0 > connection_id)
crash("unable to open connection.");
/* Tell LPD to receive a job. */
nprintf(connection_id,"%c%s\n", LPD_PRINT_JOB, rname);
if (debug) puts("told LPD to receive job, awaiting ack");
check_ack(connection_id);
cf_length = 0;
cf_length += sprintf(control_file+cf_length,"H%s\n",cp->me);
cf_length += sprintf(control_file+cf_length,"P%s\n",username);
#ifdef OLD_WAY
if (job) cf_length += sprintf(control_file+cf_length,"J%s\n",job);
cf_length += sprintf(control_file+cf_length,"C%s\n",class ? class : cp->me);
cf_length += sprintf(control_file+cf_length,"L%s\n",username);
#else
if(!noburst) {
if (job)
cf_length += sprintf(control_file+cf_length,"J%s\n",job);
cf_length += sprintf(control_file+cf_length,"C%s\n",class ? class : cp->me);
cf_length += sprintf(control_file+cf_length,"L%s\n",username);
} /* end if */
#endif
}
/******************************************************************
* finish_protocol *
* Called after last data file has been sent. Sends the control *
* file then closes the network connection. *
******************************************************************/
static void finish_protocol(void )
{
nprintf(connection_id,"%c%d cfA%03d%s\n", LPD_RECEIVE_CONTROL_FILE, cf_length,
sequence_number, cp->me);
if(debug)
puts("Told LPD we're sending control file, awaiting ack.");
check_ack(connection_id);
netwrite(connection_id,control_file,cf_length);
nprintf(connection_id,"%c",LPD_END_TRANSFER);
if(debug)
puts("Sent control file, awaiting ack.");
check_ack(connection_id);
netclose(connection_id);
}
/**************************************************************** *
* print_file *
* Changes any forward slashes in the filename to backwards ones, *
* and then expands any wildcards before calling print_one_file. *
******************************************************************/
static void print_file(char *filename)
{
int done;
struct ffblk ffblk;
char *p;
char *start_of_name;
char *expanded_name;
/* change forward slashes in filenames to suit backwards MS-DOS */
p=filename;
while (*p != '\0') {
if (*p == '/') *p = '\\';
p++;
}
/* make a copy of the path portion of the filename with room
to substitute any expanded wildcard filenames at the end */
expanded_name = (char *)malloc(strlen(filename)+13);
strcpy(expanded_name,filename);
start_of_name = strrchr(expanded_name,'\\');
#ifdef OLD_WAY
if (start_of_name == NULL)
start_of_name = expanded_name;
else
start_of_name++;
#else
if (start_of_name == NULL)
if((start_of_name =strrchr(expanded_name,':'))!=NULL) /* skip name past drive specifier */
start_of_name++;
else
start_of_name=expanded_name;
else
start_of_name++;
#endif
/* expand wildcards */
done = findfirst(filename, &ffblk, 0);
if (done)
fprintf(stderr,"%s doesn't exist.\n",filename);
else
do {
strcpy(start_of_name,ffblk.ff_name);
print_one_file(expanded_name);
} while (0 == findnext(&ffblk));
free(expanded_name);
}
/******************************************************************
* Print a file *
* Adds records to the control file as appropriate for the *
* various option settings, then sends the data file. *
******************************************************************/
static void print_one_file(char *filename)
{
char spool_file_name[40];
int is_text,i;
FILE *data_file;
if (debug) printf("printing file %s\n",filename);
/* for `non-binary' filters, we do a little bit of filtering beforehand */
#ifdef OLD_WAY
is_text = (filter == 'f' || filter == 'l' || filter == 'p' || filter == 'r');
#else
is_text = (filter == 'f' || filter == 'p' || filter == 'r');
#endif
/* open the file in the appropriate mode (text or binary) */
if (is_text)
data_file = fopen(filename,"rt");
else
data_file = fopen(filename,"rb");
if (data_file == NULL) {
fprintf(stderr,"Trouble opening file %s.\n",filename);
return;
}
printf("Sending %s ",filename);
/* make up name for spooled data file */
sequence_number = (sequence_number + 1) % 1000;
sprintf(spool_file_name,"dfA%03d%s",sequence_number,cp->me);
/* add records in control file describing job */
if (width != 0) cf_length += sprintf(control_file+cf_length,"W%d\n",width);
if (indent != -1) cf_length += sprintf(control_file+cf_length,"I%d\n",indent);
if (filter == 'p') cf_length += sprintf(control_file+cf_length,"T%s\n", (title != NULL) ? title : filename);
for (i=1; i <= copies; i++)
cf_length += sprintf(control_file+cf_length,"%c%s\n",filter,spool_file_name);
cf_length += sprintf(control_file+cf_length,"U%s\n",spool_file_name);
cf_length += sprintf(control_file+cf_length,"N%s\n",filename);
send_file(connection_id, data_file, spool_file_name, is_text);
}
/****************************************************************
* send_file *
* Does the actual work of sending the file to the daemon. *
* parameters: the connection id, as returned from Snetopen *
* a file handle for the data file to be sent *
* the name of the spool file on the remote system *
* a flag indicating if this is a text file *
****************************************************************/
static void send_file(int connection_id, FILE *data_file, char *spool_name, int is_text)
{
/* char buf[1024]; */
char *buf;
long length, step;
int xp, towrite, i;
int file_handle;
if ((buf = (char *)malloc(1024)) == NULL) crash("Out of heap...");
length = get_size_lpr(data_file, is_text);
nprintf(connection_id, "%c%ld %s\n", LPD_RECEIVE_DATA_FILE, length, spool_name);
check_ack(connection_id);
file_handle = fileno(data_file);
towrite = xp = 0;
step = max(length / 5, 1); /* print one dot for each 1/5 of the file sent */
while(1) {
if (towrite <= xp) {
towrite = read(file_handle, buf, 1024);
if (towrite == 0) break;
xp = 0;
}
i = netwrite(connection_id,&buf[xp], towrite-xp);
if (debug && i) printf("send_file sent %d bytes\n",i);
Stask();
checkerr();
if (i < 0) crash("transfer failed.");
if (i > 0) {
int j;
/* print out dots indicating how much sent */
for (j=1; j<=(int)(((length+step-5L)/step - (length+step-(long)i-5L)/step)); j++)
putchar('.');
xp += i;
length -= i;
}
}
putchar('\n');
if (length != 0)
crash ("file length discrepancy.");
/* send end mark */
nprintf(connection_id, "%c", LPD_END_TRANSFER);
check_ack(connection_id);
}
/************************************************************************
* Return the size of a file. For text files, we have to read through *
* the whole thing, as we are going to send LF as line terminator, *
* rather than CRLF. *
************************************************************************/
static long get_size_lpr(FILE *data_file, int is_text)
{
char buf[1024];
long total;
int handle;
int count;
handle = fileno(data_file);
if (is_text) {
total = 0;
while ((count = read(handle, buf, 1024)) > 0)
total += count;
rewind(data_file);
return(total);
}
else
return(filelength(handle));
}
/*********************************************************
* Wait for a one character reply from LPD. Abort with *
* an error message if it not an OK acknowledgement. *
*********************************************************/
static void check_ack(int connection_id)
{
char ack_buff[80];
int len;
while(1) {
len = nread(connection_id, ack_buff, 80);
if (len <= 0)
crash("connection closed by server.");
switch (ack_buff[0]) {
case LPD_OK :
return;
case LPD_ERROR :
crash("error code from server.");
case LPD_NO_SPOOL_SPACE :
crash("server unable to accept job at this time.");
default : /* may be an error message */
fprintf(stderr,"%.*s",len,ack_buff);
}
}
}
#if defined(MSC) && !defined(__TURBOC__)
/******************************************************************
*
* randomize()
*
* replicates the randomize function of Turbo C
* MSC 5.1 does not contain it so we have to write it ourselves.
*
*/
static void randomize(void )
{
srand((unsigned)time(NULL));
}
#endif