1/* vi: set sw=4 ts=4: */ 2/* 3 * bare bones version of lpr & lpq: BSD printing utilities 4 * 5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com> 6 * 7 * Original idea and code: 8 * Walter Harms <WHarms@bfs.de> 9 * 10 * Licensed under GPLv2, see file LICENSE in this tarball for details. 11 * 12 * See RFC 1179 for protocol description. 13 */ 14#include "libbb.h" 15 16/* 17 * LPD returns binary 0 on success. 18 * Otherwise it returns error message. 19 */ 20static void get_response_or_say_and_die(int fd, const char *errmsg) 21{ 22 ssize_t sz; 23 char buf[128]; 24 25 buf[0] = ' '; 26 sz = safe_read(fd, buf, 1); 27 if ('\0' != buf[0]) { 28 // request has failed 29 // try to make sure last char is '\n', but do not add 30 // superfluous one 31 sz = full_read(fd, buf + 1, 126); 32 bb_error_msg("error while %s%s", errmsg, 33 (sz > 0 ? ". Server said:" : "")); 34 if (sz > 0) { 35 // sz = (bytes in buf) - 1 36 if (buf[sz] != '\n') 37 buf[++sz] = '\n'; 38 safe_write(STDERR_FILENO, buf, sz + 1); 39 } 40 xfunc_die(); 41 } 42} 43 44int lpqr_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; 45int lpqr_main(int argc UNUSED_PARAM, char *argv[]) 46{ 47 enum { 48 OPT_P = 1 << 0, // -P queue[@host[:port]]. If no -P is given use $PRINTER, then "lp@localhost:515" 49 OPT_U = 1 << 1, // -U username 50 51 LPR_V = 1 << 2, // -V: be verbose 52 LPR_h = 1 << 3, // -h: want banner printed 53 LPR_C = 1 << 4, // -C class: job "class" (? supposedly printed on banner) 54 LPR_J = 1 << 5, // -J title: the job title for the banner page 55 LPR_m = 1 << 6, // -m: send mail back to user 56 57 LPQ_SHORT_FMT = 1 << 2, // -s: short listing format 58 LPQ_DELETE = 1 << 3, // -d: delete job(s) 59 LPQ_FORCE = 1 << 4, // -f: force waiting job(s) to be printed 60 }; 61 char tempfile[sizeof("/tmp/lprXXXXXX")]; 62 const char *job_title; 63 const char *printer_class = ""; // printer class, max 32 char 64 const char *queue; // name of printer queue 65 const char *server = "localhost"; // server[:port] of printer queue 66 char *hostname; 67 // N.B. IMHO getenv("USER") can be way easily spoofed! 68 const char *user = xuid2uname(getuid()); 69 unsigned job; 70 unsigned opts; 71 int fd; 72 73 // parse options 74 // TODO: set opt_complementary: s,d,f are mutually exclusive 75 opts = getopt32(argv, 76 (/*lp*/'r' == applet_name[2]) ? "P:U:VhC:J:m" : "P:U:sdf" 77 , &queue, &user 78 , &printer_class, &job_title 79 ); 80 argv += optind; 81 82 // if queue is not specified -> use $PRINTER 83 if (!(opts & OPT_P)) 84 queue = getenv("PRINTER"); 85 // if queue is still not specified -> 86 if (!queue) { 87 // ... queue defaults to "lp" 88 // server defaults to "localhost" 89 queue = "lp"; 90 // if queue is specified -> 91 } else { 92 // queue name is to the left of '@' 93 char *s = strchr(queue, '@'); 94 if (s) { 95 // server name is to the right of '@' 96 *s = '\0'; 97 server = s + 1; 98 } 99 } 100 101 // do connect 102 fd = create_and_connect_stream_or_die(server, 515); 103 104 // 105 // LPQ ------------------------ 106 // 107 if (/*lp*/'q' == applet_name[2]) { 108 char cmd; 109 // force printing of every job still in queue 110 if (opts & LPQ_FORCE) { 111 cmd = 1; 112 goto command; 113 // delete job(s) 114 } else if (opts & LPQ_DELETE) { 115 fdprintf(fd, "\x5" "%s %s", queue, user); 116 while (*argv) { 117 fdprintf(fd, " %s", *argv++); 118 } 119 bb_putchar('\n'); 120 // dump current jobs status 121 // N.B. periodical polling should be achieved 122 // via "watch -n delay lpq" 123 // They say it's the UNIX-way :) 124 } else { 125 cmd = (opts & LPQ_SHORT_FMT) ? 3 : 4; 126 command: 127 fdprintf(fd, "%c" "%s\n", cmd, queue); 128 bb_copyfd_eof(fd, STDOUT_FILENO); 129 } 130 131 return EXIT_SUCCESS; 132 } 133 134 // 135 // LPR ------------------------ 136 // 137 if (opts & LPR_V) 138 bb_error_msg("connected to server"); 139 140 job = getpid() % 1000; 141 hostname = safe_gethostname(); 142 143 // no files given on command line? -> use stdin 144 if (!*argv) 145 *--argv = (char *)"-"; 146 147 fdprintf(fd, "\x2" "%s\n", queue); 148 get_response_or_say_and_die(fd, "setting queue"); 149 150 // process files 151 do { 152 unsigned cflen; 153 int dfd; 154 struct stat st; 155 char *c; 156 char *remote_filename; 157 char *controlfile; 158 159 // if data file is stdin, we need to dump it first 160 if (LONE_DASH(*argv)) { 161 strcpy(tempfile, "/tmp/lprXXXXXX"); 162 dfd = mkstemp(tempfile); 163 if (dfd < 0) 164 bb_perror_msg_and_die("mkstemp"); 165 bb_copyfd_eof(STDIN_FILENO, dfd); 166 xlseek(dfd, 0, SEEK_SET); 167 *argv = (char*)bb_msg_standard_input; 168 } else { 169 dfd = xopen(*argv, O_RDONLY); 170 } 171 172 /* "The name ... should start with ASCII "cfA", 173 * followed by a three digit job number, followed 174 * by the host name which has constructed the file." 175 * We supply 'c' or 'd' as needed for control/data file. */ 176 remote_filename = xasprintf("fA%03u%s", job, hostname); 177 178 // create control file 179 // TODO: all lines but 2 last are constants! How we can use this fact? 180 controlfile = xasprintf( 181 "H" "%.32s\n" "P" "%.32s\n" /* H HOST, P USER */ 182 "C" "%.32s\n" /* C CLASS - printed on banner page (if L cmd is also given) */ 183 "J" "%.99s\n" /* J JOBNAME */ 184 /* "class name for banner page and job name 185 * for banner page commands must precede L command" */ 186 "L" "%.32s\n" /* L USER - print banner page, with given user's name */ 187 "M" "%.32s\n" /* M WHOM_TO_MAIL */ 188 "l" "d%.31s\n" /* l DATA_FILE_NAME ("dfAxxx") */ 189 , hostname, user 190 , printer_class /* can be "" */ 191 , ((opts & LPR_J) ? job_title : *argv) 192 , (opts & LPR_h) ? user : "" 193 , (opts & LPR_m) ? user : "" 194 , remote_filename 195 ); 196 // delete possible "\nX\n" patterns 197 c = controlfile; 198 cflen = (unsigned)strlen(controlfile); 199 while ((c = strchr(c, '\n')) != NULL) { 200 if (c[1] && c[2] == '\n') { 201 /* can't use strcpy, results are undefined */ 202 memmove(c, c+2, cflen - (c-controlfile) - 1); 203 cflen -= 2; 204 } else { 205 c++; 206 } 207 } 208 209 // send control file 210 if (opts & LPR_V) 211 bb_error_msg("sending control file"); 212 /* "Acknowledgement processing must occur as usual 213 * after the command is sent." */ 214 fdprintf(fd, "\x2" "%u c%s\n", cflen, remote_filename); 215 get_response_or_say_and_die(fd, "sending control file"); 216 /* "Once all of the contents have 217 * been delivered, an octet of zero bits is sent as 218 * an indication that the file being sent is complete. 219 * A second level of acknowledgement processing 220 * must occur at this point." */ 221 full_write(fd, controlfile, cflen + 1); /* writes NUL byte too */ 222 get_response_or_say_and_die(fd, "sending control file"); 223 224 // send data file, with name "dfaXXX" 225 if (opts & LPR_V) 226 bb_error_msg("sending data file"); 227 st.st_size = 0; /* paranoia: fstat may theoretically fail */ 228 fstat(dfd, &st); 229 fdprintf(fd, "\x3" "%"OFF_FMT"u d%s\n", st.st_size, remote_filename); 230 get_response_or_say_and_die(fd, "sending data file"); 231 if (bb_copyfd_size(dfd, fd, st.st_size) != st.st_size) { 232 // We're screwed. We sent less bytes than we advertised. 233 bb_error_msg_and_die("local file changed size?!"); 234 } 235 write(fd, "", 1); // send ACK 236 get_response_or_say_and_die(fd, "sending data file"); 237 238 // delete temporary file if we dumped stdin 239 if (*argv == (char*)bb_msg_standard_input) 240 unlink(tempfile); 241 242 // cleanup 243 close(fd); 244 free(remote_filename); 245 free(controlfile); 246 247 // say job accepted 248 if (opts & LPR_V) 249 bb_error_msg("job accepted"); 250 251 // next, please! 252 job = (job + 1) % 1000; 253 } while (*++argv); 254 255 return EXIT_SUCCESS; 256} 257