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