From 394eebed6656dfc2e56a79500b602023000ac415 Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Mon, 25 Feb 2008 20:30:24 +0000 Subject: lpd: spool mode added by Vladimir lpr: more robust error reporting *: introduce and use xchroot libbb: full_read/write now will report partial data counts prior to error isdirectory.c: style fixes lpd_main 249 486 +237 xchroot - 29 +29 get_response_or_say_and_die 110 139 +29 full_write 52 60 +8 full_read 55 63 +8 static.newline 1 - -1 switch_root_main 404 400 -4 chpst_main 1089 1079 -10 getopt32 1370 1359 -11 chroot_main 115 101 -14 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 4/4 up/down: 311/-40) Total: 271 bytes text data bss dec hex filename 798472 728 7484 806684 c4f1c busybox_old 798775 728 7484 806987 c504b busybox_unstripped --- printutils/lpd.c | 148 ++++++++++++++++++++++++++++++++++--------------------- printutils/lpr.c | 25 ++++++---- 2 files changed, 108 insertions(+), 65 deletions(-) (limited to 'printutils') 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 * @@ -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) */ } diff --git a/printutils/lpr.c b/printutils/lpr.c index 52983bf..09fbc6a 100644 --- a/printutils/lpr.c +++ b/printutils/lpr.c @@ -19,19 +19,26 @@ */ static void get_response_or_say_and_die(const char *errmsg) { - static const char newline = '\n'; - char buf = ' '; + ssize_t sz; + char buf[128]; fflush(stdout); - safe_read(STDOUT_FILENO, &buf, 1); - if ('\0' != buf) { + buf[0] = ' '; + sz = safe_read(STDOUT_FILENO, buf, 1); + if ('\0' != buf[0]) { // request has failed - bb_error_msg("error while %s. Server said:", errmsg); - safe_write(STDERR_FILENO, &buf, 1); - logmode = 0; /* no error messages from bb_copyfd_eof() pls */ - bb_copyfd_eof(STDOUT_FILENO, STDERR_FILENO); - safe_write(STDERR_FILENO, &newline, 1); + // try to make sure last char is '\n', but do not add + // superfluous one + sz = full_read(STDOUT_FILENO, buf + 1, 126); + bb_error_msg("error while %s%s", errmsg, + (sz > 0 ? ". Server said:" : "")); + if (sz > 0) { + // sz = (bytes in buf) - 1 + if (buf[sz] != '\n') + buf[++sz] = '\n'; + safe_write(STDERR_FILENO, buf, sz + 1); + } xfunc_die(); } } -- cgit v1.1