Initial public busybox upstream commit
[busybox4maemo] / printutils / lpd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * micro lpd
4  *
5  * Copyright (C) 2008 by Vladimir Dronnikov <dronnikov@gmail.com>
6  *
7  * Licensed under GPLv2, see file LICENSE in this tarball for details.
8  */
9 #include "libbb.h"
10
11 // TODO: xmalloc_reads is vulnerable to remote OOM attack!
12
13 int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
14 int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[])
15 {
16         int spooling;
17         char *s, *queue;
18
19         // read command
20         s = xmalloc_reads(STDIN_FILENO, NULL);
21
22         // we understand only "receive job" command
23         if (2 != *s) {
24  unsupported_cmd:
25                 printf("Command %02x %s\n",
26                         (unsigned char)s[0], "is not supported");
27                 return EXIT_FAILURE;
28         }
29
30         // spool directory contains either links to real printer devices or just simple files
31         // these links or files are called "queues"
32         // OR
33         // if a directory named as given queue exists within spool directory
34         // then LPD enters spooling mode and just dumps both control and data files to it
35
36         // goto spool directory
37         if (argv[1])
38                 xchdir(argv[1]);
39
40         // parse command: "\x2QUEUE_NAME\n"
41         queue = s + 1;
42         *strchrnul(s, '\n') = '\0';
43
44         // protect against "/../" attacks
45         if (queue[0] == '.' || strstr(queue, "/."))
46                 return EXIT_FAILURE;
47
48         // queue is a directory -> chdir to it and enter spooling mode
49         spooling = chdir(queue) + 1; /* 0: cannot chdir, 1: done */
50
51         xdup2(STDOUT_FILENO, STDERR_FILENO);
52
53         while (1) {
54                 char *fname;
55                 int fd;
56                 // int is easier than ssize_t: can use xatoi_u,
57                 // and can correctly display error returns (-1)
58                 int expected_len, real_len;
59
60                 // signal OK
61                 write(STDOUT_FILENO, "", 1);
62
63                 // get subcommand
64                 s = xmalloc_reads(STDIN_FILENO, NULL);
65                 if (!s)
66                         return EXIT_SUCCESS; // probably EOF
67                 // we understand only "control file" or "data file" cmds
68                 if (2 != s[0] && 3 != s[0])
69                         goto unsupported_cmd;
70
71                 *strchrnul(s, '\n') = '\0';
72                 // valid s must be of form: SUBCMD | LEN | SP | FNAME
73                 // N.B. we bail out on any error
74                 fname = strchr(s, ' ');
75                 if (!fname) {
76                         printf("Command %02x %s\n",
77                                 (unsigned char)s[0], "lacks filename");
78                         return EXIT_FAILURE;
79                 }
80                 *fname++ = '\0';
81                 if (spooling) {
82                         // spooling mode: dump both files
83                         // make "/../" attacks in file names ineffective
84                         xchroot(".");
85                         // job in flight has mode 0200 "only writable"
86                         fd = xopen3(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200);
87                 } else {
88                         // non-spooling mode:
89                         // 2: control file (ignoring), 3: data file
90                         fd = -1;
91                         if (3 == s[0])
92                                 fd = xopen(queue, O_RDWR | O_APPEND);
93                 }
94                 expected_len = xatoi_u(s + 1);
95                 real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len);
96                 if (spooling && real_len != expected_len) {
97                         unlink(fname); // don't keep corrupted files
98                         printf("Expected %d but got %d bytes\n",
99                                 expected_len, real_len);
100                         return EXIT_FAILURE;
101                 }
102                 // get ACK and see whether it is NUL (ok)
103                 if (read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) {
104                         // don't send error msg to peer - it obviously
105                         // don't follow the protocol, so probably
106                         // it can't understand us either
107                         return EXIT_FAILURE;
108                 }
109                 // chmod completely downloaded job as "readable+writable"
110                 if (spooling)
111                         fchmod(fd, 0600);
112                 close(fd); // NB: can do close(-1). Who cares?
113                 free(s);
114         } /* while (1) */
115 }