diff options
Diffstat (limited to 'printutils/lpd.c')
-rw-r--r-- | printutils/lpd.c | 148 |
1 files changed, 92 insertions, 56 deletions
diff --git a/printutils/lpd.c b/printutils/lpd.c index ed3d9d1..916fd62 100644 --- a/printutils/lpd.c +++ b/printutils/lpd.c @@ -1,6 +1,6 @@ /* vi: set sw=4 ts=4: */ /* - * micro lpd - a small non-queueing lpd + * micro lpd * * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com> * @@ -8,72 +8,108 @@ */ #include "libbb.h" +// TODO: xmalloc_reads is vulnerable to remote OOM attack! + int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; int lpd_main(int argc, char *argv[]) { - char *s; + int spooling; + char *s, *queue; + + // read command + s = xmalloc_reads(STDIN_FILENO, NULL); + + // we understand only "receive job" command + if (2 != *s) { + unsupported_cmd: + printf("Command %02x %s\n", + (unsigned char)s[0], "is not supported"); + return EXIT_FAILURE; + } - // goto spool directory // spool directory contains either links to real printer devices or just simple files // these links or files are called "queues" - if (!argv[1]) - bb_show_usage(); + // OR + // if a directory named as given queue exists within spool directory + // then LPD enters spooling mode and just dumps both control and data files to it - xchdir(argv[1]); + // goto spool directory + if (argv[1]) + xchdir(argv[1]); - xdup2(1, 2); + // parse command: "\x2QUEUE_NAME\n" + queue = s + 1; + *strchrnul(s, '\n') = '\0'; - // read command - s = xmalloc_reads(STDIN_FILENO, NULL); + // protect against "/../" attacks + if (queue[0] == '.' || strstr(queue, "/.")) + return EXIT_FAILURE; - // N.B. we keep things simple - // only "receive job" command is meaningful here... - if (2 == *s) { - char *queue; + // queue is a directory -> chdir to it and enter spooling mode + spooling = chdir(queue) + 1; /* 0: cannot chdir, 1: done */ - // parse command: "\x2QUEUE_NAME\n" - queue = s + 1; - *strchrnul(s, '\n') = '\0'; + xdup2(STDOUT_FILENO, STDERR_FILENO); - while (1) { - // signal OK - write(STDOUT_FILENO, "", 1); - // get subcommand - s = xmalloc_reads(STDIN_FILENO, NULL); - if (!s) - return EXIT_SUCCESS; // EOF (probably) - // valid s must be of form: SUBCMD | LEN | SP | FNAME - // N.B. we bail out on any error - // control or data file follows - if (2 == s[0] || 3 == s[0]) { - int fd; - size_t len; - // 2: control file (ignoring), 3: data file - fd = -1; - if (3 == s[0]) - fd = xopen(queue, O_RDWR | O_APPEND); - // get data length - *strchrnul(s, ' ') = '\0'; - len = xatou(s + 1); - // dump exactly len bytes to file, or die - bb_copyfd_exact_size(STDIN_FILENO, fd, len); - close(fd); // NB: can do close(-1). Who cares? - free(s); - // got no ACK? -> bail out - if (safe_read(STDIN_FILENO, s, 1) <= 0 || s[0]) { - // don't talk to peer - it obviously - // don't follow the protocol - return EXIT_FAILURE; - } - } else { - // any other subcommand aborts receiving job - // N.B. abort subcommand itself doesn't contain - // fname so it failed earlier... - break; - } - } - } + while (1) { + char *fname; + int fd; + // int is easier than ssize_t: can use xatoi_u, + // and can correctly display error returns (-1) + int expected_len, real_len; + + // signal OK + write(STDOUT_FILENO, "", 1); + + // get subcommand + s = xmalloc_reads(STDIN_FILENO, NULL); + if (!s) + return EXIT_SUCCESS; // probably EOF + // we understand only "control file" or "data file" cmds + if (2 != s[0] && 3 != s[0]) + goto unsupported_cmd; - printf("Command %02x not supported\n", (unsigned char)*s); - return EXIT_FAILURE; + *strchrnul(s, '\n') = '\0'; + // valid s must be of form: SUBCMD | LEN | SP | FNAME + // N.B. we bail out on any error + fname = strchr(s, ' '); + if (!fname) { + printf("Command %02x %s\n", + (unsigned char)s[0], "lacks filename"); + return EXIT_FAILURE; + } + *fname++ = '\0'; + if (spooling) { + // spooling mode: dump both files + // make "/../" attacks in file names ineffective + xchroot("."); + // job in flight has mode 0200 "only writable" + fd = xopen3(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); + } else { + // non-spooling mode: + // 2: control file (ignoring), 3: data file + fd = -1; + if (3 == s[0]) + fd = xopen(queue, O_RDWR | O_APPEND); + } + expected_len = xatoi_u(s + 1); + real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); + if (spooling && real_len != expected_len) { + unlink(fname); // don't keep corrupted files + printf("Expected %d but got %d bytes\n", + expected_len, real_len); + return EXIT_FAILURE; + } + // get ACK and see whether it is NUL (ok) + if (read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) { + // don't send error msg to peer - it obviously + // don't follow the protocol, so probably + // it can't understand us either + return EXIT_FAILURE; + } + // chmod completely downloaded job as "readable+writable" + if (spooling) + fchmod(fd, 0600); + close(fd); // NB: can do close(-1). Who cares? + free(s); + } /* while (1) */ } |