diff options
Diffstat (limited to 'sftp-server.c')
-rw-r--r-- | sftp-server.c | 1078 |
1 files changed, 1078 insertions, 0 deletions
diff --git a/sftp-server.c b/sftp-server.c new file mode 100644 index 000000000..39cecac59 --- /dev/null +++ b/sftp-server.c | |||
@@ -0,0 +1,1078 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2000 Markus Friedl. All rights reserved. | ||
3 | * | ||
4 | * Redistribution and use in source and binary forms, with or without | ||
5 | * modification, are permitted provided that the following conditions | ||
6 | * are met: | ||
7 | * 1. Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * 2. Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * 3. All advertising materials mentioning features or use of this software | ||
13 | * must display the following acknowledgement: | ||
14 | * This product includes software developed by Markus Friedl. | ||
15 | * 4. The name of the author may not be used to endorse or promote products | ||
16 | * derived from this software without specific prior written permission. | ||
17 | * | ||
18 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | ||
19 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | ||
20 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | ||
21 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | ||
22 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | ||
23 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
24 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
25 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | ||
27 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
28 | */ | ||
29 | #include "includes.h" | ||
30 | RCSID("$OpenBSD: sftp-server.c,v 1.4 2000/09/04 19:10:08 markus Exp $"); | ||
31 | |||
32 | #include "ssh.h" | ||
33 | #include "buffer.h" | ||
34 | #include "bufaux.h" | ||
35 | #include "getput.h" | ||
36 | #include "xmalloc.h" | ||
37 | |||
38 | /* version */ | ||
39 | #define SSH_FILEXFER_VERSION 2 | ||
40 | |||
41 | /* client to server */ | ||
42 | #define SSH_FXP_INIT 1 | ||
43 | #define SSH_FXP_OPEN 3 | ||
44 | #define SSH_FXP_CLOSE 4 | ||
45 | #define SSH_FXP_READ 5 | ||
46 | #define SSH_FXP_WRITE 6 | ||
47 | #define SSH_FXP_LSTAT 7 | ||
48 | #define SSH_FXP_FSTAT 8 | ||
49 | #define SSH_FXP_SETSTAT 9 | ||
50 | #define SSH_FXP_FSETSTAT 10 | ||
51 | #define SSH_FXP_OPENDIR 11 | ||
52 | #define SSH_FXP_READDIR 12 | ||
53 | #define SSH_FXP_REMOVE 13 | ||
54 | #define SSH_FXP_MKDIR 14 | ||
55 | #define SSH_FXP_RMDIR 15 | ||
56 | #define SSH_FXP_REALPATH 16 | ||
57 | #define SSH_FXP_STAT 17 | ||
58 | #define SSH_FXP_RENAME 18 | ||
59 | |||
60 | /* server to client */ | ||
61 | #define SSH_FXP_VERSION 2 | ||
62 | #define SSH_FXP_STATUS 101 | ||
63 | #define SSH_FXP_HANDLE 102 | ||
64 | #define SSH_FXP_DATA 103 | ||
65 | #define SSH_FXP_NAME 104 | ||
66 | #define SSH_FXP_ATTRS 105 | ||
67 | |||
68 | /* portable open modes */ | ||
69 | #define SSH_FXF_READ 0x01 | ||
70 | #define SSH_FXF_WRITE 0x02 | ||
71 | #define SSH_FXF_APPEND 0x04 | ||
72 | #define SSH_FXF_CREAT 0x08 | ||
73 | #define SSH_FXF_TRUNC 0x10 | ||
74 | #define SSH_FXF_EXCL 0x20 | ||
75 | |||
76 | /* attributes */ | ||
77 | #define SSH_FXA_HAVE_SIZE 0x01 | ||
78 | #define SSH_FXA_HAVE_UGID 0x02 | ||
79 | #define SSH_FXA_HAVE_PERM 0x04 | ||
80 | #define SSH_FXA_HAVE_TIME 0x08 | ||
81 | |||
82 | /* status messages */ | ||
83 | #define SSH_FX_OK 0x00 | ||
84 | #define SSH_FX_EOF 0x01 | ||
85 | #define SSH_FX_NO_SUCH_FILE 0x02 | ||
86 | #define SSH_FX_PERMISSION_DENIED 0x03 | ||
87 | #define SSH_FX_FAILURE 0x04 | ||
88 | #define SSH_FX_BAD_MESSAGE 0x05 | ||
89 | #define SSH_FX_NO_CONNECTION 0x06 | ||
90 | #define SSH_FX_CONNECTION_LOST 0x07 | ||
91 | |||
92 | |||
93 | /* helper */ | ||
94 | #define get_int() buffer_get_int(&iqueue); | ||
95 | #define get_string(lenp) buffer_get_string(&iqueue, lenp); | ||
96 | #define TRACE log | ||
97 | |||
98 | /* input and output queue */ | ||
99 | Buffer iqueue; | ||
100 | Buffer oqueue; | ||
101 | |||
102 | /* portable attibutes, etc. */ | ||
103 | |||
104 | typedef struct Attrib Attrib; | ||
105 | typedef struct Stat Stat; | ||
106 | |||
107 | struct Attrib | ||
108 | { | ||
109 | u_int32_t flags; | ||
110 | u_int32_t size_high; | ||
111 | u_int32_t size_low; | ||
112 | u_int64_t size; | ||
113 | u_int32_t uid; | ||
114 | u_int32_t gid; | ||
115 | u_int32_t perm; | ||
116 | u_int32_t atime; | ||
117 | u_int32_t mtime; | ||
118 | }; | ||
119 | |||
120 | struct Stat | ||
121 | { | ||
122 | char *name; | ||
123 | char *long_name; | ||
124 | Attrib attrib; | ||
125 | }; | ||
126 | |||
127 | int | ||
128 | errno_to_portable(int unixerrno) | ||
129 | { | ||
130 | int ret = 0; | ||
131 | switch (unixerrno) { | ||
132 | case 0: | ||
133 | ret = SSH_FX_OK; | ||
134 | break; | ||
135 | case ENOENT: | ||
136 | case ENOTDIR: | ||
137 | case EBADF: | ||
138 | case ELOOP: | ||
139 | ret = SSH_FX_NO_SUCH_FILE; | ||
140 | break; | ||
141 | case EPERM: | ||
142 | case EACCES: | ||
143 | case EFAULT: | ||
144 | ret = SSH_FX_PERMISSION_DENIED; | ||
145 | break; | ||
146 | case ENAMETOOLONG: | ||
147 | case EINVAL: | ||
148 | ret = SSH_FX_BAD_MESSAGE; | ||
149 | break; | ||
150 | default: | ||
151 | ret = SSH_FX_FAILURE; | ||
152 | break; | ||
153 | } | ||
154 | return ret; | ||
155 | } | ||
156 | |||
157 | int | ||
158 | flags_from_portable(int pflags) | ||
159 | { | ||
160 | int flags = 0; | ||
161 | if (pflags & SSH_FXF_READ && | ||
162 | pflags & SSH_FXF_WRITE) { | ||
163 | flags = O_RDWR; | ||
164 | } else if (pflags & SSH_FXF_READ) { | ||
165 | flags = O_RDONLY; | ||
166 | } else if (pflags & SSH_FXF_WRITE) { | ||
167 | flags = O_WRONLY; | ||
168 | } | ||
169 | if (pflags & SSH_FXF_CREAT) | ||
170 | flags |= O_CREAT; | ||
171 | if (pflags & SSH_FXF_TRUNC) | ||
172 | flags |= O_TRUNC; | ||
173 | if (pflags & SSH_FXF_EXCL) | ||
174 | flags |= O_EXCL; | ||
175 | return flags; | ||
176 | } | ||
177 | |||
178 | void | ||
179 | attrib_clear(Attrib *a) | ||
180 | { | ||
181 | a->flags = 0; | ||
182 | a->size_low = 0; | ||
183 | a->size_high = 0; | ||
184 | a->size = 0; | ||
185 | a->uid = 0; | ||
186 | a->gid = 0; | ||
187 | a->perm = 0; | ||
188 | a->atime = 0; | ||
189 | a->mtime = 0; | ||
190 | } | ||
191 | |||
192 | Attrib * | ||
193 | decode_attrib(Buffer *b) | ||
194 | { | ||
195 | static Attrib a; | ||
196 | attrib_clear(&a); | ||
197 | a.flags = get_int(); | ||
198 | if (a.flags & SSH_FXA_HAVE_SIZE) { | ||
199 | a.size_high = get_int(); | ||
200 | a.size_low = get_int(); | ||
201 | a.size = (((u_int64_t) a.size_high) << 32) + a.size_low; | ||
202 | } | ||
203 | if (a.flags & SSH_FXA_HAVE_UGID) { | ||
204 | a.uid = get_int(); | ||
205 | a.gid = get_int(); | ||
206 | } | ||
207 | if (a.flags & SSH_FXA_HAVE_PERM) { | ||
208 | a.perm = get_int(); | ||
209 | } | ||
210 | if (a.flags & SSH_FXA_HAVE_TIME) { | ||
211 | a.atime = get_int(); | ||
212 | a.mtime = get_int(); | ||
213 | } | ||
214 | return &a; | ||
215 | } | ||
216 | |||
217 | void | ||
218 | encode_attrib(Buffer *b, Attrib *a) | ||
219 | { | ||
220 | buffer_put_int(b, a->flags); | ||
221 | if (a->flags & SSH_FXA_HAVE_SIZE) { | ||
222 | buffer_put_int(b, a->size_high); | ||
223 | buffer_put_int(b, a->size_low); | ||
224 | } | ||
225 | if (a->flags & SSH_FXA_HAVE_UGID) { | ||
226 | buffer_put_int(b, a->uid); | ||
227 | buffer_put_int(b, a->gid); | ||
228 | } | ||
229 | if (a->flags & SSH_FXA_HAVE_PERM) { | ||
230 | buffer_put_int(b, a->perm); | ||
231 | } | ||
232 | if (a->flags & SSH_FXA_HAVE_TIME) { | ||
233 | buffer_put_int(b, a->atime); | ||
234 | buffer_put_int(b, a->mtime); | ||
235 | } | ||
236 | } | ||
237 | |||
238 | Attrib * | ||
239 | stat_to_attrib(struct stat *st) | ||
240 | { | ||
241 | static Attrib a; | ||
242 | attrib_clear(&a); | ||
243 | a.flags = 0; | ||
244 | a.flags |= SSH_FXA_HAVE_SIZE; | ||
245 | a.size = st->st_size; | ||
246 | a.size_low = a.size; | ||
247 | a.size_high = (u_int32_t) (a.size >> 32); | ||
248 | a.flags |= SSH_FXA_HAVE_UGID; | ||
249 | a.uid = st->st_uid; | ||
250 | a.gid = st->st_gid; | ||
251 | a.flags |= SSH_FXA_HAVE_PERM; | ||
252 | a.perm = st->st_mode; | ||
253 | a.flags |= SSH_FXA_HAVE_TIME; | ||
254 | a.atime = st->st_atime; | ||
255 | a.mtime = st->st_mtime; | ||
256 | return &a; | ||
257 | } | ||
258 | |||
259 | Attrib * | ||
260 | get_attrib(void) | ||
261 | { | ||
262 | return decode_attrib(&iqueue); | ||
263 | } | ||
264 | |||
265 | /* handle handles */ | ||
266 | |||
267 | typedef struct Handle Handle; | ||
268 | struct Handle { | ||
269 | int use; | ||
270 | DIR *dirp; | ||
271 | int fd; | ||
272 | char *name; | ||
273 | }; | ||
274 | enum { | ||
275 | HANDLE_UNUSED, | ||
276 | HANDLE_DIR, | ||
277 | HANDLE_FILE | ||
278 | }; | ||
279 | Handle handles[100]; | ||
280 | |||
281 | void | ||
282 | handle_init(void) | ||
283 | { | ||
284 | int i; | ||
285 | for(i = 0; i < sizeof(handles)/sizeof(Handle); i++) | ||
286 | handles[i].use = HANDLE_UNUSED; | ||
287 | } | ||
288 | |||
289 | int | ||
290 | handle_new(int use, char *name, int fd, DIR *dirp) | ||
291 | { | ||
292 | int i; | ||
293 | for(i = 0; i < sizeof(handles)/sizeof(Handle); i++) { | ||
294 | if (handles[i].use == HANDLE_UNUSED) { | ||
295 | handles[i].use = use; | ||
296 | handles[i].dirp = dirp; | ||
297 | handles[i].fd = fd; | ||
298 | handles[i].name = name; | ||
299 | return i; | ||
300 | } | ||
301 | } | ||
302 | return -1; | ||
303 | } | ||
304 | |||
305 | int | ||
306 | handle_is_ok(int i, int type) | ||
307 | { | ||
308 | return i >= 0 && i < sizeof(handles)/sizeof(Handle) && handles[i].use == type; | ||
309 | } | ||
310 | |||
311 | int | ||
312 | handle_to_string(int handle, char **stringp, int *hlenp) | ||
313 | { | ||
314 | char buf[1024]; | ||
315 | if (stringp == NULL || hlenp == NULL) | ||
316 | return -1; | ||
317 | snprintf(buf, sizeof buf, "%d", handle); | ||
318 | *stringp = xstrdup(buf); | ||
319 | *hlenp = strlen(*stringp); | ||
320 | return 0; | ||
321 | } | ||
322 | |||
323 | int | ||
324 | handle_from_string(char *handle, int hlen) | ||
325 | { | ||
326 | /* XXX OVERFLOW ? */ | ||
327 | char *ep; | ||
328 | long lval = strtol(handle, &ep, 10); | ||
329 | int val = lval; | ||
330 | if (*ep != '\0') | ||
331 | return -1; | ||
332 | if (handle_is_ok(val, HANDLE_FILE) || | ||
333 | handle_is_ok(val, HANDLE_DIR)) | ||
334 | return val; | ||
335 | return -1; | ||
336 | } | ||
337 | |||
338 | char * | ||
339 | handle_to_name(int handle) | ||
340 | { | ||
341 | if (handle_is_ok(handle, HANDLE_DIR)|| | ||
342 | handle_is_ok(handle, HANDLE_FILE)) | ||
343 | return handles[handle].name; | ||
344 | return NULL; | ||
345 | } | ||
346 | |||
347 | DIR * | ||
348 | handle_to_dir(int handle) | ||
349 | { | ||
350 | if (handle_is_ok(handle, HANDLE_DIR)) | ||
351 | return handles[handle].dirp; | ||
352 | return NULL; | ||
353 | } | ||
354 | |||
355 | int | ||
356 | handle_to_fd(int handle) | ||
357 | { | ||
358 | if (handle_is_ok(handle, HANDLE_FILE)) | ||
359 | return handles[handle].fd; | ||
360 | return -1; | ||
361 | } | ||
362 | |||
363 | int | ||
364 | handle_close(int handle) | ||
365 | { | ||
366 | int ret = -1; | ||
367 | if (handle_is_ok(handle, HANDLE_FILE)) { | ||
368 | ret = close(handles[handle].fd); | ||
369 | handles[handle].use = HANDLE_UNUSED; | ||
370 | } else if (handle_is_ok(handle, HANDLE_DIR)) { | ||
371 | ret = closedir(handles[handle].dirp); | ||
372 | handles[handle].use = HANDLE_UNUSED; | ||
373 | } else { | ||
374 | errno = ENOENT; | ||
375 | } | ||
376 | return ret; | ||
377 | } | ||
378 | |||
379 | int | ||
380 | get_handle(void) | ||
381 | { | ||
382 | char *handle; | ||
383 | int hlen, val; | ||
384 | handle = get_string(&hlen); | ||
385 | val = handle_from_string(handle, hlen); | ||
386 | xfree(handle); | ||
387 | return val; | ||
388 | } | ||
389 | |||
390 | /* send replies */ | ||
391 | |||
392 | void | ||
393 | send_msg(Buffer *m) | ||
394 | { | ||
395 | int mlen = buffer_len(m); | ||
396 | buffer_put_int(&oqueue, mlen); | ||
397 | buffer_append(&oqueue, buffer_ptr(m), mlen); | ||
398 | buffer_consume(m, mlen); | ||
399 | } | ||
400 | |||
401 | void | ||
402 | send_status(u_int32_t id, u_int32_t error) | ||
403 | { | ||
404 | Buffer msg; | ||
405 | TRACE("sent status id %d error %d", id, error); | ||
406 | buffer_init(&msg); | ||
407 | buffer_put_char(&msg, SSH_FXP_STATUS); | ||
408 | buffer_put_int(&msg, id); | ||
409 | buffer_put_int(&msg, error); | ||
410 | send_msg(&msg); | ||
411 | buffer_free(&msg); | ||
412 | } | ||
413 | void | ||
414 | send_data_or_handle(char type, u_int32_t id, char *data, int dlen) | ||
415 | { | ||
416 | Buffer msg; | ||
417 | buffer_init(&msg); | ||
418 | buffer_put_char(&msg, type); | ||
419 | buffer_put_int(&msg, id); | ||
420 | buffer_put_string(&msg, data, dlen); | ||
421 | send_msg(&msg); | ||
422 | buffer_free(&msg); | ||
423 | } | ||
424 | |||
425 | void | ||
426 | send_data(u_int32_t id, char *data, int dlen) | ||
427 | { | ||
428 | TRACE("sent data id %d len %d", id, dlen); | ||
429 | send_data_or_handle(SSH_FXP_DATA, id, data, dlen); | ||
430 | } | ||
431 | |||
432 | void | ||
433 | send_handle(u_int32_t id, int handle) | ||
434 | { | ||
435 | char *string; | ||
436 | int hlen; | ||
437 | handle_to_string(handle, &string, &hlen); | ||
438 | TRACE("sent handle id %d handle %d", id, handle); | ||
439 | send_data_or_handle(SSH_FXP_HANDLE, id, string, hlen); | ||
440 | xfree(string); | ||
441 | } | ||
442 | |||
443 | void | ||
444 | send_names(u_int32_t id, int count, Stat *stats) | ||
445 | { | ||
446 | Buffer msg; | ||
447 | int i; | ||
448 | buffer_init(&msg); | ||
449 | buffer_put_char(&msg, SSH_FXP_NAME); | ||
450 | buffer_put_int(&msg, id); | ||
451 | buffer_put_int(&msg, count); | ||
452 | TRACE("sent names id %d count %d", id, count); | ||
453 | for (i = 0; i < count; i++) { | ||
454 | buffer_put_cstring(&msg, stats[i].name); | ||
455 | buffer_put_cstring(&msg, stats[i].long_name); | ||
456 | encode_attrib(&msg, &stats[i].attrib); | ||
457 | } | ||
458 | send_msg(&msg); | ||
459 | buffer_free(&msg); | ||
460 | } | ||
461 | |||
462 | void | ||
463 | send_attrib(u_int32_t id, Attrib *a) | ||
464 | { | ||
465 | Buffer msg; | ||
466 | TRACE("sent attrib id %d have 0x%x", id, a->flags); | ||
467 | buffer_init(&msg); | ||
468 | buffer_put_char(&msg, SSH_FXP_ATTRS); | ||
469 | buffer_put_int(&msg, id); | ||
470 | encode_attrib(&msg, a); | ||
471 | send_msg(&msg); | ||
472 | buffer_free(&msg); | ||
473 | } | ||
474 | |||
475 | /* parse incoming */ | ||
476 | |||
477 | void | ||
478 | process_init(void) | ||
479 | { | ||
480 | Buffer msg; | ||
481 | int version = buffer_get_int(&iqueue); | ||
482 | |||
483 | TRACE("client version %d", version); | ||
484 | buffer_init(&msg); | ||
485 | buffer_put_char(&msg, SSH_FXP_VERSION); | ||
486 | buffer_put_int(&msg, SSH_FILEXFER_VERSION); | ||
487 | send_msg(&msg); | ||
488 | buffer_free(&msg); | ||
489 | } | ||
490 | |||
491 | void | ||
492 | process_open(void) | ||
493 | { | ||
494 | u_int32_t id, pflags; | ||
495 | Attrib *a; | ||
496 | char *name; | ||
497 | int handle, fd, flags, mode, status = SSH_FX_FAILURE; | ||
498 | |||
499 | id = get_int(); | ||
500 | name = get_string(NULL); | ||
501 | pflags = get_int(); | ||
502 | a = get_attrib(); | ||
503 | flags = flags_from_portable(pflags); | ||
504 | mode = (a->flags & SSH_FXA_HAVE_PERM) ? a->perm : 0666; | ||
505 | TRACE("open id %d name %s flags %d mode 0%o", id, name, pflags, mode); | ||
506 | fd = open(name, flags, mode); | ||
507 | if (fd < 0) { | ||
508 | status = errno_to_portable(errno); | ||
509 | } else { | ||
510 | handle = handle_new(HANDLE_FILE, xstrdup(name), fd, NULL); | ||
511 | if (handle < 0) { | ||
512 | close(fd); | ||
513 | } else { | ||
514 | send_handle(id, handle); | ||
515 | status = SSH_FX_OK; | ||
516 | } | ||
517 | } | ||
518 | if (status != SSH_FX_OK) | ||
519 | send_status(id, status); | ||
520 | xfree(name); | ||
521 | } | ||
522 | |||
523 | void | ||
524 | process_close(void) | ||
525 | { | ||
526 | u_int32_t id; | ||
527 | int handle, ret, status = SSH_FX_FAILURE; | ||
528 | |||
529 | id = get_int(); | ||
530 | handle = get_handle(); | ||
531 | TRACE("close id %d handle %d", id, handle); | ||
532 | ret = handle_close(handle); | ||
533 | status = (ret == -1) ? errno_to_portable(errno) : SSH_FX_OK; | ||
534 | send_status(id, status); | ||
535 | } | ||
536 | |||
537 | void | ||
538 | process_read(void) | ||
539 | { | ||
540 | char buf[64*1024]; | ||
541 | u_int32_t id, off_high, off_low, len; | ||
542 | int handle, fd, ret, status = SSH_FX_FAILURE; | ||
543 | u_int64_t off; | ||
544 | |||
545 | id = get_int(); | ||
546 | handle = get_handle(); | ||
547 | off_high = get_int(); | ||
548 | off_low = get_int(); | ||
549 | len = get_int(); | ||
550 | |||
551 | off = (((u_int64_t) off_high) << 32) + off_low; | ||
552 | TRACE("read id %d handle %d off %qd len %d", id, handle, off, len); | ||
553 | if (len > sizeof buf) { | ||
554 | len = sizeof buf; | ||
555 | log("read change len %d", len); | ||
556 | } | ||
557 | fd = handle_to_fd(handle); | ||
558 | if (fd >= 0) { | ||
559 | if (lseek(fd, off, SEEK_SET) < 0) { | ||
560 | error("process_read: seek failed"); | ||
561 | status = errno_to_portable(errno); | ||
562 | } else { | ||
563 | ret = read(fd, buf, len); | ||
564 | if (ret < 0) { | ||
565 | status = errno_to_portable(errno); | ||
566 | } else if (ret == 0) { | ||
567 | status = SSH_FX_EOF; | ||
568 | } else { | ||
569 | send_data(id, buf, ret); | ||
570 | status = SSH_FX_OK; | ||
571 | } | ||
572 | } | ||
573 | } | ||
574 | if (status != SSH_FX_OK) | ||
575 | send_status(id, status); | ||
576 | } | ||
577 | |||
578 | void | ||
579 | process_write(void) | ||
580 | { | ||
581 | u_int32_t id, off_high, off_low; | ||
582 | u_int64_t off; | ||
583 | int len; | ||
584 | int handle, fd, ret, status = SSH_FX_FAILURE; | ||
585 | char *data; | ||
586 | |||
587 | id = get_int(); | ||
588 | handle = get_handle(); | ||
589 | off_high = get_int(); | ||
590 | off_low = get_int(); | ||
591 | data = get_string(&len); | ||
592 | |||
593 | off = (((u_int64_t) off_high) << 32) + off_low; | ||
594 | TRACE("write id %d handle %d off %qd len %d", id, handle, off, len); | ||
595 | fd = handle_to_fd(handle); | ||
596 | if (fd >= 0) { | ||
597 | if (lseek(fd, off, SEEK_SET) < 0) { | ||
598 | status = errno_to_portable(errno); | ||
599 | error("process_write: seek failed"); | ||
600 | } else { | ||
601 | /* XXX ATOMICIO ? */ | ||
602 | ret = write(fd, data, len); | ||
603 | if (ret == -1) { | ||
604 | error("process_write: write failed"); | ||
605 | status = errno_to_portable(errno); | ||
606 | } else if (ret == len) { | ||
607 | status = SSH_FX_OK; | ||
608 | } else { | ||
609 | log("nothing at all written"); | ||
610 | } | ||
611 | } | ||
612 | } | ||
613 | send_status(id, status); | ||
614 | xfree(data); | ||
615 | } | ||
616 | |||
617 | void | ||
618 | process_do_stat(int do_lstat) | ||
619 | { | ||
620 | Attrib *a; | ||
621 | struct stat st; | ||
622 | u_int32_t id; | ||
623 | char *name; | ||
624 | int ret, status = SSH_FX_FAILURE; | ||
625 | |||
626 | id = get_int(); | ||
627 | name = get_string(NULL); | ||
628 | TRACE("%sstat id %d name %s", do_lstat ? "l" : "", id, name); | ||
629 | ret = do_lstat ? lstat(name, &st) : stat(name, &st); | ||
630 | if (ret < 0) { | ||
631 | status = errno_to_portable(errno); | ||
632 | } else { | ||
633 | a = stat_to_attrib(&st); | ||
634 | send_attrib(id, a); | ||
635 | status = SSH_FX_OK; | ||
636 | } | ||
637 | if (status != SSH_FX_OK) | ||
638 | send_status(id, status); | ||
639 | xfree(name); | ||
640 | } | ||
641 | |||
642 | void | ||
643 | process_stat(void) | ||
644 | { | ||
645 | process_do_stat(0); | ||
646 | } | ||
647 | |||
648 | void | ||
649 | process_lstat(void) | ||
650 | { | ||
651 | process_do_stat(1); | ||
652 | } | ||
653 | |||
654 | void | ||
655 | process_fstat(void) | ||
656 | { | ||
657 | Attrib *a; | ||
658 | struct stat st; | ||
659 | u_int32_t id; | ||
660 | int fd, ret, handle, status = SSH_FX_FAILURE; | ||
661 | |||
662 | id = get_int(); | ||
663 | handle = get_handle(); | ||
664 | TRACE("fstat id %d handle %d", id, handle); | ||
665 | fd = handle_to_fd(handle); | ||
666 | if (fd >= 0) { | ||
667 | ret = fstat(fd, &st); | ||
668 | if (ret < 0) { | ||
669 | status = errno_to_portable(errno); | ||
670 | } else { | ||
671 | a = stat_to_attrib(&st); | ||
672 | send_attrib(id, a); | ||
673 | status = SSH_FX_OK; | ||
674 | } | ||
675 | } | ||
676 | if (status != SSH_FX_OK) | ||
677 | send_status(id, status); | ||
678 | } | ||
679 | |||
680 | struct timeval * | ||
681 | attrib_to_tv(Attrib *a) | ||
682 | { | ||
683 | static struct timeval tv[2]; | ||
684 | tv[0].tv_sec = a->atime; | ||
685 | tv[0].tv_usec = 0; | ||
686 | tv[1].tv_sec = a->mtime; | ||
687 | tv[1].tv_usec = 0; | ||
688 | return tv; | ||
689 | } | ||
690 | |||
691 | void | ||
692 | process_setstat(void) | ||
693 | { | ||
694 | Attrib *a; | ||
695 | u_int32_t id; | ||
696 | char *name; | ||
697 | int ret; | ||
698 | int status = SSH_FX_OK; | ||
699 | |||
700 | id = get_int(); | ||
701 | name = get_string(NULL); | ||
702 | a = get_attrib(); | ||
703 | TRACE("setstat id %d name %s", id, name); | ||
704 | if (a->flags & SSH_FXA_HAVE_PERM) { | ||
705 | ret = chmod(name, a->perm & 0777); | ||
706 | if (ret == -1) | ||
707 | status = errno_to_portable(errno); | ||
708 | } | ||
709 | if (a->flags & SSH_FXA_HAVE_TIME) { | ||
710 | ret = utimes(name, attrib_to_tv(a)); | ||
711 | if (ret == -1) | ||
712 | status = errno_to_portable(errno); | ||
713 | } | ||
714 | send_status(id, status); | ||
715 | xfree(name); | ||
716 | } | ||
717 | |||
718 | void | ||
719 | process_fsetstat(void) | ||
720 | { | ||
721 | Attrib *a; | ||
722 | u_int32_t id; | ||
723 | int handle, fd, ret; | ||
724 | int status = SSH_FX_OK; | ||
725 | char *name = NULL; | ||
726 | |||
727 | id = get_int(); | ||
728 | handle = get_handle(); | ||
729 | a = get_attrib(); | ||
730 | TRACE("fsetstat id %d handle %d", id, handle); | ||
731 | fd = handle_to_fd(handle); | ||
732 | name = handle_to_name(handle); | ||
733 | if ((fd < 0) || (name == NULL)) { | ||
734 | status = SSH_FX_FAILURE; | ||
735 | } else { | ||
736 | if (a->flags & SSH_FXA_HAVE_PERM) { | ||
737 | ret = fchmod(fd, a->perm & 0777); | ||
738 | if (ret == -1) | ||
739 | status = errno_to_portable(errno); | ||
740 | } | ||
741 | if (a->flags & SSH_FXA_HAVE_TIME) { | ||
742 | #ifdef HAVE_FUTIMES | ||
743 | ret = futimes(fd, attrib_to_tv(a)); | ||
744 | #else | ||
745 | ret = utimes(name, attrib_to_tv(a)); | ||
746 | #endif | ||
747 | if (ret == -1) | ||
748 | status = errno_to_portable(errno); | ||
749 | } | ||
750 | } | ||
751 | send_status(id, status); | ||
752 | } | ||
753 | |||
754 | void | ||
755 | process_opendir(void) | ||
756 | { | ||
757 | DIR *dirp = NULL; | ||
758 | char *path; | ||
759 | int handle, status = SSH_FX_FAILURE; | ||
760 | u_int32_t id; | ||
761 | |||
762 | id = get_int(); | ||
763 | path = get_string(NULL); | ||
764 | TRACE("opendir id %d path %s", id, path); | ||
765 | dirp = opendir(path); | ||
766 | if (dirp == NULL) { | ||
767 | status = errno_to_portable(errno); | ||
768 | } else { | ||
769 | handle = handle_new(HANDLE_DIR, xstrdup(path), 0, dirp); | ||
770 | if (handle < 0) { | ||
771 | closedir(dirp); | ||
772 | } else { | ||
773 | send_handle(id, handle); | ||
774 | status = SSH_FX_OK; | ||
775 | } | ||
776 | |||
777 | } | ||
778 | if (status != SSH_FX_OK) | ||
779 | send_status(id, status); | ||
780 | xfree(path); | ||
781 | } | ||
782 | |||
783 | char * | ||
784 | ls_file(char *name, struct stat *st) | ||
785 | { | ||
786 | char buf[1024]; | ||
787 | snprintf(buf, sizeof buf, "0%o %d %d %qd %d %s", | ||
788 | st->st_mode, st->st_uid, st->st_gid, (long long)st->st_size,(int) st->st_mtime, | ||
789 | name); | ||
790 | return xstrdup(buf); | ||
791 | } | ||
792 | |||
793 | void | ||
794 | process_readdir(void) | ||
795 | { | ||
796 | DIR *dirp; | ||
797 | struct dirent *dp; | ||
798 | char *path; | ||
799 | int handle; | ||
800 | u_int32_t id; | ||
801 | |||
802 | id = get_int(); | ||
803 | handle = get_handle(); | ||
804 | TRACE("readdir id %d handle %d", id, handle); | ||
805 | dirp = handle_to_dir(handle); | ||
806 | path = handle_to_name(handle); | ||
807 | if (dirp == NULL || path == NULL) { | ||
808 | send_status(id, SSH_FX_FAILURE); | ||
809 | } else { | ||
810 | Attrib *a; | ||
811 | struct stat st; | ||
812 | char pathname[1024]; | ||
813 | Stat *stats; | ||
814 | int nstats = 10, count = 0, i; | ||
815 | stats = xmalloc(nstats * sizeof(Stat)); | ||
816 | while ((dp = readdir(dirp)) != NULL) { | ||
817 | if (count >= nstats) { | ||
818 | nstats *= 2; | ||
819 | stats = xrealloc(stats, nstats * sizeof(Stat)); | ||
820 | } | ||
821 | /* XXX OVERFLOW ? */ | ||
822 | snprintf(pathname, sizeof pathname, | ||
823 | "%s/%s", path, dp->d_name); | ||
824 | if (lstat(pathname, &st) < 0) | ||
825 | continue; | ||
826 | a = stat_to_attrib(&st); | ||
827 | stats[count].attrib = *a; | ||
828 | stats[count].name = xstrdup(dp->d_name); | ||
829 | stats[count].long_name = ls_file(dp->d_name, &st); | ||
830 | count++; | ||
831 | /* send up to 100 entries in one message */ | ||
832 | if (count == 100) | ||
833 | break; | ||
834 | } | ||
835 | send_names(id, count, stats); | ||
836 | for(i = 0; i < count; i++) { | ||
837 | xfree(stats[i].name); | ||
838 | xfree(stats[i].long_name); | ||
839 | } | ||
840 | xfree(stats); | ||
841 | } | ||
842 | } | ||
843 | |||
844 | void | ||
845 | process_remove(void) | ||
846 | { | ||
847 | char *name; | ||
848 | u_int32_t id; | ||
849 | int status = SSH_FX_FAILURE; | ||
850 | int ret; | ||
851 | |||
852 | id = get_int(); | ||
853 | name = get_string(NULL); | ||
854 | TRACE("remove id %d name %s", id, name); | ||
855 | ret = remove(name); | ||
856 | status = (ret == -1) ? errno_to_portable(errno) : SSH_FX_OK; | ||
857 | send_status(id, status); | ||
858 | xfree(name); | ||
859 | } | ||
860 | |||
861 | void | ||
862 | process_mkdir(void) | ||
863 | { | ||
864 | Attrib *a; | ||
865 | u_int32_t id; | ||
866 | char *name; | ||
867 | int ret, mode, status = SSH_FX_FAILURE; | ||
868 | |||
869 | id = get_int(); | ||
870 | name = get_string(NULL); | ||
871 | a = get_attrib(); | ||
872 | mode = (a->flags & SSH_FXA_HAVE_PERM) ? a->perm & 0777 : 0777; | ||
873 | TRACE("mkdir id %d name %s mode 0%o", id, name, mode); | ||
874 | ret = mkdir(name, mode); | ||
875 | status = (ret == -1) ? errno_to_portable(errno) : SSH_FX_OK; | ||
876 | send_status(id, status); | ||
877 | xfree(name); | ||
878 | } | ||
879 | |||
880 | void | ||
881 | process_rmdir(void) | ||
882 | { | ||
883 | u_int32_t id; | ||
884 | char *name; | ||
885 | int ret, status; | ||
886 | |||
887 | id = get_int(); | ||
888 | name = get_string(NULL); | ||
889 | TRACE("rmdir id %d name %s", id, name); | ||
890 | ret = rmdir(name); | ||
891 | status = (ret == -1) ? errno_to_portable(errno) : SSH_FX_OK; | ||
892 | send_status(id, status); | ||
893 | xfree(name); | ||
894 | } | ||
895 | |||
896 | void | ||
897 | process_realpath(void) | ||
898 | { | ||
899 | char resolvedname[MAXPATHLEN]; | ||
900 | u_int32_t id; | ||
901 | char *path; | ||
902 | |||
903 | id = get_int(); | ||
904 | path = get_string(NULL); | ||
905 | TRACE("realpath id %d path %s", id, path); | ||
906 | if (realpath(path, resolvedname) == NULL) { | ||
907 | send_status(id, errno_to_portable(errno)); | ||
908 | } else { | ||
909 | Stat s; | ||
910 | attrib_clear(&s.attrib); | ||
911 | s.name = s.long_name = resolvedname; | ||
912 | send_names(id, 1, &s); | ||
913 | } | ||
914 | xfree(path); | ||
915 | } | ||
916 | |||
917 | void | ||
918 | process_rename(void) | ||
919 | { | ||
920 | u_int32_t id; | ||
921 | char *oldpath, *newpath; | ||
922 | int ret, status; | ||
923 | |||
924 | id = get_int(); | ||
925 | oldpath = get_string(NULL); | ||
926 | newpath = get_string(NULL); | ||
927 | TRACE("rename id %d old %s new %s", id, oldpath, newpath); | ||
928 | ret = rename(oldpath, newpath); | ||
929 | status = (ret == -1) ? errno_to_portable(errno) : SSH_FX_OK; | ||
930 | send_status(id, status); | ||
931 | xfree(oldpath); | ||
932 | xfree(newpath); | ||
933 | } | ||
934 | |||
935 | |||
936 | /* stolen from ssh-agent */ | ||
937 | |||
938 | void | ||
939 | process(void) | ||
940 | { | ||
941 | unsigned int msg_len; | ||
942 | unsigned int type; | ||
943 | unsigned char *cp; | ||
944 | |||
945 | if (buffer_len(&iqueue) < 5) | ||
946 | return; /* Incomplete message. */ | ||
947 | cp = (unsigned char *) buffer_ptr(&iqueue); | ||
948 | msg_len = GET_32BIT(cp); | ||
949 | if (msg_len > 256 * 1024) { | ||
950 | error("bad message "); | ||
951 | exit(11); | ||
952 | } | ||
953 | if (buffer_len(&iqueue) < msg_len + 4) | ||
954 | return; | ||
955 | buffer_consume(&iqueue, 4); | ||
956 | type = buffer_get_char(&iqueue); | ||
957 | switch (type) { | ||
958 | case SSH_FXP_INIT: | ||
959 | process_init(); | ||
960 | break; | ||
961 | case SSH_FXP_OPEN: | ||
962 | process_open(); | ||
963 | break; | ||
964 | case SSH_FXP_CLOSE: | ||
965 | process_close(); | ||
966 | break; | ||
967 | case SSH_FXP_READ: | ||
968 | process_read(); | ||
969 | break; | ||
970 | case SSH_FXP_WRITE: | ||
971 | process_write(); | ||
972 | break; | ||
973 | case SSH_FXP_LSTAT: | ||
974 | process_lstat(); | ||
975 | break; | ||
976 | case SSH_FXP_FSTAT: | ||
977 | process_fstat(); | ||
978 | break; | ||
979 | case SSH_FXP_SETSTAT: | ||
980 | process_setstat(); | ||
981 | break; | ||
982 | case SSH_FXP_FSETSTAT: | ||
983 | process_fsetstat(); | ||
984 | break; | ||
985 | case SSH_FXP_OPENDIR: | ||
986 | process_opendir(); | ||
987 | break; | ||
988 | case SSH_FXP_READDIR: | ||
989 | process_readdir(); | ||
990 | break; | ||
991 | case SSH_FXP_REMOVE: | ||
992 | process_remove(); | ||
993 | break; | ||
994 | case SSH_FXP_MKDIR: | ||
995 | process_mkdir(); | ||
996 | break; | ||
997 | case SSH_FXP_RMDIR: | ||
998 | process_rmdir(); | ||
999 | break; | ||
1000 | case SSH_FXP_REALPATH: | ||
1001 | process_realpath(); | ||
1002 | break; | ||
1003 | case SSH_FXP_STAT: | ||
1004 | process_stat(); | ||
1005 | break; | ||
1006 | case SSH_FXP_RENAME: | ||
1007 | process_rename(); | ||
1008 | break; | ||
1009 | default: | ||
1010 | error("Unknown message %d", type); | ||
1011 | break; | ||
1012 | } | ||
1013 | } | ||
1014 | |||
1015 | int | ||
1016 | main(int ac, char **av) | ||
1017 | { | ||
1018 | fd_set rset, wset; | ||
1019 | int in, out, max; | ||
1020 | size_t len, olen; | ||
1021 | |||
1022 | handle_init(); | ||
1023 | |||
1024 | in = dup(STDIN_FILENO); | ||
1025 | out = dup(STDOUT_FILENO); | ||
1026 | |||
1027 | max = 0; | ||
1028 | if (in > max) | ||
1029 | max = in; | ||
1030 | if (out > max) | ||
1031 | max = out; | ||
1032 | |||
1033 | buffer_init(&iqueue); | ||
1034 | buffer_init(&oqueue); | ||
1035 | |||
1036 | for (;;) { | ||
1037 | FD_ZERO(&rset); | ||
1038 | FD_ZERO(&wset); | ||
1039 | |||
1040 | FD_SET(in, &rset); | ||
1041 | olen = buffer_len(&oqueue); | ||
1042 | if (olen > 0) | ||
1043 | FD_SET(out, &wset); | ||
1044 | |||
1045 | if (select(max+1, &rset, &wset, NULL, NULL) < 0) { | ||
1046 | if (errno == EINTR) | ||
1047 | continue; | ||
1048 | exit(2); | ||
1049 | } | ||
1050 | |||
1051 | /* copy stdin to iqueue */ | ||
1052 | if (FD_ISSET(in, &rset)) { | ||
1053 | char buf[4*4096]; | ||
1054 | len = read(in, buf, sizeof buf); | ||
1055 | if (len == 0) { | ||
1056 | debug("read eof"); | ||
1057 | exit(0); | ||
1058 | } else if (len < 0) { | ||
1059 | error("read error"); | ||
1060 | exit(1); | ||
1061 | } else { | ||
1062 | buffer_append(&iqueue, buf, len); | ||
1063 | } | ||
1064 | } | ||
1065 | /* send oqueue to stdout */ | ||
1066 | if (FD_ISSET(out, &wset)) { | ||
1067 | len = write(out, buffer_ptr(&oqueue), olen); | ||
1068 | if (len < 0) { | ||
1069 | error("write error"); | ||
1070 | exit(1); | ||
1071 | } else { | ||
1072 | buffer_consume(&oqueue, len); | ||
1073 | } | ||
1074 | } | ||
1075 | /* process requests from client */ | ||
1076 | process(); | ||
1077 | } | ||
1078 | } | ||