/src/libssh/src/sftp_common.c
Line | Count | Source |
1 | | /* |
2 | | * sftp_common.c - Secure FTP functions which are private and are used |
3 | | * internally by other sftp api functions spread across |
4 | | * various source files. |
5 | | * |
6 | | * This file is part of the SSH Library |
7 | | * |
8 | | * Copyright (c) 2005-2008 by Aris Adamantiadis |
9 | | * Copyright (c) 2008-2018 by Andreas Schneider <asn@cryptomilk.org> |
10 | | * |
11 | | * The SSH Library is free software; you can redistribute it and/or modify |
12 | | * it under the terms of the GNU Lesser General Public License as published by |
13 | | * the Free Software Foundation; either version 2.1 of the License, or (at your |
14 | | * option) any later version. |
15 | | * |
16 | | * The SSH Library is distributed in the hope that it will be useful, but |
17 | | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
18 | | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
19 | | * License for more details. |
20 | | * |
21 | | * You should have received a copy of the GNU Lesser General Public License |
22 | | * along with the SSH Library; see the file COPYING. If not, write to |
23 | | * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, |
24 | | * MA 02111-1307, USA. |
25 | | */ |
26 | | |
27 | | #include "config.h" |
28 | | |
29 | | #include <ctype.h> |
30 | | |
31 | | #include "libssh/sftp.h" |
32 | | #include "libssh/sftp_priv.h" |
33 | | #include "libssh/buffer.h" |
34 | | #include "libssh/session.h" |
35 | | #include "libssh/bytearray.h" |
36 | | |
37 | | #ifdef WITH_SFTP |
38 | | |
39 | | /* Buffer size maximum is 256M */ |
40 | 0 | #define SFTP_PACKET_SIZE_MAX 0x10000000 |
41 | | |
42 | | sftp_packet sftp_packet_read(sftp_session sftp) |
43 | 0 | { |
44 | 0 | uint8_t tmpbuf[4]; |
45 | 0 | uint8_t *buffer = NULL; |
46 | 0 | sftp_packet packet = sftp->read_packet; |
47 | 0 | uint32_t size; |
48 | 0 | int nread; |
49 | 0 | bool is_eof; |
50 | 0 | int rc; |
51 | |
|
52 | 0 | packet->sftp = sftp; |
53 | | |
54 | | /* |
55 | | * If the packet has a payload, then just reinit the buffer, otherwise |
56 | | * allocate a new one. |
57 | | */ |
58 | 0 | if (packet->payload != NULL) { |
59 | 0 | rc = ssh_buffer_reinit(packet->payload); |
60 | 0 | if (rc != 0) { |
61 | 0 | ssh_set_error_oom(sftp->session); |
62 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
63 | 0 | return NULL; |
64 | 0 | } |
65 | 0 | } else { |
66 | 0 | packet->payload = ssh_buffer_new(); |
67 | 0 | if (packet->payload == NULL) { |
68 | 0 | ssh_set_error_oom(sftp->session); |
69 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
70 | 0 | return NULL; |
71 | 0 | } |
72 | 0 | } |
73 | | |
74 | 0 | nread = 0; |
75 | 0 | do { |
76 | 0 | int s; |
77 | | |
78 | | /* read from channel until 4 bytes have been read or an error occurs */ |
79 | 0 | s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); |
80 | 0 | if (s < 0) { |
81 | 0 | goto error; |
82 | 0 | } else if (s == 0) { |
83 | 0 | is_eof = ssh_channel_is_eof(sftp->channel); |
84 | 0 | if (is_eof) { |
85 | 0 | ssh_set_error(sftp->session, |
86 | 0 | SSH_FATAL, |
87 | 0 | "Received EOF while reading sftp packet size"); |
88 | 0 | sftp_set_error(sftp, SSH_FX_EOF); |
89 | 0 | goto error; |
90 | 0 | } else { |
91 | 0 | ssh_set_error(sftp->session, |
92 | 0 | SSH_FATAL, |
93 | 0 | "Timeout while reading sftp packet size"); |
94 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
95 | 0 | goto error; |
96 | 0 | } |
97 | 0 | } else { |
98 | 0 | nread += s; |
99 | 0 | } |
100 | 0 | } while (nread < 4); |
101 | | |
102 | 0 | size = PULL_BE_U32(tmpbuf, 0); |
103 | 0 | if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { |
104 | 0 | ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); |
105 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
106 | 0 | goto error; |
107 | 0 | } |
108 | | |
109 | 0 | do { |
110 | 0 | nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); |
111 | 0 | if (nread < 0) { |
112 | 0 | goto error; |
113 | 0 | } else if (nread == 0) { |
114 | 0 | is_eof = ssh_channel_is_eof(sftp->channel); |
115 | 0 | if (is_eof) { |
116 | 0 | ssh_set_error(sftp->session, |
117 | 0 | SSH_FATAL, |
118 | 0 | "Received EOF while reading sftp packet type"); |
119 | 0 | sftp_set_error(sftp, SSH_FX_EOF); |
120 | 0 | goto error; |
121 | 0 | } else { |
122 | 0 | ssh_set_error(sftp->session, |
123 | 0 | SSH_FATAL, |
124 | 0 | "Timeout while reading sftp packet type"); |
125 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
126 | 0 | goto error; |
127 | 0 | } |
128 | 0 | } |
129 | 0 | } while (nread < 1); |
130 | | |
131 | 0 | packet->type = tmpbuf[0]; |
132 | | |
133 | | /* Remove the packet type size */ |
134 | 0 | size -= sizeof(uint8_t); |
135 | | |
136 | | /* Allocate the receive buffer from payload */ |
137 | 0 | buffer = ssh_buffer_allocate(packet->payload, size); |
138 | 0 | if (buffer == NULL) { |
139 | 0 | ssh_set_error_oom(sftp->session); |
140 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
141 | 0 | goto error; |
142 | 0 | } |
143 | 0 | while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { |
144 | 0 | nread = ssh_channel_read(sftp->channel, buffer, size, 0); |
145 | 0 | if (nread < 0) { |
146 | | /* TODO: check if there are cases where an error needs to be set here */ |
147 | 0 | goto error; |
148 | 0 | } |
149 | | |
150 | 0 | if (nread > 0) { |
151 | 0 | buffer += nread; |
152 | 0 | size -= nread; |
153 | 0 | } else { /* nread == 0 */ |
154 | | /* Retry the reading unless the remote was closed */ |
155 | 0 | is_eof = ssh_channel_is_eof(sftp->channel); |
156 | 0 | if (is_eof) { |
157 | 0 | ssh_set_error(sftp->session, |
158 | 0 | SSH_REQUEST_DENIED, |
159 | 0 | "Received EOF while reading sftp packet"); |
160 | 0 | sftp_set_error(sftp, SSH_FX_EOF); |
161 | 0 | goto error; |
162 | 0 | } else { |
163 | 0 | ssh_set_error(sftp->session, |
164 | 0 | SSH_FATAL, |
165 | 0 | "Timeout while reading sftp packet"); |
166 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
167 | 0 | goto error; |
168 | 0 | } |
169 | 0 | } |
170 | 0 | } |
171 | | |
172 | 0 | return packet; |
173 | 0 | error: |
174 | 0 | ssh_buffer_reinit(packet->payload); |
175 | 0 | return NULL; |
176 | 0 | } |
177 | | |
178 | | int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) |
179 | 0 | { |
180 | 0 | uint8_t header[5] = {0}; |
181 | 0 | uint32_t payload_size; |
182 | 0 | int size; |
183 | 0 | int rc; |
184 | | |
185 | | /* Add size of type */ |
186 | 0 | payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); |
187 | 0 | PUSH_BE_U32(header, 0, payload_size); |
188 | 0 | PUSH_BE_U8(header, 4, type); |
189 | |
|
190 | 0 | rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); |
191 | 0 | if (rc < 0) { |
192 | 0 | ssh_set_error_oom(sftp->session); |
193 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
194 | 0 | return -1; |
195 | 0 | } |
196 | | |
197 | 0 | size = ssh_channel_write(sftp->channel, |
198 | 0 | ssh_buffer_get(payload), |
199 | 0 | ssh_buffer_get_len(payload)); |
200 | 0 | if (size < 0) { |
201 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
202 | 0 | return -1; |
203 | 0 | } |
204 | | |
205 | 0 | if ((uint32_t)size != ssh_buffer_get_len(payload)) { |
206 | 0 | SSH_LOG(SSH_LOG_PACKET, |
207 | 0 | "Had to write %" PRIu32 " bytes, wrote only %d", |
208 | 0 | ssh_buffer_get_len(payload), |
209 | 0 | size); |
210 | 0 | } |
211 | |
|
212 | 0 | return size; |
213 | 0 | } |
214 | | |
215 | | void sftp_packet_free(sftp_packet packet) |
216 | 0 | { |
217 | 0 | if (packet == NULL) { |
218 | 0 | return; |
219 | 0 | } |
220 | | |
221 | 0 | SSH_BUFFER_FREE(packet->payload); |
222 | 0 | free(packet); |
223 | 0 | } |
224 | | |
225 | | int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) |
226 | 0 | { |
227 | 0 | uint32_t flags = (attr ? attr->flags : 0); |
228 | 0 | int rc; |
229 | |
|
230 | 0 | flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | |
231 | 0 | SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); |
232 | |
|
233 | 0 | rc = ssh_buffer_pack(buffer, "d", flags); |
234 | 0 | if (rc != SSH_OK) { |
235 | 0 | return -1; |
236 | 0 | } |
237 | | |
238 | 0 | if (attr != NULL) { |
239 | 0 | if (flags & SSH_FILEXFER_ATTR_SIZE) { |
240 | 0 | rc = ssh_buffer_pack(buffer, "q", attr->size); |
241 | 0 | if (rc != SSH_OK) { |
242 | 0 | return -1; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | 0 | if (flags & SSH_FILEXFER_ATTR_UIDGID) { |
247 | 0 | rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); |
248 | 0 | if (rc != SSH_OK) { |
249 | 0 | return -1; |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | 0 | if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { |
254 | 0 | rc = ssh_buffer_pack(buffer, "d", attr->permissions); |
255 | 0 | if (rc != SSH_OK) { |
256 | 0 | return -1; |
257 | 0 | } |
258 | 0 | } |
259 | | |
260 | 0 | if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { |
261 | 0 | rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); |
262 | 0 | if (rc != SSH_OK) { |
263 | 0 | return -1; |
264 | 0 | } |
265 | 0 | } |
266 | 0 | } |
267 | | |
268 | 0 | return 0; |
269 | 0 | } |
270 | | |
271 | | /* |
272 | | * Parse the attributes from a payload from some messages. It is coded on |
273 | | * baselines from the protocol version 4. |
274 | | * This code is more or less dead but maybe we will need it in the future. |
275 | | */ |
276 | | static sftp_attributes sftp_parse_attr_4(sftp_session sftp, |
277 | | ssh_buffer buf, |
278 | | int expectnames) |
279 | 1.51k | { |
280 | 1.51k | sftp_attributes attr = NULL; |
281 | 1.51k | ssh_string owner = NULL; |
282 | 1.51k | ssh_string group = NULL; |
283 | 1.51k | uint32_t flags = 0; |
284 | 1.51k | int ok = 0; |
285 | | |
286 | | /* unused member variable */ |
287 | 1.51k | (void) expectnames; |
288 | | |
289 | 1.51k | attr = calloc(1, sizeof(struct sftp_attributes_struct)); |
290 | 1.51k | if (attr == NULL) { |
291 | 0 | ssh_set_error_oom(sftp->session); |
292 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
293 | 0 | return NULL; |
294 | 0 | } |
295 | | |
296 | | /* This isn't really a loop, but it is like a try..catch.. */ |
297 | 1.51k | do { |
298 | 1.51k | if (ssh_buffer_get_u32(buf, &flags) != 4) { |
299 | 14 | break; |
300 | 14 | } |
301 | | |
302 | 1.49k | flags = ntohl(flags); |
303 | 1.49k | attr->flags = flags; |
304 | | |
305 | 1.49k | if (flags & SSH_FILEXFER_ATTR_SIZE) { |
306 | 406 | if (ssh_buffer_get_u64(buf, &attr->size) != 8) { |
307 | 40 | break; |
308 | 40 | } |
309 | 366 | attr->size = ntohll(attr->size); |
310 | 366 | } |
311 | | |
312 | 1.45k | if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { |
313 | 506 | owner = ssh_buffer_get_ssh_string(buf); |
314 | 506 | if (owner == NULL) { |
315 | 214 | break; |
316 | 214 | } |
317 | 292 | attr->owner = ssh_string_to_char(owner); |
318 | 292 | SSH_STRING_FREE(owner); |
319 | 292 | if (attr->owner == NULL) { |
320 | 0 | break; |
321 | 0 | } |
322 | | |
323 | 292 | group = ssh_buffer_get_ssh_string(buf); |
324 | 292 | if (group == NULL) { |
325 | 122 | break; |
326 | 122 | } |
327 | 170 | attr->group = ssh_string_to_char(group); |
328 | 170 | SSH_STRING_FREE(group); |
329 | 170 | if (attr->group == NULL) { |
330 | 0 | break; |
331 | 0 | } |
332 | 170 | } |
333 | | |
334 | 1.12k | if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { |
335 | 292 | if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { |
336 | 20 | break; |
337 | 20 | } |
338 | 272 | attr->permissions = ntohl(attr->permissions); |
339 | | |
340 | | /* FIXME on windows! */ |
341 | 272 | switch (attr->permissions & SSH_S_IFMT) { |
342 | 10 | case SSH_S_IFSOCK: |
343 | 22 | case SSH_S_IFBLK: |
344 | 32 | case SSH_S_IFCHR: |
345 | 40 | case SSH_S_IFIFO: |
346 | 40 | attr->type = SSH_FILEXFER_TYPE_SPECIAL; |
347 | 40 | break; |
348 | 6 | case SSH_S_IFLNK: |
349 | 6 | attr->type = SSH_FILEXFER_TYPE_SYMLINK; |
350 | 6 | break; |
351 | 16 | case SSH_S_IFREG: |
352 | 16 | attr->type = SSH_FILEXFER_TYPE_REGULAR; |
353 | 16 | break; |
354 | 2 | case SSH_S_IFDIR: |
355 | 2 | attr->type = SSH_FILEXFER_TYPE_DIRECTORY; |
356 | 2 | break; |
357 | 208 | default: |
358 | 208 | attr->type = SSH_FILEXFER_TYPE_UNKNOWN; |
359 | 208 | break; |
360 | 272 | } |
361 | 272 | } |
362 | | |
363 | 1.10k | if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { |
364 | 228 | if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { |
365 | 24 | break; |
366 | 24 | } |
367 | 204 | attr->atime64 = ntohll(attr->atime64); |
368 | | |
369 | 204 | if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { |
370 | 132 | if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { |
371 | 8 | break; |
372 | 8 | } |
373 | 124 | attr->atime_nseconds = ntohl(attr->atime_nseconds); |
374 | 124 | } |
375 | 204 | } |
376 | | |
377 | 1.06k | if (flags & SSH_FILEXFER_ATTR_CREATETIME) { |
378 | 228 | if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { |
379 | 30 | break; |
380 | 30 | } |
381 | 198 | attr->createtime = ntohll(attr->createtime); |
382 | | |
383 | 198 | if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { |
384 | 150 | if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { |
385 | 6 | break; |
386 | 6 | } |
387 | 144 | attr->createtime_nseconds = ntohl(attr->createtime_nseconds); |
388 | 144 | } |
389 | 198 | } |
390 | | |
391 | 1.03k | if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { |
392 | 204 | if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { |
393 | 20 | break; |
394 | 20 | } |
395 | 184 | attr->mtime64 = ntohll(attr->mtime64); |
396 | | |
397 | 184 | if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { |
398 | 132 | if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { |
399 | 6 | break; |
400 | 6 | } |
401 | 126 | attr->mtime_nseconds = ntohl(attr->mtime_nseconds); |
402 | 126 | } |
403 | 184 | } |
404 | | |
405 | 1.00k | if (flags & SSH_FILEXFER_ATTR_ACL) { |
406 | 212 | if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { |
407 | 80 | break; |
408 | 80 | } |
409 | 212 | } |
410 | | |
411 | 926 | if (flags & SSH_FILEXFER_ATTR_EXTENDED) { |
412 | 654 | if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { |
413 | 138 | break; |
414 | 138 | } |
415 | 516 | attr->extended_count = ntohl(attr->extended_count); |
416 | | |
417 | 224k | while (attr->extended_count && |
418 | 224k | (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && |
419 | 224k | (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { |
420 | 224k | attr->extended_count--; |
421 | | /* just ignore the extensions -- we can't interpret them */ |
422 | 224k | SSH_STRING_FREE(attr->extended_type); |
423 | 224k | SSH_STRING_FREE(attr->extended_data); |
424 | 224k | } |
425 | | |
426 | 516 | if (attr->extended_count) { |
427 | 462 | break; |
428 | 462 | } |
429 | 516 | } |
430 | 326 | ok = 1; |
431 | 326 | } while (0); |
432 | | |
433 | 1.51k | if (ok == 0) { |
434 | | /* break issued somewhere */ |
435 | 1.18k | SSH_STRING_FREE(attr->acl); |
436 | 1.18k | SSH_STRING_FREE(attr->extended_type); |
437 | 1.18k | SSH_STRING_FREE(attr->extended_data); |
438 | 1.18k | SAFE_FREE(attr->owner); |
439 | 1.18k | SAFE_FREE(attr->group); |
440 | 1.18k | SAFE_FREE(attr); |
441 | | |
442 | 1.18k | ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); |
443 | | |
444 | 1.18k | return NULL; |
445 | 1.18k | } |
446 | | |
447 | 326 | return attr; |
448 | 1.51k | } |
449 | | |
450 | | enum sftp_longname_field_e { |
451 | | SFTP_LONGNAME_PERM = 0, |
452 | | SFTP_LONGNAME_FIXME, |
453 | | SFTP_LONGNAME_OWNER, |
454 | | SFTP_LONGNAME_GROUP, |
455 | | SFTP_LONGNAME_SIZE, |
456 | | SFTP_LONGNAME_DATE, |
457 | | SFTP_LONGNAME_TIME, |
458 | | SFTP_LONGNAME_NAME, |
459 | | }; |
460 | | |
461 | | static char * sftp_parse_longname(const char *longname, |
462 | | enum sftp_longname_field_e longname_field) |
463 | 0 | { |
464 | 0 | const char *p = NULL, *q = NULL; |
465 | 0 | size_t len, field = 0; |
466 | |
|
467 | 0 | if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || |
468 | 0 | longname_field > SFTP_LONGNAME_NAME) { |
469 | 0 | return NULL; |
470 | 0 | } |
471 | | |
472 | 0 | p = longname; |
473 | | /* |
474 | | * Find the beginning of the field which is specified |
475 | | * by sftp_longname_field_e. |
476 | | */ |
477 | 0 | while (*p != '\0' && field != longname_field) { |
478 | 0 | if (isspace(*p)) { |
479 | 0 | field++; |
480 | 0 | p++; |
481 | 0 | while (*p != '\0' && isspace(*p)) { |
482 | 0 | p++; |
483 | 0 | } |
484 | 0 | } else { |
485 | 0 | p++; |
486 | 0 | } |
487 | 0 | } |
488 | | |
489 | | /* If we reached NULL before we got our field fail */ |
490 | 0 | if (field != longname_field) { |
491 | 0 | return NULL; |
492 | 0 | } |
493 | | |
494 | 0 | q = p; |
495 | 0 | while (*q != '\0' && !isspace(*q)) { |
496 | 0 | q++; |
497 | 0 | } |
498 | |
|
499 | 0 | len = q - p; |
500 | |
|
501 | 0 | return strndup(p, len); |
502 | 0 | } |
503 | | |
504 | | /* sftp version 0-3 code. It is different from the v4 */ |
505 | | /* maybe a paste of the draft is better than the code */ |
506 | | /* |
507 | | uint32 flags |
508 | | uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE |
509 | | uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID |
510 | | uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID |
511 | | uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS |
512 | | uint32 atime present only if flag SSH_FILEXFER_ACMODTIME |
513 | | uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME |
514 | | uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED |
515 | | string extended_type |
516 | | string extended_data |
517 | | ... more extended data (extended_type - extended_data pairs), |
518 | | so that number of pairs equals extended_count */ |
519 | | static sftp_attributes sftp_parse_attr_3(sftp_session sftp, |
520 | | ssh_buffer buf, |
521 | | int expectname) |
522 | 1.51k | { |
523 | 1.51k | sftp_attributes attr; |
524 | 1.51k | int rc; |
525 | | |
526 | 1.51k | attr = calloc(1, sizeof(struct sftp_attributes_struct)); |
527 | 1.51k | if (attr == NULL) { |
528 | 0 | ssh_set_error_oom(sftp->session); |
529 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
530 | 0 | return NULL; |
531 | 0 | } |
532 | | |
533 | 1.51k | if (expectname) { |
534 | 755 | rc = ssh_buffer_unpack(buf, "ss", |
535 | 755 | &attr->name, |
536 | 755 | &attr->longname); |
537 | 755 | if (rc != SSH_OK){ |
538 | 687 | goto error; |
539 | 687 | } |
540 | 68 | SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); |
541 | | |
542 | | /* Set owner and group if we talk to openssh and have the longname */ |
543 | 68 | if (ssh_get_openssh_version(sftp->session)) { |
544 | 0 | attr->owner = sftp_parse_longname(attr->longname, |
545 | 0 | SFTP_LONGNAME_OWNER); |
546 | 0 | if (attr->owner == NULL) { |
547 | 0 | goto error; |
548 | 0 | } |
549 | | |
550 | 0 | attr->group = sftp_parse_longname(attr->longname, |
551 | 0 | SFTP_LONGNAME_GROUP); |
552 | 0 | if (attr->group == NULL) { |
553 | 0 | goto error; |
554 | 0 | } |
555 | 0 | } |
556 | 68 | } |
557 | | |
558 | 823 | rc = ssh_buffer_unpack(buf, "d", &attr->flags); |
559 | 823 | if (rc != SSH_OK){ |
560 | 14 | goto error; |
561 | 14 | } |
562 | 809 | SSH_LOG(SSH_LOG_DEBUG, "Flags: %.8" PRIx32, attr->flags); |
563 | | |
564 | 809 | if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { |
565 | 229 | rc = ssh_buffer_unpack(buf, "q", &attr->size); |
566 | 229 | if(rc != SSH_OK) { |
567 | 23 | goto error; |
568 | 23 | } |
569 | 206 | SSH_LOG(SSH_LOG_DEBUG, "Size: %" PRIu64, (uint64_t)attr->size); |
570 | 206 | } |
571 | | |
572 | 786 | if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { |
573 | 295 | rc = ssh_buffer_unpack(buf, "dd", |
574 | 295 | &attr->uid, |
575 | 295 | &attr->gid); |
576 | 295 | if (rc != SSH_OK) { |
577 | 51 | goto error; |
578 | 51 | } |
579 | 295 | } |
580 | | |
581 | 735 | if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { |
582 | 241 | rc = ssh_buffer_unpack(buf, "d", &attr->permissions); |
583 | 241 | if (rc != SSH_OK) { |
584 | 17 | goto error; |
585 | 17 | } |
586 | | |
587 | 224 | switch (attr->permissions & SSH_S_IFMT) { |
588 | 3 | case SSH_S_IFSOCK: |
589 | 6 | case SSH_S_IFBLK: |
590 | 9 | case SSH_S_IFCHR: |
591 | 13 | case SSH_S_IFIFO: |
592 | 13 | attr->type = SSH_FILEXFER_TYPE_SPECIAL; |
593 | 13 | break; |
594 | 6 | case SSH_S_IFLNK: |
595 | 6 | attr->type = SSH_FILEXFER_TYPE_SYMLINK; |
596 | 6 | break; |
597 | 11 | case SSH_S_IFREG: |
598 | 11 | attr->type = SSH_FILEXFER_TYPE_REGULAR; |
599 | 11 | break; |
600 | 1 | case SSH_S_IFDIR: |
601 | 1 | attr->type = SSH_FILEXFER_TYPE_DIRECTORY; |
602 | 1 | break; |
603 | 193 | default: |
604 | 193 | attr->type = SSH_FILEXFER_TYPE_UNKNOWN; |
605 | 193 | break; |
606 | 224 | } |
607 | 224 | } |
608 | | |
609 | 718 | if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { |
610 | 207 | rc = ssh_buffer_unpack(buf, "dd", |
611 | 207 | &attr->atime, |
612 | 207 | &attr->mtime); |
613 | 207 | if (rc != SSH_OK) { |
614 | 29 | goto error; |
615 | 29 | } |
616 | 207 | } |
617 | | |
618 | 689 | if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { |
619 | 482 | rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); |
620 | 482 | if (rc != SSH_OK) { |
621 | 61 | goto error; |
622 | 61 | } |
623 | | |
624 | 421 | if (attr->extended_count > 0) { |
625 | 384 | rc = ssh_buffer_unpack(buf, "ss", |
626 | 384 | &attr->extended_type, |
627 | 384 | &attr->extended_data); |
628 | 384 | if (rc != SSH_OK) { |
629 | 199 | goto error; |
630 | 199 | } |
631 | 185 | attr->extended_count--; |
632 | 185 | } |
633 | | /* just ignore the remaining extensions */ |
634 | | |
635 | 115k | while (attr->extended_count > 0) { |
636 | 115k | ssh_string tmp1,tmp2; |
637 | 115k | rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); |
638 | 115k | if (rc != SSH_OK){ |
639 | 180 | goto error; |
640 | 180 | } |
641 | 115k | SAFE_FREE(tmp1); |
642 | 115k | SAFE_FREE(tmp2); |
643 | 115k | attr->extended_count--; |
644 | 115k | } |
645 | 222 | } |
646 | | |
647 | 249 | return attr; |
648 | | |
649 | 1.26k | error: |
650 | 1.26k | SSH_STRING_FREE(attr->extended_type); |
651 | 1.26k | SSH_STRING_FREE(attr->extended_data); |
652 | 1.26k | SAFE_FREE(attr->name); |
653 | 1.26k | SAFE_FREE(attr->longname); |
654 | 1.26k | SAFE_FREE(attr->owner); |
655 | 1.26k | SAFE_FREE(attr->group); |
656 | 1.26k | SAFE_FREE(attr); |
657 | 1.26k | ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); |
658 | 1.26k | sftp_set_error(sftp, SSH_FX_FAILURE); |
659 | | |
660 | 1.26k | return NULL; |
661 | 689 | } |
662 | | |
663 | | sftp_attributes sftp_parse_attr(sftp_session session, |
664 | | ssh_buffer buf, |
665 | | int expectname) |
666 | 3.02k | { |
667 | 3.02k | switch (session->version) { |
668 | 1.51k | case 4: |
669 | 1.51k | return sftp_parse_attr_4(session, buf, expectname); |
670 | 1.51k | case 3: |
671 | 1.51k | case 2: |
672 | 1.51k | case 1: |
673 | 1.51k | case 0: |
674 | 1.51k | return sftp_parse_attr_3(session, buf, expectname); |
675 | 0 | default: |
676 | 0 | ssh_set_error(session->session, SSH_FATAL, |
677 | 0 | "Version %d unsupported by client", |
678 | 0 | session->server_version); |
679 | 0 | return NULL; |
680 | 3.02k | } |
681 | | |
682 | 0 | return NULL; |
683 | 3.02k | } |
684 | | |
685 | | void sftp_set_error(sftp_session sftp, int errnum) |
686 | 1.26k | { |
687 | 1.26k | if (sftp != NULL) { |
688 | 1.26k | sftp->errnum = errnum; |
689 | 1.26k | } |
690 | 1.26k | } |
691 | | |
692 | | void sftp_message_free(sftp_message msg) |
693 | 0 | { |
694 | 0 | if (msg == NULL) { |
695 | 0 | return; |
696 | 0 | } |
697 | | |
698 | 0 | SSH_BUFFER_FREE(msg->payload); |
699 | 0 | SAFE_FREE(msg); |
700 | 0 | } |
701 | | |
702 | | static sftp_request_queue request_queue_new(sftp_message msg) |
703 | 0 | { |
704 | 0 | sftp_request_queue queue = NULL; |
705 | |
|
706 | 0 | queue = calloc(1, sizeof(struct sftp_request_queue_struct)); |
707 | 0 | if (queue == NULL) { |
708 | 0 | ssh_set_error_oom(msg->sftp->session); |
709 | 0 | sftp_set_error(msg->sftp, SSH_FX_FAILURE); |
710 | 0 | return NULL; |
711 | 0 | } |
712 | | |
713 | 0 | queue->message = msg; |
714 | |
|
715 | 0 | return queue; |
716 | 0 | } |
717 | | |
718 | | static void request_queue_free(sftp_request_queue queue) |
719 | 0 | { |
720 | 0 | if (queue == NULL) { |
721 | 0 | return; |
722 | 0 | } |
723 | | |
724 | 0 | ZERO_STRUCTP(queue); |
725 | 0 | SAFE_FREE(queue); |
726 | 0 | } |
727 | | |
728 | | static int |
729 | | sftp_enqueue(sftp_session sftp, sftp_message msg) |
730 | 0 | { |
731 | 0 | sftp_request_queue queue = NULL; |
732 | 0 | sftp_request_queue ptr; |
733 | |
|
734 | 0 | queue = request_queue_new(msg); |
735 | 0 | if (queue == NULL) { |
736 | 0 | return -1; |
737 | 0 | } |
738 | | |
739 | 0 | SSH_LOG(SSH_LOG_PACKET, |
740 | 0 | "Queued msg id %" PRIu32 " type %d", |
741 | 0 | msg->id, msg->packet_type); |
742 | |
|
743 | 0 | if(sftp->queue == NULL) { |
744 | 0 | sftp->queue = queue; |
745 | 0 | } else { |
746 | 0 | ptr = sftp->queue; |
747 | 0 | while(ptr->next) { |
748 | 0 | ptr=ptr->next; /* find end of linked list */ |
749 | 0 | } |
750 | 0 | ptr->next = queue; /* add it on bottom */ |
751 | 0 | } |
752 | |
|
753 | 0 | return 0; |
754 | 0 | } |
755 | | |
756 | | /* |
757 | | * Pulls a message from the queue based on the ID. |
758 | | * Returns NULL if no message has been found. |
759 | | */ |
760 | | sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) |
761 | 0 | { |
762 | 0 | sftp_request_queue prev = NULL; |
763 | 0 | sftp_request_queue queue; |
764 | 0 | sftp_message msg; |
765 | |
|
766 | 0 | if(sftp->queue == NULL) { |
767 | 0 | return NULL; |
768 | 0 | } |
769 | | |
770 | 0 | queue = sftp->queue; |
771 | 0 | while (queue) { |
772 | 0 | if (queue->message->id == id) { |
773 | | /* remove from queue */ |
774 | 0 | if (prev == NULL) { |
775 | 0 | sftp->queue = queue->next; |
776 | 0 | } else { |
777 | 0 | prev->next = queue->next; |
778 | 0 | } |
779 | 0 | msg = queue->message; |
780 | 0 | request_queue_free(queue); |
781 | 0 | SSH_LOG(SSH_LOG_PACKET, |
782 | 0 | "Dequeued msg id %" PRIu32 " type %d", |
783 | 0 | msg->id, |
784 | 0 | msg->packet_type); |
785 | 0 | return msg; |
786 | 0 | } |
787 | 0 | prev = queue; |
788 | 0 | queue = queue->next; |
789 | 0 | } |
790 | | |
791 | 0 | return NULL; |
792 | 0 | } |
793 | | |
794 | | static sftp_message sftp_get_message(sftp_packet packet) |
795 | 0 | { |
796 | 0 | sftp_session sftp = packet->sftp; |
797 | 0 | sftp_message msg = NULL; |
798 | 0 | int rc; |
799 | |
|
800 | 0 | switch (packet->type) { |
801 | 0 | case SSH_FXP_STATUS: |
802 | 0 | case SSH_FXP_HANDLE: |
803 | 0 | case SSH_FXP_DATA: |
804 | 0 | case SSH_FXP_ATTRS: |
805 | 0 | case SSH_FXP_NAME: |
806 | 0 | case SSH_FXP_EXTENDED_REPLY: |
807 | 0 | break; |
808 | 0 | default: |
809 | 0 | ssh_set_error(packet->sftp->session, |
810 | 0 | SSH_FATAL, |
811 | 0 | "Unknown packet type %d", |
812 | 0 | packet->type); |
813 | 0 | sftp_set_error(packet->sftp, SSH_FX_FAILURE); |
814 | 0 | return NULL; |
815 | 0 | } |
816 | | |
817 | 0 | msg = calloc(1, sizeof(struct sftp_message_struct)); |
818 | 0 | if (msg == NULL) { |
819 | 0 | ssh_set_error_oom(sftp->session); |
820 | 0 | sftp_set_error(packet->sftp, SSH_FX_FAILURE); |
821 | 0 | return NULL; |
822 | 0 | } |
823 | | |
824 | 0 | msg->sftp = packet->sftp; |
825 | 0 | msg->packet_type = packet->type; |
826 | | |
827 | | /* Move the payload from the packet to the message */ |
828 | 0 | msg->payload = packet->payload; |
829 | 0 | packet->payload = NULL; |
830 | |
|
831 | 0 | rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); |
832 | 0 | if (rc != SSH_OK) { |
833 | 0 | ssh_set_error(packet->sftp->session, SSH_FATAL, |
834 | 0 | "Invalid packet %d: no ID", packet->type); |
835 | 0 | sftp_message_free(msg); |
836 | 0 | sftp_set_error(packet->sftp, SSH_FX_FAILURE); |
837 | 0 | return NULL; |
838 | 0 | } |
839 | | |
840 | 0 | SSH_LOG(SSH_LOG_PACKET, |
841 | 0 | "Packet with id %" PRIu32 " type %d", |
842 | 0 | msg->id, |
843 | 0 | msg->packet_type); |
844 | |
|
845 | 0 | return msg; |
846 | 0 | } |
847 | | |
848 | | int sftp_read_and_dispatch(sftp_session sftp) |
849 | 0 | { |
850 | 0 | sftp_packet packet = NULL; |
851 | 0 | sftp_message msg = NULL; |
852 | |
|
853 | 0 | packet = sftp_packet_read(sftp); |
854 | 0 | if (packet == NULL) { |
855 | | /* something nasty happened reading the packet */ |
856 | 0 | return -1; |
857 | 0 | } |
858 | | |
859 | 0 | msg = sftp_get_message(packet); |
860 | 0 | if (msg == NULL) { |
861 | 0 | return -1; |
862 | 0 | } |
863 | | |
864 | 0 | if (sftp_enqueue(sftp, msg) < 0) { |
865 | 0 | sftp_message_free(msg); |
866 | 0 | return -1; |
867 | 0 | } |
868 | | |
869 | 0 | return 0; |
870 | 0 | } |
871 | | |
872 | | int sftp_recv_response_msg(sftp_session sftp, |
873 | | uint32_t id, |
874 | | bool blocking, |
875 | | sftp_message *msg_ptr) |
876 | 0 | { |
877 | 0 | sftp_message msg = NULL; |
878 | 0 | int rc; |
879 | |
|
880 | 0 | if (sftp == NULL) { |
881 | 0 | return SSH_ERROR; |
882 | 0 | } |
883 | | |
884 | 0 | if (msg_ptr == NULL) { |
885 | 0 | ssh_set_error_invalid(sftp->session); |
886 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
887 | 0 | return SSH_ERROR; |
888 | 0 | } |
889 | | |
890 | 0 | SSH_LOG(SSH_LOG_PACKET, |
891 | 0 | "Trying to receive response of request id %" PRIu32 " in %s mode", |
892 | 0 | id, |
893 | 0 | blocking ? "blocking" : "non-blocking"); |
894 | | |
895 | | /* |
896 | | * We deliberately check the queue first for the response before |
897 | | * polling/blocking on the channel. The reason for this approach is |
898 | | * explained by the following example (And a similar scenario can occur when |
899 | | * the async sftp aio API is used, because it provides the control of which |
900 | | * responses to receive (and in what order) to the user via the |
901 | | * sftp_aio_wait_*() functions) |
902 | | * |
903 | | * Its possible that while this function is trying to receive some |
904 | | * specific response (based on request id), other responses have already |
905 | | * arrived (or may arrive) before that specific response on the channel. In |
906 | | * that case, this function would collect those other responses from the |
907 | | * channel, add them to the sftp response queue (using |
908 | | * sftp_read_and_dipatch()) and finally provide the caller with the |
909 | | * required specific response. |
910 | | * |
911 | | * Now, whenever the caller will call this function again to get one of |
912 | | * those other responses, it won't be on the channel, instead it would be |
913 | | * present in the queue. |
914 | | * |
915 | | * Assuming that no new response ever comes on the channel, if we don't |
916 | | * check the queue first and instead: |
917 | | * - (In non blocking mode) poll on the channel, then we'd always get 0 |
918 | | * bytes of data and return SSH_AGAIN. |
919 | | * - (In blocking mode) wait on the channel, then |
920 | | * sftp_read_and_dispatch() would block infinitely by default if the |
921 | | * user has not set any timeout. |
922 | | * |
923 | | * Hence checking the queue for the response first and if not found there, |
924 | | * polling/blocking on the channel is advised. |
925 | | */ |
926 | 0 | while (msg == NULL) { |
927 | | /* |
928 | | * Before trying to poll/block on the channel for data, probe the queue |
929 | | * to check whether the response is already present in it. |
930 | | */ |
931 | 0 | msg = sftp_dequeue(sftp, id); |
932 | 0 | if (msg != NULL) { |
933 | 0 | break; |
934 | 0 | } |
935 | | |
936 | 0 | if (!blocking) { |
937 | 0 | rc = ssh_channel_poll(sftp->channel, 0); |
938 | 0 | if (rc == SSH_ERROR) { |
939 | 0 | sftp_set_error(sftp, SSH_FX_FAILURE); |
940 | 0 | return SSH_ERROR; |
941 | 0 | } |
942 | | |
943 | 0 | if (rc == 0) { |
944 | | /* nothing available and we cannot block */ |
945 | 0 | return SSH_AGAIN; |
946 | 0 | } |
947 | 0 | } |
948 | | |
949 | 0 | rc = sftp_read_and_dispatch(sftp); |
950 | 0 | if (rc == -1) { |
951 | | /* something nasty has happened */ |
952 | 0 | return SSH_ERROR; |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | 0 | *msg_ptr = msg; |
957 | 0 | return SSH_OK; |
958 | 0 | } |
959 | | |
960 | | sftp_status_message parse_status_msg(sftp_message msg) |
961 | 0 | { |
962 | 0 | sftp_status_message status = NULL; |
963 | 0 | int rc; |
964 | |
|
965 | 0 | if (msg->packet_type != SSH_FXP_STATUS) { |
966 | 0 | ssh_set_error(msg->sftp->session, SSH_FATAL, |
967 | 0 | "Not a ssh_fxp_status message passed in!"); |
968 | 0 | sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); |
969 | 0 | return NULL; |
970 | 0 | } |
971 | | |
972 | 0 | status = calloc(1, sizeof(struct sftp_status_message_struct)); |
973 | 0 | if (status == NULL) { |
974 | 0 | ssh_set_error_oom(msg->sftp->session); |
975 | 0 | sftp_set_error(msg->sftp, SSH_FX_FAILURE); |
976 | 0 | return NULL; |
977 | 0 | } |
978 | | |
979 | 0 | status->id = msg->id; |
980 | 0 | rc = ssh_buffer_unpack(msg->payload, "d", |
981 | 0 | &status->status); |
982 | 0 | if (rc != SSH_OK) { |
983 | 0 | SAFE_FREE(status); |
984 | 0 | ssh_set_error(msg->sftp->session, SSH_FATAL, |
985 | 0 | "Invalid SSH_FXP_STATUS message"); |
986 | 0 | sftp_set_error(msg->sftp, SSH_FX_FAILURE); |
987 | 0 | return NULL; |
988 | 0 | } |
989 | | |
990 | 0 | rc = ssh_buffer_unpack(msg->payload, "ss", |
991 | 0 | &status->errormsg, |
992 | 0 | &status->langmsg); |
993 | |
|
994 | 0 | if (rc != SSH_OK && msg->sftp->version >= 3) { |
995 | 0 | SSH_LOG(SSH_LOG_WARN, |
996 | 0 | "Invalid SSH_FXP_STATUS message. Missing error message."); |
997 | 0 | } |
998 | |
|
999 | 0 | if (status->errormsg == NULL) |
1000 | 0 | status->errormsg = strdup("No error message in packet"); |
1001 | |
|
1002 | 0 | if (status->langmsg == NULL) |
1003 | 0 | status->langmsg = strdup(""); |
1004 | |
|
1005 | 0 | if (status->errormsg == NULL || status->langmsg == NULL) { |
1006 | 0 | ssh_set_error_oom(msg->sftp->session); |
1007 | 0 | sftp_set_error(msg->sftp, SSH_FX_FAILURE); |
1008 | 0 | status_msg_free(status); |
1009 | 0 | return NULL; |
1010 | 0 | } |
1011 | | |
1012 | 0 | return status; |
1013 | 0 | } |
1014 | | |
1015 | | void status_msg_free(sftp_status_message status) |
1016 | 0 | { |
1017 | 0 | if (status == NULL) { |
1018 | 0 | return; |
1019 | 0 | } |
1020 | | |
1021 | 0 | SAFE_FREE(status->errormsg); |
1022 | 0 | SAFE_FREE(status->langmsg); |
1023 | | SAFE_FREE(status); |
1024 | 0 | } |
1025 | | |
1026 | | #endif /* WITH_SFTP */ |