1 /* vi: set sw=4 ts=4: */
5 * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
11 * A typical usage of BB lpd looks as follows:
12 * # tcpsvd -E 0 515 lpd SPOOLDIR [HELPER-PROG [ARGS...]]
14 * This means a network listener is started on port 515 (default for LP protocol).
15 * When a client connection is made (via lpr) lpd first change its working directory to SPOOLDIR.
17 * SPOOLDIR is the spool directory which contains printing queues
18 * and should have the following structure:
25 * <queueX> can be of two types:
26 * A. a printer character device or an ordinary file a link to such;
29 * In case A lpd just dumps the data it receives from client (lpr) to the
30 * end of queue file/device. This is non-spooling mode.
32 * In case B lpd enters spooling mode. It reliably saves client data along with control info
33 * in two unique files under the queue directory. These files are named dfAXXXHHHH and cfAXXXHHHH,
34 * where XXX is the job number and HHHH is the client hostname. Unless a printing helper application
35 * is specified lpd is done at this point.
37 * If HELPER-PROG (with optional arguments) is specified then lpd continues to process client data:
38 * 1. it reads and parses control file (cfA...). The parse process results in setting environment
39 * variables whose values were passed in control file; when parsing is complete, lpd deletes
41 * 2. it spawns specified helper application. It is then the helper application who is responsible
42 * for both actual printing and deleting processed data file.
44 * A good lpr passes control files which when parsed provide the following variables:
45 * $H = host which issues the job
46 * $P = user who prints
47 * $C = class of printing (what is printed on banner page)
48 * $J = the name of the job
49 * $L = print banner page
50 * $M = the user to whom a mail should be sent if a problem occurs
51 * $l = name of datafile ("dfAxxx") - file whose content are to be printed
53 * Thus, a typical helper can be something like this:
61 // strip argument of bad chars
62 static char *sane(char *str)
67 if (isalnum(*s) || '-' == *s) {
76 /* vfork() disables some optimizations. Moving its use
77 * to minimal, non-inlined function saves bytes */
78 static NOINLINE void vfork_close_stdio_and_exec(char **argv)
82 // we are the helper. we wanna be silent.
83 // this call reopens stdio fds to "/dev/null"
84 // (no daemonization is done)
85 bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL);
86 BB_EXECVP(*argv, argv);
91 static void exec_helper(const char *fname, char **argv)
98 file = q = xmalloc_open_read_close(fname, NULL);
99 // delete control file
101 // parse control file by "\n"
103 while ((p = strchr(q, '\n')) != NULL
105 && env_idx < ARRAY_SIZE(our_env)
108 // here q is a line of <SYM><VALUE>
109 // let us set environment string <SYM>=<VALUE>
110 // N.B. setenv is leaky!
111 // We have to use putenv(malloced_str),
112 // and unsetenv+free (in parent)
113 our_env[env_idx] = xasprintf("%c=%s", *q, q+1);
114 putenv(our_env[env_idx]);
121 vfork_close_stdio_and_exec(argv);
123 // PARENT (or vfork error)
125 while (--env_idx >= 0) {
126 *strchrnul(our_env[env_idx], '=') = '\0';
127 unsetenv(our_env[env_idx]);
131 static char *xmalloc_read_stdin(void)
133 size_t max = 4 * 1024; /* more than enough for commands! */
134 return xmalloc_reads(STDIN_FILENO, NULL, &max);
137 int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
138 int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[])
144 s = xmalloc_read_stdin();
146 // we understand only "receive job" command
149 printf("Command %02x %s\n",
150 (unsigned char)s[0], "is not supported");
154 // goto spool directory
158 // parse command: "\x2QUEUE_NAME\n"
160 *strchrnul(s, '\n') = '\0';
162 // protect against "/../" attacks
166 // queue is a directory -> chdir to it and enter spooling mode
167 spooling = chdir(queue) + 1; /* 0: cannot chdir, 1: done */
169 xdup2(STDOUT_FILENO, STDERR_FILENO);
174 // int is easier than ssize_t: can use xatoi_u,
175 // and can correctly display error returns (-1)
176 int expected_len, real_len;
179 write(STDOUT_FILENO, "", 1);
182 s = xmalloc_read_stdin();
184 return EXIT_SUCCESS; // probably EOF
185 // we understand only "control file" or "data file" cmds
186 if (2 != s[0] && 3 != s[0])
187 goto unsupported_cmd;
189 *strchrnul(s, '\n') = '\0';
190 // valid s must be of form: SUBCMD | LEN | SP | FNAME
191 // N.B. we bail out on any error
192 fname = strchr(s, ' ');
194 printf("Command %02x %s\n",
195 (unsigned char)s[0], "lacks filename");
200 // spooling mode: dump both files
201 // job in flight has mode 0200 "only writable"
202 fd = xopen3(sane(fname), O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200);
204 // non-spooling mode:
205 // 2: control file (ignoring), 3: data file
208 fd = xopen(queue, O_RDWR | O_APPEND);
210 expected_len = xatoi_u(s + 1);
211 real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len);
212 if (spooling && real_len != expected_len) {
213 unlink(fname); // don't keep corrupted files
214 printf("Expected %d but got %d bytes\n",
215 expected_len, real_len);
218 // chmod completely downloaded file as "readable+writable" ...
221 // ... and accumulate dump state.
222 // N.B. after all files are dumped spooling should be 1+2+3==6
225 close(fd); // NB: can do close(-1). Who cares?
227 // are all files dumped? -> spawn spool helper
228 if (6 == spooling && *argv) {
229 fname[0] = 'c'; // pass control file name
230 exec_helper(fname, argv);
232 // get ACK and see whether it is NUL (ok)
233 if (read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) {
234 // don't send error msg to peer - it obviously
235 // don't follow the protocol, so probably
236 // it can't understand us either