busybox/printutils/lpr.c
<<
>>
Prefs
   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