diff options
-rw-r--r-- | printutils/lpd.c | 207 |
1 files changed, 120 insertions, 87 deletions
diff --git a/printutils/lpd.c b/printutils/lpd.c index fe895939a..cac881383 100644 --- a/printutils/lpd.c +++ b/printutils/lpd.c | |||
@@ -64,7 +64,7 @@ static char *sane(char *str) | |||
64 | char *s = str; | 64 | char *s = str; |
65 | char *p = s; | 65 | char *p = s; |
66 | while (*s) { | 66 | while (*s) { |
67 | if (isalnum(*s) || '-' == *s) { | 67 | if (isalnum(*s) || '-' == *s || '_' == *s) { |
68 | *p++ = *s; | 68 | *p++ = *s; |
69 | } | 69 | } |
70 | s++; | 70 | s++; |
@@ -73,100 +73,85 @@ static char *sane(char *str) | |||
73 | return str; | 73 | return str; |
74 | } | 74 | } |
75 | 75 | ||
76 | /* vfork() disables some optimizations. Moving its use | 76 | // we can use leaky setenv since we are about to exec or exit |
77 | * to minimal, non-inlined function saves bytes */ | 77 | static void exec_helper(char **filenames, char **argv) ATTRIBUTE_NORETURN; |
78 | static NOINLINE void vfork_close_stdio_and_exec(char **argv) | 78 | static void exec_helper(char **filenames, char **argv) |
79 | { | 79 | { |
80 | if (vfork() == 0) { | 80 | char *p, *q; |
81 | // CHILD | 81 | char var[2]; |
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); | ||
87 | _exit(127); | ||
88 | } | ||
89 | } | ||
90 | 82 | ||
91 | static void exec_helper(const char *fname, char **argv) | 83 | // read and delete ctrlfile |
92 | { | 84 | q = xmalloc_open_read_close(filenames[0], NULL); |
93 | char *p, *q, *file; | 85 | unlink(filenames[0]); |
94 | char *our_env[12]; | 86 | // provide datafile name |
95 | int env_idx; | 87 | xsetenv("DATAFILE", filenames[1]); |
96 | |||
97 | // read control file | ||
98 | file = q = xmalloc_open_read_close(fname, NULL); | ||
99 | // delete control file | ||
100 | unlink(fname); | ||
101 | // parse control file by "\n" | 88 | // parse control file by "\n" |
102 | env_idx = 0; | ||
103 | while ((p = strchr(q, '\n')) != NULL | 89 | while ((p = strchr(q, '\n')) != NULL |
104 | && isalpha(*q) | 90 | && isalpha(*q) |
105 | && env_idx < ARRAY_SIZE(our_env) | ||
106 | ) { | 91 | ) { |
107 | *p++ = '\0'; | 92 | *p++ = '\0'; |
108 | // here q is a line of <SYM><VALUE> | 93 | // here q is a line of <SYM><VALUE> |
109 | // let us set environment string <SYM>=<VALUE> | 94 | // let us set environment string <SYM>=<VALUE> |
110 | // N.B. setenv is leaky! | 95 | var[0] = *q++; |
111 | // We have to use putenv(malloced_str), | 96 | var[1] = '\0'; |
112 | // and unsetenv+free (in parent) | 97 | xsetenv(var, q); |
113 | our_env[env_idx] = xasprintf("%c=%s", *q, q+1); | ||
114 | putenv(our_env[env_idx]); | ||
115 | env_idx++; | ||
116 | // next line, plz! | 98 | // next line, plz! |
117 | q = p; | 99 | q = p; |
118 | } | 100 | } |
119 | free(file); | 101 | // we are the helper, we wanna be silent. |
120 | 102 | // this call reopens stdio fds to "/dev/null" | |
121 | vfork_close_stdio_and_exec(argv); | 103 | // (no daemonization is done) |
122 | 104 | bb_daemonize_or_rexec(DAEMON_DEVNULL_STDIO | DAEMON_ONLY_SANITIZE, NULL); | |
123 | // PARENT (or vfork error) | 105 | BB_EXECVP(*argv, argv); |
124 | // clean up... | 106 | exit(0); |
125 | while (--env_idx >= 0) { | ||
126 | *strchrnul(our_env[env_idx], '=') = '\0'; | ||
127 | unsetenv(our_env[env_idx]); | ||
128 | } | ||
129 | } | 107 | } |
130 | 108 | ||
131 | static char *xmalloc_read_stdin(void) | 109 | static char *xmalloc_read_stdin(void) |
132 | { | 110 | { |
133 | size_t max = 4 * 1024; /* more than enough for commands! */ | 111 | // SECURITY: |
112 | size_t max = 4 * 1024; // more than enough for commands! | ||
134 | return xmalloc_reads(STDIN_FILENO, NULL, &max); | 113 | return xmalloc_reads(STDIN_FILENO, NULL, &max); |
135 | } | 114 | } |
136 | 115 | ||
137 | int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; | 116 | int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; |
138 | int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[]) | 117 | int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[]) |
139 | { | 118 | { |
140 | int spooling; | 119 | int spooling = spooling; // for compiler |
120 | int seen; | ||
141 | char *s, *queue; | 121 | char *s, *queue; |
122 | char *filenames[2]; | ||
142 | 123 | ||
143 | // read command | 124 | // goto spool directory |
144 | s = xmalloc_read_stdin(); | 125 | if (*++argv) |
126 | xchdir(*argv++); | ||
127 | |||
128 | // error messages of xfuncs will be sent over network | ||
129 | xdup2(STDOUT_FILENO, STDERR_FILENO); | ||
145 | 130 | ||
131 | filenames[0] = NULL; // ctrlfile name | ||
132 | filenames[1] = NULL; // datafile name | ||
133 | |||
134 | // read command | ||
135 | s = queue = xmalloc_read_stdin(); | ||
146 | // we understand only "receive job" command | 136 | // we understand only "receive job" command |
147 | if (2 != *s) { | 137 | if (2 != *queue) { |
148 | unsupported_cmd: | 138 | unsupported_cmd: |
149 | printf("Command %02x %s\n", | 139 | printf("Command %02x %s\n", |
150 | (unsigned char)s[0], "is not supported"); | 140 | (unsigned char)s[0], "is not supported"); |
151 | return EXIT_FAILURE; | 141 | goto err_exit; |
152 | } | 142 | } |
153 | 143 | ||
154 | // goto spool directory | 144 | // parse command: "2 | QUEUE_NAME | '\n'" |
155 | if (*++argv) | 145 | queue++; |
156 | xchdir(*argv++); | ||
157 | |||
158 | // parse command: "\x2QUEUE_NAME\n" | ||
159 | queue = s + 1; | ||
160 | *strchrnul(s, '\n') = '\0'; | ||
161 | |||
162 | // protect against "/../" attacks | 146 | // protect against "/../" attacks |
147 | // *strchrnul(queue, '\n') = '\0'; - redundant, sane() will do | ||
163 | if (!*sane(queue)) | 148 | if (!*sane(queue)) |
164 | return EXIT_FAILURE; | 149 | return EXIT_FAILURE; |
165 | 150 | ||
166 | // queue is a directory -> chdir to it and enter spooling mode | 151 | // queue is a directory -> chdir to it and enter spooling mode |
167 | spooling = chdir(queue) + 1; /* 0: cannot chdir, 1: done */ | 152 | spooling = chdir(queue) + 1; // 0: cannot chdir, 1: done |
168 | 153 | seen = 0; | |
169 | xdup2(STDOUT_FILENO, STDERR_FILENO); | 154 | // we don't free(queue), we might need it later |
170 | 155 | ||
171 | while (1) { | 156 | while (1) { |
172 | char *fname; | 157 | char *fname; |
@@ -176,30 +161,65 @@ int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[]) | |||
176 | int expected_len, real_len; | 161 | int expected_len, real_len; |
177 | 162 | ||
178 | // signal OK | 163 | // signal OK |
179 | write(STDOUT_FILENO, "", 1); | 164 | safe_write(STDOUT_FILENO, "", 1); |
180 | 165 | ||
181 | // get subcommand | 166 | // get subcommand |
167 | // valid s must be of form: "SUBCMD | LEN | space | FNAME" | ||
168 | // N.B. we bail out on any error | ||
182 | s = xmalloc_read_stdin(); | 169 | s = xmalloc_read_stdin(); |
183 | if (!s) | 170 | if (!s) { // (probably) EOF |
184 | return EXIT_SUCCESS; // probably EOF | 171 | if (spooling /* && 6 != spooling - always true */) { |
172 | // we didn't see both ctrlfile & datafile! | ||
173 | goto err_exit; | ||
174 | } | ||
175 | // one of only two non-error exits | ||
176 | return EXIT_SUCCESS; | ||
177 | } | ||
178 | |||
179 | // validate input. | ||
185 | // we understand only "control file" or "data file" cmds | 180 | // we understand only "control file" or "data file" cmds |
186 | if (2 != s[0] && 3 != s[0]) | 181 | if (2 != s[0] && 3 != s[0]) |
187 | goto unsupported_cmd; | 182 | goto unsupported_cmd; |
188 | 183 | if (seen & (s[0] - 1)) { | |
184 | printf("Duplicated subcommand\n"); | ||
185 | goto err_exit; | ||
186 | } | ||
187 | seen &= (s[0] - 1); // bit 1: ctrlfile; bit 2: datafile | ||
188 | // get filename | ||
189 | *strchrnul(s, '\n') = '\0'; | 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, ' '); | 190 | fname = strchr(s, ' '); |
193 | if (!fname) { | 191 | if (!fname) { |
194 | printf("Command %02x %s\n", | 192 | // bad_fname: |
195 | (unsigned char)s[0], "lacks filename"); | 193 | printf("No or bad filename\n"); |
196 | return EXIT_FAILURE; | 194 | goto err_exit; |
197 | } | 195 | } |
198 | *fname++ = '\0'; | 196 | *fname++ = '\0'; |
197 | // // s[0]==2: ctrlfile, must start with 'c' | ||
198 | // // s[0]==3: datafile, must start with 'd' | ||
199 | // if (fname[0] != s[0] + ('c'-2)) | ||
200 | // goto bad_fname; | ||
201 | // get length | ||
202 | expected_len = bb_strtou(s + 1, NULL, 10); | ||
203 | if (errno || expected_len < 0) { | ||
204 | printf("Bad length\n"); | ||
205 | goto err_exit; | ||
206 | } | ||
207 | if (2 == s[0] && expected_len > 16 * 1024) { | ||
208 | // SECURITY: | ||
209 | // ctrlfile can't be big (we want to read it back later!) | ||
210 | printf("File is too big\n"); | ||
211 | goto err_exit; | ||
212 | } | ||
213 | |||
214 | // open the file | ||
199 | if (spooling) { | 215 | if (spooling) { |
200 | // spooling mode: dump both files | 216 | // spooling mode: dump both files |
201 | // job in flight has mode 0200 "only writable" | 217 | // job in flight has mode 0200 "only writable" |
202 | fd = xopen3(sane(fname), O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); | 218 | sane(fname); |
219 | fd = open3_or_warn(fname, O_CREAT | O_WRONLY | O_TRUNC | O_EXCL, 0200); | ||
220 | if (fd < 0) | ||
221 | goto err_exit; | ||
222 | filenames[s[0] - 2] = xstrdup(fname); | ||
203 | } else { | 223 | } else { |
204 | // non-spooling mode: | 224 | // non-spooling mode: |
205 | // 2: control file (ignoring), 3: data file | 225 | // 2: control file (ignoring), 3: data file |
@@ -207,35 +227,48 @@ int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[]) | |||
207 | if (3 == s[0]) | 227 | if (3 == s[0]) |
208 | fd = xopen(queue, O_RDWR | O_APPEND); | 228 | fd = xopen(queue, O_RDWR | O_APPEND); |
209 | } | 229 | } |
210 | expected_len = xatoi_u(s + 1); | 230 | |
231 | // copy the file | ||
211 | real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); | 232 | real_len = bb_copyfd_size(STDIN_FILENO, fd, expected_len); |
212 | if (spooling && real_len != expected_len) { | 233 | if (real_len != expected_len) { |
213 | unlink(fname); // don't keep corrupted files | ||
214 | printf("Expected %d but got %d bytes\n", | 234 | printf("Expected %d but got %d bytes\n", |
215 | expected_len, real_len); | 235 | expected_len, real_len); |
216 | return EXIT_FAILURE; | 236 | goto err_exit; |
217 | } | 237 | } |
218 | // chmod completely downloaded file as "readable+writable" ... | 238 | // get ACK and see whether it is NUL (ok) |
239 | if (safe_read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) { | ||
240 | // don't send error msg to peer - it obviously | ||
241 | // don't follow the protocol, so probably | ||
242 | // it can't understand us either | ||
243 | goto err_exit; | ||
244 | } | ||
245 | |||
219 | if (spooling) { | 246 | if (spooling) { |
247 | // chmod completely downloaded file as "readable+writable" | ||
220 | fchmod(fd, 0600); | 248 | fchmod(fd, 0600); |
221 | // ... and accumulate dump state. | 249 | // accumulate dump state |
222 | // N.B. after all files are dumped spooling should be 1+2+3==6 | 250 | // N.B. after all files are dumped spooling should be 1+2+3==6 |
223 | spooling += s[0]; | 251 | spooling += s[0]; |
224 | } | 252 | } |
253 | free(s); | ||
225 | close(fd); // NB: can do close(-1). Who cares? | 254 | close(fd); // NB: can do close(-1). Who cares? |
226 | 255 | ||
227 | // are all files dumped? -> spawn spool helper | 256 | // spawn spool helper and exit if all files are dumped |
228 | if (6 == spooling && *argv) { | 257 | if (6 == spooling && *argv) { |
229 | fname[0] = 'c'; // pass control file name | 258 | // signal OK |
230 | exec_helper(fname, argv); | 259 | safe_write(STDOUT_FILENO, "", 1); |
260 | // does not return (exits 0) | ||
261 | exec_helper(filenames, argv); | ||
231 | } | 262 | } |
232 | // get ACK and see whether it is NUL (ok) | 263 | } // while (1) |
233 | if (read(STDIN_FILENO, s, 1) != 1 || s[0] != 0) { | 264 | |
234 | // don't send error msg to peer - it obviously | 265 | err_exit: |
235 | // don't follow the protocol, so probably | 266 | // don't keep corrupted files |
236 | // it can't understand us either | 267 | if (spooling) { |
237 | return EXIT_FAILURE; | 268 | if (filenames[0]) |
238 | } | 269 | unlink(filenames[0]); |
239 | free(s); | 270 | if (filenames[1]) |
240 | } /* while (1) */ | 271 | unlink(filenames[1]); |
272 | } | ||
273 | return EXIT_FAILURE; | ||
241 | } | 274 | } |