/* * Send files to a Berkeley (BSD) line printer daemon. * This is done using a TCP connection, following the protocol * inherent (and undocumented) in the BSD printer daemon (/usr/lib/lpd). * * This program differs from the normal BSD lpr(1) command in the * following ways. The BSD lpr command writes the files to be * printed into the spooling directory, and then notifies the * line printer daemon (lpd) that the files are there (using a * UNIX domain socket message). The lpd program then sees that * the files get printed at some time. If the files are to be * printed on another host's line printer, then the printer daemon * will contact the daemon on the other host (using an Internet * TCP socket) and will transfer the file to the other daemon, * who will then see to it that the file gets printed. * This program, however, acts like a UNIX printer daemon (lpd) * as we assume the files are to be printed on another system. * We go ahead and contact the UNIX lpd program on the remote host * (using an Internet TCP socket) and send the files to that * daemon for printing. * * There are three functions in this file that main() calls: * send_start() - called once, before the first file * send_file() - called for every file that can be opened by main * send_done() - called at the end */ #include "defs.h" #include "systype.h" #include #include /* * Variables specific to this file. */ static FILE *cfp; /* file pointer for control file */ static long cfilesize; /* size of cfile, in bytes */ static char myhostname[MAXHOSTNAMELEN]; /* name of host running lpr */ static char username[MAXHOSTNAMELEN]; /* name of user running lpr */ static char cfname[MAXFILENAME]; /* name of "cf" file */ static char dfname[MAXFILENAME]; /* name of "df" file */ static char buf[MAXLINE]; /* temp buffer */ static int sockfd; /* network connection */ static int seqnum; /* seq#, set to same value for now */ long get_size(); /* * Start things up. */ send_start() { register int uid; struct passwd *pw, *getpwuid(); DEBUG2("send_start: host = %s, printer = %s", hostname, printername); /* we need a reserved port */ if ( (sockfd = tcp_open(hostname, LPR_SERVICE, -1)) < 0) err_quit("can't connect to service: %s on host: %s", LPR_SERVICE, hostname); /* * If we got the reserved port, then we're either running as * root, or the program is set-user-ID root. In the latter case, * the only reason we need to be set-user-ID root is to bind the * reserved port, so we can now go back to being the "real" * user who executed this program. We really need to do * this anyway, to assure we can't read files as root * that the user doesn't have normal access to. */ setuid(getuid()); /* * Get the name of the local host. */ if (gethostname(myhostname, MAXHOSTNAMELEN) < 0) err_dump("gethostname error"); /* * Get the name of the user executing this program. */ uid = getuid(); if ( (pw = getpwuid(uid)) == NULL) err_quit("getpwuid failed, uid = %d; who are you", uid); strcpy(username, pw->pw_name); /* * We must insert a 3-digit sequence number into the filenames * that we're creating to send to the server. This is to * distinguish between successive files that are sent to the * same server for the same printer. */ seqnum = get_seqno(); sprintf(cfname, "cfA%03d%s", seqnum, myhostname); sprintf(dfname, "dfA%03d%s", seqnum, myhostname); DEBUG2("cfname = %s, dfname = %s", cfname, dfname); /* * Create the control file and open it for writing. */ if ( (cfp = fopen(cfname, "w")) == NULL) err_dump("can't open control file: %s for writing", cfname); cfilesize = 0L; /* * Initialize the control file by inserting the following lines: * * H (host on which the lpr was executed) * P (person executing the lpr) * J (for the banner page) * C (for the banner page) * L (literal value for banner page) * * Then, for each file to be printed, send_file() will add * the following three lines: * * f (name of text file to print) * U (to unlink the file after printing) * N (real name of the file, used by lpq) * * The is of the form "df[A-Z]nnn" where * "nnn" is the 3-digit sequence number from this host, * and "" is the name of the host on which the lpr * was executed. */ add_H(); add_P(); add_C(); add_L(); /* * Send a line to the print server telling it we want * to send it some files to print, and specifying the * printer to be used. */ sprintf(buf, "%c%s\n", '\002', printername); if (writen(sockfd, buf, strlen(buf)) != strlen(buf)) err_dump("writen error"); if (readn(sockfd, buf, 1) != 1) err_dump("readn error"); if (buf[0] != '\0') { if (readline(sockfd, &buf[1], MAXLINE-1) > 0) err_quit("error, server returned: %s", buf); else err_dump("didn't get ACK from server, got 0x%02x", buf[0]); } } /* * Send a single file. * This function is called by main once for every file to be printed. * Main has already opened the file for reading, but it still passes * us the actual filename from the command line, so that we can * use the filename for identifying the file to the server. */ send_file(filename, fp) char *filename; /* filename from command line, or "-stdin" */ FILE *fp; /* file pointer on which file is open for reading */ { static int filecount = 0; register char *ptr; char *rindex(); /* * We don't currently handle standard input. To do so requires * that we copy stdin to a temporary file, so that we can get * its size in bytes. We have to know the file's size before * we send it to the server. */ if (strcmp(filename, "-stdin") == 0) { err_ret("can't currently print standard input"); return; } filecount++; /* * First strip any leading directory names off the filename. * This is to get the base filename for the job banner. */ if ( (ptr = rindex(filename, '/')) != NULL) { filename = ptr + 1; } /* * If this is the first file, set the Job Class on the banner * page to the filename. */ if (filecount == 1) add_J(filename); else dfname[2]++; /* A, B, C, ... */ /* * Add the 'f', 'U' and 'N' lines to the control file. */ add_f(dfname); add_U(dfname); add_N(filename); DEBUG2("send_file: %s, dfname = %s", filename, dfname); xmit_file(filename, fp, dfname, '\003'); /* transmit file across network */ } /* * All done with the user's files. * Now we must transmit the control file that we've been building * to the other side. */ send_done() { fclose(cfp); if ( (cfp = fopen(cfname, "r")) == NULL) err_dump("can't reopen cfile for reading"); xmit_file("-cfile", cfp, cfname, '\002'); fclose(cfp); /* * We're done with the control file, so delete it. * (Don't unlink if debugflag is 1, assuming we're debugging.) */ if (debugflag == 0 && unlink(cfname) < 0) err_dump("can't unlink control file: %s", cfname); close(sockfd); } /* * Transmit one file to the server. * This routine is used to send both the actual text files (data files) * that the user wants printed, and to send the control file (cfile) * that we build up as we send the data files. * The only difference between transmitting these two types of files * is the first byte of the transmission (002 for the cfile and 003 * for the dfiles). */ xmit_file(filename, fp, fname, xmittype) char *filename; /* name from command line, or "-stdin" or "-cfile" */ FILE *fp; char *fname; /* the cfname or dfname */ char xmittype; /* '\002' or '\003' */ { register long size; /* * We have to get the exact size of the file in bytes * to send to the server, so that it knows how much * data to read from the net. */ size = get_size(filename, fp); DEBUG2("xmit_file: %s, size = %ld", filename, size); /* * Send a line to the print server giving the type of * file, the exact size of the file in bytes, * and the name of the file (its dfname, not its actual * name). */ sprintf(buf, "%c%ld %s\n", xmittype, size, fname); if (writen(sockfd, buf, strlen(buf)) != strlen(buf)) err_dump("writen error"); if (readn(sockfd, buf, 1) != 1) err_dump("readn error"); if (buf[0] != '\0') err_dump("didn't get an ACK from server, got 0x%02x", buf[0]); /* * Now send the actual file itself. */ copyfile(fp); /* * Write a byte of zero to the server, and wait for * a byte of zero to be returned from the server, * telling us all is OK (I'm OK, you're OK). */ if (writen(sockfd, "", 1) != 1) err_dump("writen error"); if (readn(sockfd, buf, 1) != 1) err_dump("readn error"); if (buf[0] != '\0') err_dump("didn't get an ACK from server, got 0x%02x", buf[0]); } /* * Copy a file to the network. * We read the file using standard i/o, one line at a time, * and write the data to the network one line at a time. */ copyfile(fp) FILE *fp; { register int len; char line[MAXLINE]; while (fgets(line, MAXLINE, fp) != NULL) { len = strlen(line); if (writen(sockfd, line, len) != len) err_dump("writen error"); } if (ferror(fp)) err_dump("read error from fgets"); } /* * Determine the exact size of a file. * Under UNIX this is easy - we just call the fstat() system call. * Under other operating systems it is harder, since they may not use * exactly one character to represent a newline. */ long get_size(filename, fp) char *filename; FILE *fp; { struct stat statbuff; if (fstat(fileno(fp), &statbuff) < 0) err_dump("can't fstat"); return(statbuff.st_size); } add_H() { fprintf(cfp, "H%s\n", myhostname); cfilesize += strlen(myhostname) + 2; } add_P() { fprintf(cfp, "P%s\n", username); cfilesize += strlen(username) + 2; } /* * We add the Job Class when the first file is processed. */ add_J(filename) char *filename; { fprintf(cfp, "J%s\n", filename); cfilesize += strlen(filename) + 2; } add_C() { fprintf(cfp, "C%s\n", myhostname); cfilesize += strlen(myhostname) + 2; /* just use this host's name */ } add_L() { fprintf(cfp, "L%s\n", username); cfilesize += strlen(username) + 2; } add_f(dfname) char *dfname; { fprintf(cfp, "f%s\n", dfname); cfilesize += strlen(dfname) + 2; } add_U(dfname) char *dfname; { fprintf(cfp, "U%s\n", dfname); cfilesize += strlen(dfname) + 2; } add_N(filename) char *filename; { fprintf(cfp, "N%s\n", filename); cfilesize += strlen(filename) + 2; }