aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--printutils/lpd.c207
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 */ 77static void exec_helper(char **filenames, char **argv) ATTRIBUTE_NORETURN;
78static NOINLINE void vfork_close_stdio_and_exec(char **argv) 78static 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
91static 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
131static char *xmalloc_read_stdin(void) 109static 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
137int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE; 116int lpd_main(int argc, char *argv[]) MAIN_EXTERNALLY_VISIBLE;
138int lpd_main(int argc ATTRIBUTE_UNUSED, char *argv[]) 117int 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}