Fix various trivial compiler warnings
[u/mdw/putty] / sftp.c
1 /*
2 * sftp.c: SFTP generic client code.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <assert.h>
9
10 #include "int64.h"
11 #include "sftp.h"
12
13 #define smalloc malloc
14 #define srealloc realloc
15 #define sfree free
16
17 #define GET_32BIT(cp) \
18 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
19 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
20 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
21 ((unsigned long)(unsigned char)(cp)[3]))
22
23 #define PUT_32BIT(cp, value) { \
24 (cp)[0] = (unsigned char)((value) >> 24); \
25 (cp)[1] = (unsigned char)((value) >> 16); \
26 (cp)[2] = (unsigned char)((value) >> 8); \
27 (cp)[3] = (unsigned char)(value); }
28
29 struct sftp_packet {
30 char *data;
31 int length, maxlen;
32 int savedpos;
33 int type;
34 };
35
36 /* ----------------------------------------------------------------------
37 * SFTP packet construction functions.
38 */
39 static void sftp_pkt_ensure(struct sftp_packet *pkt, int length) {
40 if (pkt->maxlen < length) {
41 pkt->maxlen = length + 256;
42 pkt->data = srealloc(pkt->data, pkt->maxlen);
43 }
44 }
45 static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len) {
46 pkt->length += len;
47 sftp_pkt_ensure(pkt, pkt->length);
48 memcpy(pkt->data+pkt->length-len, data, len);
49 }
50 static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) {
51 sftp_pkt_adddata(pkt, &byte, 1);
52 }
53 static struct sftp_packet *sftp_pkt_init(int pkt_type) {
54 struct sftp_packet *pkt;
55 pkt = smalloc(sizeof(struct sftp_packet));
56 pkt->data = NULL;
57 pkt->savedpos = -1;
58 pkt->length = 0;
59 pkt->maxlen = 0;
60 sftp_pkt_addbyte(pkt, (unsigned char)pkt_type);
61 return pkt;
62 }
63 static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) {
64 sftp_pkt_adddata(pkt, &value, 1);
65 }
66 static void sftp_pkt_adduint32(struct sftp_packet *pkt, unsigned long value) {
67 unsigned char x[4];
68 PUT_32BIT(x, value);
69 sftp_pkt_adddata(pkt, x, 4);
70 }
71 static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value) {
72 unsigned char x[8];
73 PUT_32BIT(x, value.hi);
74 PUT_32BIT(x+4, value.lo);
75 sftp_pkt_adddata(pkt, x, 8);
76 }
77 static void sftp_pkt_addstring_start(struct sftp_packet *pkt) {
78 sftp_pkt_adduint32(pkt, 0);
79 pkt->savedpos = pkt->length;
80 }
81 static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data) {
82 sftp_pkt_adddata(pkt, data, strlen(data));
83 PUT_32BIT(pkt->data + pkt->savedpos - 4,
84 pkt->length - pkt->savedpos);
85 }
86 static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
87 char *data, int len) {
88 sftp_pkt_adddata(pkt, data, len);
89 PUT_32BIT(pkt->data + pkt->savedpos - 4,
90 pkt->length - pkt->savedpos);
91 }
92 static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data) {
93 sftp_pkt_addstring_start(pkt);
94 sftp_pkt_addstring_str(pkt, data);
95 }
96
97 /* ----------------------------------------------------------------------
98 * SFTP packet decode functions.
99 */
100
101 static unsigned char sftp_pkt_getbyte(struct sftp_packet *pkt) {
102 unsigned char value;
103 if (pkt->length - pkt->savedpos < 1)
104 return 0; /* arrgh, no way to decline (FIXME?) */
105 value = (unsigned char) pkt->data[pkt->savedpos];
106 pkt->savedpos++;
107 return value;
108 }
109 static unsigned long sftp_pkt_getuint32(struct sftp_packet *pkt) {
110 unsigned long value;
111 if (pkt->length - pkt->savedpos < 4)
112 return 0; /* arrgh, no way to decline (FIXME?) */
113 value = GET_32BIT(pkt->data+pkt->savedpos);
114 pkt->savedpos += 4;
115 return value;
116 }
117 static void sftp_pkt_getstring(struct sftp_packet *pkt,
118 char **p, int *length) {
119 *p = NULL;
120 if (pkt->length - pkt->savedpos < 4)
121 return;
122 *length = GET_32BIT(pkt->data+pkt->savedpos);
123 pkt->savedpos += 4;
124 if (pkt->length - pkt->savedpos < *length)
125 return;
126 *p = pkt->data+pkt->savedpos;
127 pkt->savedpos += *length;
128 }
129 static struct fxp_attrs sftp_pkt_getattrs(struct sftp_packet *pkt) {
130 struct fxp_attrs ret;
131 ret.flags = sftp_pkt_getuint32(pkt);
132 if (ret.flags & SSH_FILEXFER_ATTR_SIZE) {
133 unsigned long hi, lo;
134 hi = sftp_pkt_getuint32(pkt);
135 lo = sftp_pkt_getuint32(pkt);
136 ret.size = uint64_make(hi, lo);
137 }
138 if (ret.flags & SSH_FILEXFER_ATTR_UIDGID) {
139 ret.uid = sftp_pkt_getuint32(pkt);
140 ret.gid = sftp_pkt_getuint32(pkt);
141 }
142 if (ret.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
143 ret.permissions = sftp_pkt_getuint32(pkt);
144 }
145 if (ret.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
146 ret.atime = sftp_pkt_getuint32(pkt);
147 ret.mtime = sftp_pkt_getuint32(pkt);
148 }
149 if (ret.flags & SSH_FILEXFER_ATTR_EXTENDED) {
150 int count;
151 count = sftp_pkt_getuint32(pkt);
152 while (count--) {
153 char *str;
154 int len;
155 /*
156 * We should try to analyse these, if we ever find one
157 * we recognise.
158 */
159 sftp_pkt_getstring(pkt, &str, &len);
160 sftp_pkt_getstring(pkt, &str, &len);
161 }
162 }
163 return ret;
164 }
165 static void sftp_pkt_free(struct sftp_packet *pkt) {
166 if (pkt->data) sfree(pkt->data);
167 sfree(pkt);
168 }
169
170 /* ----------------------------------------------------------------------
171 * Send and receive packet functions.
172 */
173 int sftp_send(struct sftp_packet *pkt) {
174 int ret;
175 char x[4];
176 PUT_32BIT(x, pkt->length);
177 ret = (sftp_senddata(x, 4) &&
178 sftp_senddata(pkt->data, pkt->length));
179 sftp_pkt_free(pkt);
180 return ret;
181 }
182 struct sftp_packet *sftp_recv(void) {
183 struct sftp_packet *pkt;
184 char x[4];
185
186 if (!sftp_recvdata(x, 4))
187 return NULL;
188
189 pkt = smalloc(sizeof(struct sftp_packet));
190 pkt->savedpos = 0;
191 pkt->length = pkt->maxlen = GET_32BIT(x);
192 pkt->data = smalloc(pkt->length);
193
194 if (!sftp_recvdata(pkt->data, pkt->length)) {
195 sftp_pkt_free(pkt);
196 return NULL;
197 }
198
199 pkt->type = sftp_pkt_getbyte(pkt);
200
201 return pkt;
202 }
203
204 /* ----------------------------------------------------------------------
205 * String handling routines.
206 */
207
208 static char *mkstr(char *s, int len) {
209 char *p = smalloc(len+1);
210 memcpy(p, s, len);
211 p[len] = '\0';
212 return p;
213 }
214
215 /* ----------------------------------------------------------------------
216 * SFTP primitives.
217 */
218
219 static const char *fxp_error_message;
220 static int fxp_errtype;
221
222 /*
223 * Deal with (and free) an FXP_STATUS packet. Return 1 if
224 * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
225 * Also place the status into fxp_errtype.
226 */
227 static int fxp_got_status(struct sftp_packet *pktin) {
228 static const char *const messages[] = {
229 /* SSH_FX_OK. The only time we will display a _message_ for this
230 * is if we were expecting something other than FXP_STATUS on
231 * success, so this is actually an error message! */
232 "unexpected OK response",
233 "end of file",
234 "no such file or directory",
235 "permission denied",
236 "failure",
237 "bad message",
238 "no connection",
239 "connection lost",
240 "operation unsupported",
241 };
242
243 if (pktin->type != SSH_FXP_STATUS) {
244 fxp_error_message = "expected FXP_STATUS packet";
245 fxp_errtype = -1;
246 } else {
247 fxp_errtype = sftp_pkt_getuint32(pktin);
248 if (fxp_errtype < 0 ||
249 fxp_errtype >= sizeof(messages)/sizeof(*messages))
250 fxp_error_message = "unknown error code";
251 else
252 fxp_error_message = messages[fxp_errtype];
253 }
254
255 if (fxp_errtype == SSH_FX_OK)
256 return 1;
257 else if (fxp_errtype == SSH_FX_EOF)
258 return 0;
259 else
260 return -1;
261 }
262
263 static void fxp_internal_error(char *msg) {
264 fxp_error_message = msg;
265 fxp_errtype = -1;
266 }
267
268 const char *fxp_error(void) {
269 return fxp_error_message;
270 }
271
272 int fxp_error_type(void) {
273 return fxp_errtype;
274 }
275
276 /*
277 * Perform exchange of init/version packets. Return 0 on failure.
278 */
279 int fxp_init(void) {
280 struct sftp_packet *pktout, *pktin;
281 int remotever;
282
283 pktout = sftp_pkt_init(SSH_FXP_INIT);
284 sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
285 sftp_send(pktout);
286
287 pktin = sftp_recv();
288 if (!pktin) {
289 fxp_internal_error("could not connect");
290 return 0;
291 }
292 if (pktin->type != SSH_FXP_VERSION) {
293 fxp_internal_error("did not receive FXP_VERSION");
294 return 0;
295 }
296 remotever = sftp_pkt_getuint32(pktin);
297 if (remotever > SFTP_PROTO_VERSION) {
298 fxp_internal_error("remote protocol is more advanced than we support");
299 return 0;
300 }
301 /*
302 * In principle, this packet might also contain extension-
303 * string pairs. We should work through them and look for any
304 * we recognise. In practice we don't currently do so because
305 * we know we don't recognise _any_.
306 */
307 sftp_pkt_free(pktin);
308
309 return 1;
310 }
311
312 /*
313 * Canonify a pathname.
314 */
315 char *fxp_realpath(char *path) {
316 struct sftp_packet *pktin, *pktout;
317 int id;
318
319 pktout = sftp_pkt_init(SSH_FXP_REALPATH);
320 sftp_pkt_adduint32(pktout, 0x123); /* request id */
321 sftp_pkt_addstring_start(pktout);
322 sftp_pkt_addstring_str(pktout, path);
323 sftp_send(pktout);
324 pktin = sftp_recv();
325 id = sftp_pkt_getuint32(pktin);
326 if (id != 0x123) {
327 fxp_internal_error("request ID mismatch\n");
328 return NULL;
329 }
330 if (pktin->type == SSH_FXP_NAME) {
331 int count;
332 char *path;
333 int len;
334
335 count = sftp_pkt_getuint32(pktin);
336 if (count != 1) {
337 fxp_internal_error("REALPATH returned name count != 1\n");
338 return NULL;
339 }
340 sftp_pkt_getstring(pktin, &path, &len);
341 if (!path) {
342 fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
343 return NULL;
344 }
345 path = mkstr(path, len);
346 sftp_pkt_free(pktin);
347 return path;
348 } else {
349 fxp_got_status(pktin);
350 return NULL;
351 }
352 }
353
354 /*
355 * Open a file.
356 */
357 struct fxp_handle *fxp_open(char *path, int type) {
358 struct sftp_packet *pktin, *pktout;
359 int id;
360
361 pktout = sftp_pkt_init(SSH_FXP_OPEN);
362 sftp_pkt_adduint32(pktout, 0x567); /* request id */
363 sftp_pkt_addstring(pktout, path);
364 sftp_pkt_adduint32(pktout, type);
365 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
366 sftp_send(pktout);
367 pktin = sftp_recv();
368 id = sftp_pkt_getuint32(pktin);
369 if (id != 0x567) {
370 fxp_internal_error("request ID mismatch\n");
371 return NULL;
372 }
373 if (pktin->type == SSH_FXP_HANDLE) {
374 char *hstring;
375 struct fxp_handle *handle;
376 int len;
377
378 sftp_pkt_getstring(pktin, &hstring, &len);
379 if (!hstring) {
380 fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
381 return NULL;
382 }
383 handle = smalloc(sizeof(struct fxp_handle));
384 handle->hstring = mkstr(hstring, len);
385 handle->hlen = len;
386 sftp_pkt_free(pktin);
387 return handle;
388 } else {
389 fxp_got_status(pktin);
390 return NULL;
391 }
392 }
393
394 /*
395 * Open a directory.
396 */
397 struct fxp_handle *fxp_opendir(char *path) {
398 struct sftp_packet *pktin, *pktout;
399 int id;
400
401 pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
402 sftp_pkt_adduint32(pktout, 0x456); /* request id */
403 sftp_pkt_addstring(pktout, path);
404 sftp_send(pktout);
405 pktin = sftp_recv();
406 id = sftp_pkt_getuint32(pktin);
407 if (id != 0x456) {
408 fxp_internal_error("request ID mismatch\n");
409 return NULL;
410 }
411 if (pktin->type == SSH_FXP_HANDLE) {
412 char *hstring;
413 struct fxp_handle *handle;
414 int len;
415
416 sftp_pkt_getstring(pktin, &hstring, &len);
417 if (!hstring) {
418 fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
419 return NULL;
420 }
421 handle = smalloc(sizeof(struct fxp_handle));
422 handle->hstring = mkstr(hstring, len);
423 handle->hlen = len;
424 sftp_pkt_free(pktin);
425 return handle;
426 } else {
427 fxp_got_status(pktin);
428 return NULL;
429 }
430 }
431
432 /*
433 * Close a file/dir.
434 */
435 void fxp_close(struct fxp_handle *handle) {
436 struct sftp_packet *pktin, *pktout;
437 int id;
438
439 pktout = sftp_pkt_init(SSH_FXP_CLOSE);
440 sftp_pkt_adduint32(pktout, 0x789); /* request id */
441 sftp_pkt_addstring_start(pktout);
442 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
443 sftp_send(pktout);
444 pktin = sftp_recv();
445 id = sftp_pkt_getuint32(pktin);
446 if (id != 0x789) {
447 fxp_internal_error("request ID mismatch\n");
448 return;
449 }
450 fxp_got_status(pktin);
451 sfree(handle->hstring);
452 sfree(handle);
453 }
454
455 /*
456 * Read from a file. Returns the number of bytes read, or -1 on an
457 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
458 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
459 * error indicator. It might even depend on the SFTP server.)
460 */
461 int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
462 struct sftp_packet *pktin, *pktout;
463 int id;
464
465 pktout = sftp_pkt_init(SSH_FXP_READ);
466 sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
467 sftp_pkt_addstring_start(pktout);
468 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
469 sftp_pkt_adduint64(pktout, offset);
470 sftp_pkt_adduint32(pktout, len);
471 sftp_send(pktout);
472 pktin = sftp_recv();
473 id = sftp_pkt_getuint32(pktin);
474 if (id != 0xBCD) {
475 fxp_internal_error("request ID mismatch");
476 return -1;
477 }
478 if (pktin->type == SSH_FXP_DATA) {
479 char *str;
480 int rlen;
481
482 sftp_pkt_getstring(pktin, &str, &rlen);
483
484 if (rlen > len || rlen < 0) {
485 fxp_internal_error("READ returned more bytes than requested");
486 return -1;
487 }
488
489 memcpy(buffer, str, rlen);
490 sfree(pktin);
491 return rlen;
492 } else {
493 fxp_got_status(pktin);
494 return -1;
495 }
496 }
497
498 /*
499 * Read from a directory.
500 */
501 struct fxp_names *fxp_readdir(struct fxp_handle *handle) {
502 struct sftp_packet *pktin, *pktout;
503 int id;
504
505 pktout = sftp_pkt_init(SSH_FXP_READDIR);
506 sftp_pkt_adduint32(pktout, 0xABC); /* request id */
507 sftp_pkt_addstring_start(pktout);
508 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
509 sftp_send(pktout);
510 pktin = sftp_recv();
511 id = sftp_pkt_getuint32(pktin);
512 if (id != 0xABC) {
513 fxp_internal_error("request ID mismatch\n");
514 return NULL;
515 }
516 if (pktin->type == SSH_FXP_NAME) {
517 struct fxp_names *ret;
518 int i;
519 ret = smalloc(sizeof(struct fxp_names));
520 ret->nnames = sftp_pkt_getuint32(pktin);
521 ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
522 for (i = 0; i < ret->nnames; i++) {
523 char *str;
524 int len;
525 sftp_pkt_getstring(pktin, &str, &len);
526 ret->names[i].filename = mkstr(str, len);
527 sftp_pkt_getstring(pktin, &str, &len);
528 ret->names[i].longname = mkstr(str, len);
529 ret->names[i].attrs = sftp_pkt_getattrs(pktin);
530 }
531 return ret;
532 } else {
533 fxp_got_status(pktin);
534 return NULL;
535 }
536 }
537
538 /*
539 * Write to a file. Returns 0 on error, 1 on OK.
540 */
541 int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
542 struct sftp_packet *pktin, *pktout;
543 int id;
544
545 pktout = sftp_pkt_init(SSH_FXP_WRITE);
546 sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
547 sftp_pkt_addstring_start(pktout);
548 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
549 sftp_pkt_adduint64(pktout, offset);
550 sftp_pkt_addstring_start(pktout);
551 sftp_pkt_addstring_data(pktout, buffer, len);
552 sftp_send(pktout);
553 pktin = sftp_recv();
554 id = sftp_pkt_getuint32(pktin);
555 if (id != 0xDCB) {
556 fxp_internal_error("request ID mismatch\n");
557 return 0;
558 }
559 fxp_got_status(pktin);
560 return fxp_errtype == SSH_FX_OK;
561 }
562
563 /*
564 * Free up an fxp_names structure.
565 */
566 void fxp_free_names(struct fxp_names *names) {
567 int i;
568
569 for (i = 0; i < names->nnames; i++) {
570 sfree(names->names[i].filename);
571 sfree(names->names[i].longname);
572 }
573 sfree(names->names);
574 sfree(names);
575 }