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