replaced buggy concat_dir_and_file() by mhl_str_dir_plus_file()
[midnight-commander.git] / vfs / ftpfs.c
blob68f6967ff1f38b751099507a0996be1ac74c52d5
1 /* Virtual File System: FTP file system.
2 Copyright (C) 1995, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
3 2006, 2007 Free Software Foundation, Inc.
5 Written by: 1995 Ching Hui
6 1995 Jakub Jelinek
7 1995, 1996, 1997 Miguel de Icaza
8 1997 Norbert Warmuth
9 1998 Pavel Machek
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU Library General Public License
13 as published by the Free Software Foundation; either version 2 of
14 the License, or (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU Library General Public License for more details.
21 You should have received a copy of the GNU Library General Public
22 License along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
25 /* FTPfs TODO:
27 - make it more robust - all the connects etc. should handle EADDRINUSE and
28 ERETRY (have I spelled these names correctly?)
29 - make the user able to flush a connection - all the caches will get empty
30 etc., (tarfs as well), we should give there a user selectable timeout
31 and assign a key sequence.
32 - use hash table instead of linklist to cache ftpfs directory.
34 What to do with this?
37 * NOTE: Usage of tildes is deprecated, consider:
38 * cd /#ftp:pavel@hobit
39 * cd ~
40 * And now: what do I want to do? Do I want to go to /home/pavel or to
41 * /#ftp:hobit/home/pavel? I think first has better sense...
44 int f = !strcmp( remote_path, "/~" );
45 if (f || !strncmp( remote_path, "/~/", 3 )) {
46 char *s;
47 s = mhl_str_dir_plus_file( qhome (*bucket), remote_path +3-f );
48 g_free (remote_path);
49 remote_path = s;
56 /* Namespace pollution: horrible */
58 #include <config.h>
59 #include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
60 #include <netdb.h> /* struct hostent */
61 #include <sys/socket.h> /* AF_INET */
62 #include <netinet/in.h> /* struct in_addr */
63 #ifdef HAVE_ARPA_INET_H
64 #include <arpa/inet.h>
65 #endif
66 #include <arpa/ftp.h>
67 #include <arpa/telnet.h>
68 #include <sys/param.h>
69 #include <errno.h>
70 #include <ctype.h>
72 #include <mhl/string.h>
74 #include "../src/global.h"
75 #include "../src/tty.h" /* enable/disable interrupt key */
76 #include "../src/wtools.h" /* message() */
77 #include "../src/main.h" /* print_vfs_message */
78 #include "../src/history.h"
79 #include "utilvfs.h"
80 #include "xdirentry.h"
81 #include "vfs.h"
82 #include "vfs-impl.h"
83 #include "gc.h" /* vfs_stamp_create */
84 #include "tcputil.h"
85 #include "../src/setup.h" /* for load_anon_passwd */
86 #include "ftpfs.h"
87 #ifndef MAXHOSTNAMELEN
88 # define MAXHOSTNAMELEN 64
89 #endif
91 #define UPLOAD_ZERO_LENGTH_FILE
92 #define SUP super->u.ftp
93 #define FH_SOCK fh->u.ftp.sock
95 #ifndef INADDR_NONE
96 #define INADDR_NONE 0xffffffff
97 #endif
99 #define RFC_AUTODETECT 0
100 #define RFC_DARING 1
101 #define RFC_STRICT 2
103 #ifndef HAVE_SOCKLEN_T
104 typedef int socklen_t;
105 #endif
107 static int ftpfs_errno;
108 static int code;
110 /* Delay to retry a connection */
111 int ftpfs_retry_seconds = 30;
113 /* Method to use to connect to ftp sites */
114 int ftpfs_use_passive_connections = 1;
115 int ftpfs_use_passive_connections_over_proxy = 0;
117 /* Method used to get directory listings:
118 * 1: try 'LIST -la <path>', if it fails
119 * fall back to CWD <path>; LIST
120 * 0: always use CWD <path>; LIST
122 int ftpfs_use_unix_list_options = 1;
124 /* First "CWD <path>", then "LIST -la ." */
125 int ftpfs_first_cd_then_ls = 1;
127 /* Use the ~/.netrc */
128 int use_netrc = 1;
130 /* Anonymous setup */
131 char *ftpfs_anonymous_passwd = NULL;
132 int ftpfs_directory_timeout = 900;
134 /* Proxy host */
135 char *ftpfs_proxy_host = NULL;
137 /* wether we have to use proxy by default? */
138 int ftpfs_always_use_proxy;
140 #ifdef FIXME_LATER_ALIGATOR
141 static struct linklist *connections_list;
142 #endif
144 /* ftpfs_command wait_flag: */
145 #define NONE 0x00
146 #define WAIT_REPLY 0x01
147 #define WANT_STRING 0x02
148 static char reply_str [80];
150 static struct vfs_class vfs_ftpfs_ops;
152 /* char *ftpfs_translate_path (struct ftpfs_connection *bucket, char *remote_path)
153 Translate a Unix path, i.e. MC's internal path representation (e.g.
154 /somedir/somefile) to a path valid for the remote server. Every path
155 transfered to the remote server has to be mangled by this function
156 right prior to sending it.
157 Currently only Amiga ftp servers are handled in a special manner.
159 When the remote server is an amiga:
160 a) strip leading slash if necesarry
161 b) replace first occurance of ":/" with ":"
162 c) strip trailing "/."
165 static char *ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super);
166 static int ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path);
167 static int ftpfs_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *fmt, ...)
168 __attribute__ ((format (__printf__, 4, 5)));
169 static int ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super);
170 static int ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super, const char *netrcpass);
171 static int ftpfs_netrc_lookup (const char *host, char **login, char **pass);
173 static char *
174 ftpfs_translate_path (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
176 if (!SUP.remote_is_amiga)
177 return g_strdup (remote_path);
178 else {
179 char *ret, *p;
181 if (MEDATA->logfile) {
182 fprintf (MEDATA->logfile, "MC -- ftpfs_translate_path: %s\n", remote_path);
183 fflush (MEDATA->logfile);
186 /* strip leading slash(es) */
187 while (*remote_path == '/')
188 remote_path++;
191 * Don't change "/" into "", e.g. "CWD " would be
192 * invalid.
194 if (*remote_path == '\0')
195 return g_strdup (".");
197 ret = g_strdup (remote_path);
199 /* replace first occurance of ":/" with ":" */
200 if ((p = strchr (ret, ':')) && *(p + 1) == '/')
201 strcpy (p + 1, p + 2);
203 /* strip trailing "/." */
204 if ((p = strrchr (ret, '/')) && *(p + 1) == '.' && *(p + 2) == '\0')
205 *p = '\0';
206 return ret;
210 /* Extract the hostname and username from the path */
213 * path is in the form: [user@]hostname:port/remote-dir, e.g.:
214 * ftp://sunsite.unc.edu/pub/linux
215 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
216 * ftp://tsx-11.mit.edu:8192/
217 * ftp://joe@foo.edu:11321/private
218 * If the user is empty, e.g. ftp://@roxanne/private, then your login name
219 * is supplied.
223 #define FTP_COMMAND_PORT 21
225 static void
226 ftpfs_split_url(char *path, char **host, char **user, int *port, char **pass)
228 char *p;
230 p = vfs_split_url (path, host, user, port, pass, FTP_COMMAND_PORT,
231 URL_ALLOW_ANON);
233 if (!*user) {
234 /* Look up user and password in netrc */
235 if (use_netrc)
236 ftpfs_netrc_lookup (*host, user, pass);
237 if (!*user)
238 *user = g_strdup ("anonymous");
241 /* Look up password in netrc for known user */
242 if (use_netrc && *user && pass && !*pass) {
243 char *new_user;
245 ftpfs_netrc_lookup (*host, &new_user, pass);
247 /* If user is different, remove password */
248 if (new_user && strcmp (*user, new_user)) {
249 g_free (*pass);
250 *pass = NULL;
253 g_free (new_user);
256 g_free (p);
259 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
260 static int
261 ftpfs_get_reply (struct vfs_class *me, int sock, char *string_buf, int string_len)
263 char answer[BUF_1K];
264 int i;
266 for (;;) {
267 if (!vfs_s_get_line (me, sock, answer, sizeof (answer), '\n')){
268 if (string_buf)
269 *string_buf = 0;
270 code = 421;
271 return 4;
273 switch (sscanf(answer, "%d", &code)){
274 case 0:
275 if (string_buf)
276 g_strlcpy (string_buf, answer, string_len);
277 code = 500;
278 return 5;
279 case 1:
280 if (answer[3] == '-') {
281 while (1) {
282 if (!vfs_s_get_line (me, sock, answer, sizeof(answer), '\n')){
283 if (string_buf)
284 *string_buf = 0;
285 code = 421;
286 return 4;
288 if ((sscanf (answer, "%d", &i) > 0) &&
289 (code == i) && (answer[3] == ' '))
290 break;
293 if (string_buf)
294 g_strlcpy (string_buf, answer, string_len);
295 return code / 100;
300 static int
301 ftpfs_reconnect (struct vfs_class *me, struct vfs_s_super *super)
303 int sock = ftpfs_open_socket (me, super);
304 if (sock != -1){
305 char *cwdir = SUP.cwdir;
306 close (SUP.sock);
307 SUP.sock = sock;
308 SUP.cwdir = NULL;
309 if (ftpfs_login_server (me, super, SUP.password)){
310 if (!cwdir)
311 return 1;
312 sock = ftpfs_chdir_internal (me, super, cwdir);
313 g_free (cwdir);
314 return sock == COMPLETE;
316 SUP.cwdir = cwdir;
318 return 0;
321 static int
322 ftpfs_command (struct vfs_class *me, struct vfs_s_super *super, int wait_reply, const char *fmt, ...)
324 va_list ap;
325 char *cmdstr;
326 int status, cmdlen;
327 static int retry = 0;
328 static int level = 0; /* ftpfs_login_server() use ftpfs_command() */
330 va_start (ap, fmt);
331 cmdstr = g_strdup_vprintf (fmt, ap);
332 va_end (ap);
334 cmdlen = strlen (cmdstr);
335 cmdstr = g_realloc (cmdstr, cmdlen + 3);
336 strcpy (cmdstr + cmdlen, "\r\n");
337 cmdlen += 2;
339 if (MEDATA->logfile) {
340 if (strncmp (cmdstr, "PASS ", 5) == 0) {
341 fputs ("PASS <Password not logged>\r\n", MEDATA->logfile);
342 } else
343 fwrite (cmdstr, cmdlen, 1, MEDATA->logfile);
345 fflush (MEDATA->logfile);
348 got_sigpipe = 0;
349 enable_interrupt_key ();
350 status = write (SUP.sock, cmdstr, cmdlen);
352 if (status < 0) {
353 code = 421;
355 if (errno == EPIPE) { /* Remote server has closed connection */
356 if (level == 0) {
357 level = 1;
358 status = ftpfs_reconnect (me, super);
359 level = 0;
360 if (status && (write (SUP.sock, cmdstr, cmdlen) > 0)) {
361 goto ok;
365 got_sigpipe = 1;
367 g_free (cmdstr);
368 disable_interrupt_key ();
369 return TRANSIENT;
371 retry = 0;
373 disable_interrupt_key ();
375 if (wait_reply)
377 status = ftpfs_get_reply (me, SUP.sock,
378 (wait_reply & WANT_STRING) ? reply_str : NULL,
379 sizeof (reply_str) - 1);
380 if ((wait_reply & WANT_STRING) && !retry && !level && code == 421)
382 retry = 1;
383 level = 1;
384 status = ftpfs_reconnect (me, super);
385 level = 0;
386 if (status && (write (SUP.sock, cmdstr, cmdlen) > 0)) {
387 goto ok;
390 retry = 0;
391 g_free (cmdstr);
392 return status;
394 g_free (cmdstr);
395 return COMPLETE;
398 static void
399 ftpfs_free_archive (struct vfs_class *me, struct vfs_s_super *super)
401 if (SUP.sock != -1){
402 print_vfs_message (_("ftpfs: Disconnecting from %s"), SUP.host);
403 ftpfs_command(me, super, NONE, "QUIT");
404 close(SUP.sock);
406 g_free (SUP.host);
407 g_free (SUP.user);
408 g_free (SUP.cwdir);
409 g_free (SUP.password);
412 /* some defines only used by ftpfs_changetype */
413 /* These two are valid values for the second parameter */
414 #define TYPE_ASCII 0
415 #define TYPE_BINARY 1
417 /* This one is only used to initialize bucket->isbinary, don't use it as
418 second parameter to ftpfs_changetype. */
419 #define TYPE_UNKNOWN -1
421 static int
422 ftpfs_changetype (struct vfs_class *me, struct vfs_s_super *super, int binary)
424 if (binary != SUP.isbinary) {
425 if (ftpfs_command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE)
426 ERRNOR (EIO, -1);
427 SUP.isbinary = binary;
429 return binary;
432 /* This routine logs the user in */
433 static int
434 ftpfs_login_server (struct vfs_class *me, struct vfs_s_super *super,
435 const char *netrcpass)
437 char *pass;
438 char *op;
439 char *name; /* login user name */
440 int anon = 0;
441 char reply_string[BUF_MEDIUM];
443 SUP.isbinary = TYPE_UNKNOWN;
445 if (SUP.password) /* explicit password */
446 op = g_strdup (SUP.password);
447 else if (netrcpass) /* password from netrc */
448 op = g_strdup (netrcpass);
449 else if (!strcmp (SUP.user, "anonymous") || !strcmp (SUP.user, "ftp")) {
450 if (!ftpfs_anonymous_passwd) /* default anonymous password */
451 ftpfs_init_passwd ();
452 op = g_strdup (ftpfs_anonymous_passwd);
453 anon = 1;
454 } else { /* ask user */
455 char *p;
457 p = g_strconcat (_(" FTP: Password required for "), SUP.user, " ",
458 NULL);
459 op = vfs_get_password (p);
460 g_free (p);
461 if (op == NULL)
462 ERRNOR (EPERM, 0);
463 SUP.password = g_strdup (op);
466 if (!anon || MEDATA->logfile)
467 pass = op;
468 else {
469 pass = g_strconcat ("-", op, (char *) NULL);
470 wipe_password (op);
473 /* Proxy server accepts: username@host-we-want-to-connect */
474 if (SUP.proxy) {
475 name =
476 g_strconcat (SUP.user, "@",
477 SUP.host[0] == '!' ? SUP.host + 1 : SUP.host,
478 NULL);
479 } else
480 name = g_strdup (SUP.user);
482 if (ftpfs_get_reply
483 (me, SUP.sock, reply_string,
484 sizeof (reply_string) - 1) == COMPLETE) {
485 g_strup (reply_string);
486 SUP.remote_is_amiga = strstr (reply_string, "AMIGA") != 0;
487 if (MEDATA->logfile) {
488 fprintf (MEDATA->logfile, "MC -- remote_is_amiga = %d\n",
489 SUP.remote_is_amiga);
490 fflush (MEDATA->logfile);
493 print_vfs_message (_("ftpfs: sending login name"));
495 switch (ftpfs_command (me, super, WAIT_REPLY, "USER %s", name)) {
496 case CONTINUE:
497 print_vfs_message (_("ftpfs: sending user password"));
498 code = ftpfs_command (me, super, WAIT_REPLY, "PASS %s", pass);
499 if (code == CONTINUE) {
500 char *p;
502 p = g_strdup_printf (_
503 ("FTP: Account required for user %s"),
504 SUP.user);
505 op = input_dialog (p, _("Account:"), MC_HISTORY_FTPFS_ACCOUNT, "");
506 g_free (p);
507 if (op == NULL)
508 ERRNOR (EPERM, 0);
509 print_vfs_message (_("ftpfs: sending user account"));
510 code =
511 ftpfs_command (me, super, WAIT_REPLY, "ACCT %s", op);
512 g_free (op);
514 if (code != COMPLETE)
515 break;
516 /* fall through */
518 case COMPLETE:
519 print_vfs_message (_("ftpfs: logged in"));
520 wipe_password (pass);
521 g_free (name);
522 return 1;
524 default:
525 SUP.failed_on_login = 1;
526 if (SUP.password)
527 wipe_password (SUP.password);
528 SUP.password = 0;
530 goto login_fail;
533 message (1, MSG_ERROR, _("ftpfs: Login incorrect for user %s "),
534 SUP.user);
535 login_fail:
536 wipe_password (pass);
537 g_free (name);
538 ERRNOR (EPERM, 0);
541 static struct no_proxy_entry {
542 char *domain;
543 void *next;
544 } *no_proxy;
546 static void
547 ftpfs_load_no_proxy_list (void)
549 /* FixMe: shouldn't be hardcoded!!! */
550 char s[BUF_LARGE]; /* provide for BUF_LARGE characters */
551 struct no_proxy_entry *np, *current = 0;
552 FILE *npf;
553 int c;
554 char *p;
555 static char *mc_file;
557 if (mc_file)
558 return;
560 mc_file = mhl_str_dir_plus_file (mc_home, "mc.no_proxy");
561 if (exist_file (mc_file) &&
562 (npf = fopen (mc_file, "r"))) {
563 while (fgets (s, sizeof (s), npf)) {
564 if (!(p = strchr (s, '\n'))) { /* skip bogus entries */
565 while ((c = fgetc (npf)) != EOF && c != '\n')
567 continue;
570 if (p == s)
571 continue;
573 *p = '\0';
575 np = g_new (struct no_proxy_entry, 1);
576 np->domain = g_strdup (s);
577 np->next = NULL;
578 if (no_proxy)
579 current->next = np;
580 else
581 no_proxy = np;
582 current = np;
585 fclose (npf);
587 g_free (mc_file);
590 /* Return 1 if FTP proxy should be used for this host, 0 otherwise */
591 static int
592 ftpfs_check_proxy (const char *host)
594 struct no_proxy_entry *npe;
596 if (!ftpfs_proxy_host || !*ftpfs_proxy_host || !host || !*host)
597 return 0; /* sanity check */
599 if (*host == '!')
600 return 1;
602 if (!ftpfs_always_use_proxy)
603 return 0;
605 if (!strchr (host, '.'))
606 return 0;
608 ftpfs_load_no_proxy_list ();
609 for (npe = no_proxy; npe; npe=npe->next) {
610 char *domain = npe->domain;
612 if (domain[0] == '.') {
613 int ld = strlen (domain);
614 int lh = strlen (host);
616 while (ld && lh && host[lh - 1] == domain[ld - 1]) {
617 ld--;
618 lh--;
621 if (!ld)
622 return 0;
623 } else
624 if (!g_strcasecmp (host, domain))
625 return 0;
628 return 1;
631 static void
632 ftpfs_get_proxy_host_and_port (const char *proxy, char **host, int *port)
634 char *user, *dir;
636 dir =
637 vfs_split_url (proxy, host, &user, port, 0, FTP_COMMAND_PORT,
638 URL_ALLOW_ANON);
639 g_free (user);
640 g_free (dir);
643 static int
644 ftpfs_open_socket (struct vfs_class *me, struct vfs_s_super *super)
646 struct sockaddr_in server_address;
647 struct hostent *hp;
648 int my_socket;
649 char *host;
650 int port = SUP.port;
651 int free_host = 0;
653 (void) me;
655 /* Use a proxy host? */
656 host = SUP.host;
658 if (!host || !*host){
659 print_vfs_message (_("ftpfs: Invalid host name."));
660 ftpfs_errno = EINVAL;
661 return -1;
664 /* Hosts to connect to that start with a ! should use proxy */
665 if (SUP.proxy){
666 ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
667 free_host = 1;
670 enable_interrupt_key(); /* clear the interrupt flag */
672 /* Get host address */
673 memset ((char *) &server_address, 0, sizeof (server_address));
674 server_address.sin_family = AF_INET;
675 server_address.sin_addr.s_addr = inet_addr (host);
676 if (server_address.sin_addr.s_addr == INADDR_NONE) {
677 hp = gethostbyname (host);
678 if (hp == NULL){
679 disable_interrupt_key();
680 print_vfs_message (_("ftpfs: Invalid host address."));
681 ftpfs_errno = EINVAL;
682 if (free_host)
683 g_free (host);
684 return -1;
686 server_address.sin_family = hp->h_addrtype;
688 /* We copy only 4 bytes, we cannot trust hp->h_length, as it comes from the DNS */
689 memcpy ((char *) &server_address.sin_addr, (char *) hp->h_addr, 4);
692 server_address.sin_port = htons (port);
694 /* Connect */
695 if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
696 disable_interrupt_key();
697 ftpfs_errno = errno;
698 if (free_host)
699 g_free (host);
700 return -1;
703 print_vfs_message (_("ftpfs: making connection to %s"), host);
704 if (free_host)
705 g_free (host);
707 if (connect (my_socket, (struct sockaddr *) &server_address,
708 sizeof (server_address)) < 0){
709 ftpfs_errno = errno;
710 if (errno == EINTR && got_interrupt ())
711 print_vfs_message (_("ftpfs: connection interrupted by user"));
712 else
713 print_vfs_message (_("ftpfs: connection to server failed: %s"),
714 unix_error_string(errno));
715 disable_interrupt_key();
716 close (my_socket);
717 return -1;
719 disable_interrupt_key();
720 return my_socket;
723 static int
724 ftpfs_open_archive_int (struct vfs_class *me, struct vfs_s_super *super)
726 int retry_seconds, count_down;
728 /* We do not want to use the passive if we are using proxies */
729 if (SUP.proxy)
730 SUP.use_passive_connection = ftpfs_use_passive_connections_over_proxy;
732 retry_seconds = 0;
733 do {
734 SUP.failed_on_login = 0;
736 SUP.sock = ftpfs_open_socket (me, super);
737 if (SUP.sock == -1)
738 return -1;
740 if (ftpfs_login_server (me, super, NULL)) {
741 /* Logged in, no need to retry the connection */
742 break;
743 } else {
744 if (SUP.failed_on_login){
745 /* Close only the socket descriptor */
746 close (SUP.sock);
747 } else {
748 return -1;
750 if (ftpfs_retry_seconds){
751 retry_seconds = ftpfs_retry_seconds;
752 enable_interrupt_key ();
753 for (count_down = retry_seconds; count_down; count_down--){
754 print_vfs_message (_("Waiting to retry... %d (Control-C to cancel)"), count_down);
755 sleep (1);
756 if (got_interrupt ()){
757 /* ftpfs_errno = E; */
758 disable_interrupt_key ();
759 return 0;
762 disable_interrupt_key ();
765 } while (retry_seconds);
767 SUP.cwdir = ftpfs_get_current_directory (me, super);
768 if (!SUP.cwdir)
769 SUP.cwdir = g_strdup (PATH_SEP_STR);
770 return 0;
773 static int
774 ftpfs_open_archive (struct vfs_class *me, struct vfs_s_super *super,
775 const char *archive_name, char *op)
777 char *host, *user, *password;
778 int port;
780 (void) archive_name;
782 ftpfs_split_url (strchr (op, ':') + 1, &host, &user, &port, &password);
784 SUP.host = host;
785 SUP.user = user;
786 SUP.port = port;
787 SUP.cwdir = NULL;
788 SUP.proxy = 0;
789 if (ftpfs_check_proxy (host))
790 SUP.proxy = ftpfs_proxy_host;
791 SUP.password = password;
792 SUP.use_passive_connection = ftpfs_use_passive_connections;
793 SUP.strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
794 SUP.isbinary = TYPE_UNKNOWN;
795 SUP.remote_is_amiga = 0;
796 super->name = g_strdup ("/");
797 super->root =
798 vfs_s_new_inode (me, super,
799 vfs_s_default_stat (me, S_IFDIR | 0755));
801 return ftpfs_open_archive_int (me, super);
804 static int
805 ftpfs_archive_same (struct vfs_class *me, struct vfs_s_super *super,
806 const char *archive_name, char *op, void *cookie)
808 char *host, *user;
809 int port;
811 (void) me;
812 (void) archive_name;
813 (void) cookie;
815 ftpfs_split_url (strchr (op, ':') + 1, &host, &user, &port, 0);
817 port = ((strcmp (host, SUP.host) == 0)
818 && (strcmp (user, SUP.user) == 0) && (port == SUP.port));
820 g_free (host);
821 g_free (user);
823 return port;
826 /* The returned directory should always contain a trailing slash */
827 static char *
828 ftpfs_get_current_directory (struct vfs_class *me, struct vfs_s_super *super)
830 char buf[BUF_8K], *bufp, *bufq;
832 if (ftpfs_command (me, super, NONE, "PWD") == COMPLETE &&
833 ftpfs_get_reply(me, SUP.sock, buf, sizeof(buf)) == COMPLETE) {
834 bufp = NULL;
835 for (bufq = buf; *bufq; bufq++)
836 if (*bufq == '"') {
837 if (!bufp) {
838 bufp = bufq + 1;
839 } else {
840 *bufq = 0;
841 if (*bufp) {
842 if (*(bufq - 1) != '/') {
843 *bufq++ = '/';
844 *bufq = 0;
846 if (*bufp == '/')
847 return g_strdup (bufp);
848 else {
849 /* If the remote server is an Amiga a leading slash
850 might be missing. MC needs it because it is used
851 as separator between hostname and path internally. */
852 return g_strconcat( "/", bufp, NULL);
854 } else {
855 ftpfs_errno = EIO;
856 return NULL;
861 ftpfs_errno = EIO;
862 return NULL;
866 /* Setup Passive ftp connection, we use it for source routed connections */
867 static int
868 ftpfs_setup_passive (struct vfs_class *me, struct vfs_s_super *super, int my_socket, struct sockaddr_in *sa)
870 int xa, xb, xc, xd, xe, xf;
871 char n [6];
872 char *c;
874 if (ftpfs_command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
875 return 0;
877 /* Parse remote parameters */
878 for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++)
880 if (!*c)
881 return 0;
882 if (!isdigit ((unsigned char) *c))
883 return 0;
884 if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
885 return 0;
886 n [0] = (unsigned char) xa;
887 n [1] = (unsigned char) xb;
888 n [2] = (unsigned char) xc;
889 n [3] = (unsigned char) xd;
890 n [4] = (unsigned char) xe;
891 n [5] = (unsigned char) xf;
893 memcpy (&(sa->sin_addr.s_addr), (void *)n, 4);
894 memcpy (&(sa->sin_port), (void *)&n[4], 2);
895 if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0)
896 return 0;
897 return 1;
900 static int
901 ftpfs_initconn (struct vfs_class *me, struct vfs_s_super *super)
903 struct sockaddr_in data_addr;
904 int data;
905 socklen_t len = sizeof(data_addr);
906 struct protoent *pe;
908 pe = getprotobyname ("tcp");
909 if (pe == NULL)
910 ERRNOR (EIO, -1);
911 again:
912 if (getsockname (SUP.sock, (struct sockaddr *) &data_addr, &len) == -1)
913 ERRNOR (EIO, -1);
914 data_addr.sin_port = 0;
916 data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
917 if (data < 0)
918 ERRNOR (EIO, -1);
920 if (SUP.use_passive_connection) {
921 if (ftpfs_setup_passive (me, super, data, &data_addr))
922 return data;
924 SUP.use_passive_connection = 0;
925 print_vfs_message (_("ftpfs: could not setup passive mode"));
927 /* data or data_addr may be damaged by ftpfs_setup_passive */
928 close (data);
929 goto again;
932 /* If passive setup fails, fallback to active connections */
933 /* Active FTP connection */
934 if ((bind (data, (struct sockaddr *)&data_addr, len) == 0) &&
935 (getsockname (data, (struct sockaddr *) &data_addr, &len) == 0) &&
936 (listen (data, 1) == 0))
938 unsigned char *a = (unsigned char *)&data_addr.sin_addr;
939 unsigned char *p = (unsigned char *)&data_addr.sin_port;
941 if (ftpfs_command (me, super, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1],
942 a[2], a[3], p[0], p[1]) == COMPLETE)
943 return data;
945 close (data);
946 ftpfs_errno = EIO;
947 return -1;
950 static int
951 ftpfs_open_data_connection (struct vfs_class *me, struct vfs_s_super *super, const char *cmd,
952 const char *remote, int isbinary, int reget)
954 struct sockaddr_in from;
955 int s, j, data;
956 socklen_t fromlen = sizeof(from);
958 if ((s = ftpfs_initconn (me, super)) == -1)
959 return -1;
960 if (ftpfs_changetype (me, super, isbinary) == -1)
961 return -1;
962 if (reget > 0){
963 j = ftpfs_command (me, super, WAIT_REPLY, "REST %d", reget);
964 if (j != CONTINUE)
965 return -1;
967 if (remote) {
968 char *remote_path = ftpfs_translate_path (me, super, remote);
969 j = ftpfs_command (me, super, WAIT_REPLY, "%s /%s", cmd,
970 /* WarFtpD can't STORE //filename */
971 (*remote_path == '/') ? remote_path + 1 : remote_path);
972 g_free (remote_path);
973 } else
974 j = ftpfs_command (me, super, WAIT_REPLY, "%s", cmd);
975 if (j != PRELIM)
976 ERRNOR (EPERM, -1);
977 enable_interrupt_key();
978 if (SUP.use_passive_connection)
979 data = s;
980 else {
981 data = accept (s, (struct sockaddr *)&from, &fromlen);
982 if (data < 0) {
983 ftpfs_errno = errno;
984 close (s);
985 return -1;
987 close (s);
989 disable_interrupt_key();
990 return data;
993 #define ABORT_TIMEOUT 5
994 static void
995 ftpfs_linear_abort (struct vfs_class *me, struct vfs_s_fh *fh)
997 struct vfs_s_super *super = FH_SUPER;
998 static unsigned char const ipbuf[3] = { IAC, IP, IAC };
999 fd_set mask;
1000 char buf[1024];
1001 int dsock = FH_SOCK;
1002 FH_SOCK = -1;
1003 SUP.ctl_connection_busy = 0;
1005 print_vfs_message (_("ftpfs: aborting transfer."));
1006 if (send (SUP.sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf)) {
1007 print_vfs_message (_("ftpfs: abort error: %s"),
1008 unix_error_string (errno));
1009 if (dsock != -1)
1010 close (dsock);
1011 return;
1014 if (ftpfs_command (me, super, NONE, "%cABOR", DM) != COMPLETE) {
1015 print_vfs_message (_("ftpfs: abort failed"));
1016 if (dsock != -1)
1017 close (dsock);
1018 return;
1020 if (dsock != -1) {
1021 FD_ZERO (&mask);
1022 FD_SET (dsock, &mask);
1023 if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0) {
1024 struct timeval start_tim, tim;
1025 gettimeofday (&start_tim, NULL);
1026 /* flush the remaining data */
1027 while (read (dsock, buf, sizeof (buf)) > 0) {
1028 gettimeofday (&tim, NULL);
1029 if (tim.tv_sec > start_tim.tv_sec + ABORT_TIMEOUT) {
1030 /* server keeps sending, drop the connection and ftpfs_reconnect */
1031 close (dsock);
1032 ftpfs_reconnect (me, super);
1033 return;
1037 close (dsock);
1039 if ((ftpfs_get_reply (me, SUP.sock, NULL, 0) == TRANSIENT) && (code == 426))
1040 ftpfs_get_reply (me, SUP.sock, NULL, 0);
1043 #if 0
1044 static void
1045 resolve_symlink_without_ls_options(struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir)
1047 struct linklist *flist;
1048 struct direntry *fe, *fel;
1049 char tmp[MC_MAXPATHLEN];
1050 int depth;
1052 dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
1053 for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next) {
1054 /* flist->data->l_stat is alread initialized with 0 */
1055 fel = flist->data;
1056 if (S_ISLNK(fel->s.st_mode) && fel->linkname) {
1057 if (fel->linkname[0] == '/') {
1058 if (strlen (fel->linkname) >= MC_MAXPATHLEN)
1059 continue;
1060 strcpy (tmp, fel->linkname);
1061 } else {
1062 if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
1063 continue;
1064 strcpy (tmp, dir->remote_path);
1065 if (tmp[1] != '\0')
1066 strcat (tmp, "/");
1067 strcat (tmp + 1, fel->linkname);
1069 for ( depth = 0; depth < 100; depth++) { /* depth protects against recursive symbolic links */
1070 canonicalize_pathname (tmp);
1071 fe = _get_file_entry(bucket, tmp, 0, 0);
1072 if (fe) {
1073 if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0) {
1074 /* Symlink points to link which isn't resolved, yet. */
1075 if (fe->linkname[0] == '/') {
1076 if (strlen (fe->linkname) >= MC_MAXPATHLEN)
1077 break;
1078 strcpy (tmp, fe->linkname);
1079 } else {
1080 /* at this point tmp looks always like this
1081 /directory/filename, i.e. no need to check
1082 strrchr's return value */
1083 *(strrchr (tmp, '/') + 1) = '\0'; /* dirname */
1084 if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
1085 break;
1086 strcat (tmp, fe->linkname);
1088 continue;
1089 } else {
1090 fel->l_stat = g_new (struct stat, 1);
1091 if ( S_ISLNK (fe->s.st_mode))
1092 *fel->l_stat = *fe->l_stat;
1093 else
1094 *fel->l_stat = fe->s;
1095 (*fel->l_stat).st_ino = bucket->__inode_counter++;
1098 break;
1102 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1105 static void
1106 resolve_symlink_with_ls_options(struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir)
1108 char buffer[2048] = "", *filename;
1109 int sock;
1110 FILE *fp;
1111 struct stat s;
1112 struct linklist *flist;
1113 struct direntry *fe;
1114 int switch_method = 0;
1116 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1117 if (strchr (dir->remote_path, ' ')) {
1118 if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE) {
1119 print_vfs_message(_("ftpfs: CWD failed."));
1120 return;
1122 sock = ftpfs_open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
1124 else
1125 sock = ftpfs_open_data_connection (bucket, "LIST -lLa",
1126 dir->remote_path, TYPE_ASCII, 0);
1128 if (sock == -1) {
1129 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1130 return;
1133 fp = fdopen(sock, "r");
1134 if (fp == NULL) {
1135 close(sock);
1136 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1137 return;
1139 enable_interrupt_key();
1140 flist = dir->file_list->next;
1141 while (1) {
1142 do {
1143 if (flist == dir->file_list)
1144 goto done;
1145 fe = flist->data;
1146 flist = flist->next;
1147 } while (!S_ISLNK(fe->s.st_mode));
1148 while (1) {
1149 if (fgets (buffer, sizeof (buffer), fp) == NULL)
1150 goto done;
1151 if (MEDATA->logfile){
1152 fputs (buffer, MEDATA->logfile);
1153 fflush (MEDATA->logfile);
1155 vfs_die("This code should be commented out\n");
1156 if (vfs_parse_ls_lga (buffer, &s, &filename, NULL)) {
1157 int r = strcmp(fe->name, filename);
1158 g_free(filename);
1159 if (r == 0) {
1160 if (S_ISLNK (s.st_mode)) {
1161 /* This server doesn't understand LIST -lLa */
1162 switch_method = 1;
1163 goto done;
1165 fe->l_stat = g_new (struct stat, 1);
1166 if (fe->l_stat == NULL)
1167 goto done;
1168 *fe->l_stat = s;
1169 (*fe->l_stat).st_ino = bucket->__inode_counter++;
1170 break;
1172 if (r < 0)
1173 break;
1177 done:
1178 while (fgets(buffer, sizeof(buffer), fp) != NULL);
1179 disable_interrupt_key();
1180 fclose(fp);
1181 ftpfs_get_reply(me, SUP.sock, NULL, 0);
1184 static void
1185 resolve_symlink(struct vfs_class *me, struct vfs_s_super *super, struct vfs_s_inode *dir)
1187 print_vfs_message(_("Resolving symlink..."));
1189 if (SUP.strict_rfc959_list_cmd)
1190 resolve_symlink_without_ls_options(me, super, dir);
1191 else
1192 resolve_symlink_with_ls_options(me, super, dir);
1194 #endif
1196 static int
1197 ftpfs_dir_load (struct vfs_class *me, struct vfs_s_inode *dir, char *remote_path)
1199 struct vfs_s_entry *ent;
1200 struct vfs_s_super *super = dir->super;
1201 int sock, num_entries = 0;
1202 char buffer[BUF_8K];
1203 int cd_first;
1205 cd_first = ftpfs_first_cd_then_ls || (SUP.strict == RFC_STRICT)
1206 || (strchr (remote_path, ' ') != NULL);
1208 again:
1209 print_vfs_message (_("ftpfs: Reading FTP directory %s... %s%s"),
1210 remote_path,
1211 SUP.strict ==
1212 RFC_STRICT ? _("(strict rfc959)") : "",
1213 cd_first ? _("(chdir first)") : "");
1215 if (cd_first) {
1216 if (ftpfs_chdir_internal (me, super, remote_path) != COMPLETE) {
1217 ftpfs_errno = ENOENT;
1218 print_vfs_message (_("ftpfs: CWD failed."));
1219 return -1;
1223 gettimeofday (&dir->timestamp, NULL);
1224 dir->timestamp.tv_sec += ftpfs_directory_timeout;
1226 if (SUP.strict == RFC_STRICT)
1227 sock = ftpfs_open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
1228 else if (cd_first)
1229 /* Dirty hack to avoid autoprepending / to . */
1230 /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
1231 sock =
1232 ftpfs_open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
1233 else {
1234 /* Trailing "/." is necessary if remote_path is a symlink */
1235 char *path = mhl_str_dir_plus_file (remote_path, ".");
1236 sock =
1237 ftpfs_open_data_connection (me, super, "LIST -la", path, TYPE_ASCII,
1239 g_free (path);
1242 if (sock == -1)
1243 goto fallback;
1245 /* Clear the interrupt flag */
1246 enable_interrupt_key ();
1248 while (1) {
1249 int i;
1250 int res =
1251 vfs_s_get_line_interruptible (me, buffer, sizeof (buffer),
1252 sock);
1253 if (!res)
1254 break;
1256 if (res == EINTR) {
1257 me->verrno = ECONNRESET;
1258 close (sock);
1259 disable_interrupt_key ();
1260 ftpfs_get_reply (me, SUP.sock, NULL, 0);
1261 print_vfs_message (_("%s: failure"), me->name);
1262 return -1;
1265 if (MEDATA->logfile) {
1266 fputs (buffer, MEDATA->logfile);
1267 fputs ("\n", MEDATA->logfile);
1268 fflush (MEDATA->logfile);
1271 ent = vfs_s_generate_entry (me, NULL, dir, 0);
1272 i = ent->ino->st.st_nlink;
1273 if (!vfs_parse_ls_lga
1274 (buffer, &ent->ino->st, &ent->name, &ent->ino->linkname)) {
1275 vfs_s_free_entry (me, ent);
1276 continue;
1278 ent->ino->st.st_nlink = i; /* Ouch, we need to preserve our counts :-( */
1279 num_entries++;
1280 vfs_s_insert_entry (me, dir, ent);
1283 close (sock);
1284 me->verrno = E_REMOTE;
1285 if ((ftpfs_get_reply (me, SUP.sock, NULL, 0) != COMPLETE))
1286 goto fallback;
1288 if (num_entries == 0 && cd_first == 0) {
1289 /* The LIST command may produce an empty output. In such scenario
1290 it is not clear whether this is caused by `remote_path' being
1291 a non-existent path or for some other reason (listing emtpy
1292 directory without the -a option, non-readable directory, etc.).
1294 Since `dir_load' is a crucial method, when it comes to determine
1295 whether a given path is a _directory_, the code must try its best
1296 to determine the type of `remote_path'. The only reliable way to
1297 achieve this is trough issuing a CWD command. */
1299 cd_first = 1;
1300 goto again;
1303 if (SUP.strict == RFC_AUTODETECT)
1304 SUP.strict = RFC_DARING;
1306 print_vfs_message (_("%s: done."), me->name);
1307 return 0;
1309 fallback:
1310 if (SUP.strict == RFC_AUTODETECT) {
1311 /* It's our first attempt to get a directory listing from this
1312 server (UNIX style LIST command) */
1313 SUP.strict = RFC_STRICT;
1314 /* I hate goto, but recursive call needs another 8K on stack */
1315 /* return ftpfs_dir_load (me, dir, remote_path); */
1316 cd_first = 1;
1317 goto again;
1319 print_vfs_message (_("ftpfs: failed; nowhere to fallback to"));
1320 ERRNOR (EACCES, -1);
1323 static int
1324 ftpfs_file_store (struct vfs_class *me, struct vfs_s_fh *fh, char *name,
1325 char *localname)
1327 int h, sock, n_read, n_written;
1328 off_t n_stored;
1329 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1330 struct linger li;
1331 #else
1332 int flag_one = 1;
1333 #endif
1334 char buffer[8192];
1335 struct stat s;
1336 char *w_buf;
1337 struct vfs_s_super *super = FH_SUPER;
1339 h = open (localname, O_RDONLY);
1340 if (h == -1)
1341 ERRNOR (EIO, -1);
1342 sock =
1343 ftpfs_open_data_connection (me, super,
1344 fh->u.ftp.append ? "APPE" : "STOR", name,
1345 TYPE_BINARY, 0);
1346 if (sock < 0 || fstat (h, &s) == -1) {
1347 close (h);
1348 return -1;
1350 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1351 li.l_onoff = 1;
1352 li.l_linger = 120;
1353 setsockopt (sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof (li));
1354 #else
1355 setsockopt (sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
1356 #endif
1357 n_stored = 0;
1359 enable_interrupt_key ();
1360 while (1) {
1361 while ((n_read = read (h, buffer, sizeof (buffer))) == -1) {
1362 if (errno == EINTR) {
1363 if (got_interrupt ()) {
1364 ftpfs_errno = EINTR;
1365 goto error_return;
1366 } else
1367 continue;
1369 ftpfs_errno = errno;
1370 goto error_return;
1372 if (n_read == 0)
1373 break;
1374 n_stored += n_read;
1375 w_buf = buffer;
1376 while ((n_written = write (sock, w_buf, n_read)) != n_read) {
1377 if (n_written == -1) {
1378 if (errno == EINTR && !got_interrupt ()) {
1379 continue;
1381 ftpfs_errno = errno;
1382 goto error_return;
1384 w_buf += n_written;
1385 n_read -= n_written;
1387 print_vfs_message (_("ftpfs: storing file %lu (%lu)"),
1388 (unsigned long) n_stored, (unsigned long) s.st_size);
1390 disable_interrupt_key ();
1391 close (sock);
1392 close (h);
1393 if (ftpfs_get_reply (me, SUP.sock, NULL, 0) != COMPLETE)
1394 ERRNOR (EIO, -1);
1395 return 0;
1396 error_return:
1397 disable_interrupt_key ();
1398 close (sock);
1399 close (h);
1400 ftpfs_get_reply (me, SUP.sock, NULL, 0);
1401 return -1;
1404 static int
1405 ftpfs_linear_start (struct vfs_class *me, struct vfs_s_fh *fh, off_t offset)
1407 char *name = vfs_s_fullpath (me, fh->ino);
1409 if (!name)
1410 return 0;
1411 FH_SOCK = ftpfs_open_data_connection(me, FH_SUPER, "RETR", name, TYPE_BINARY, offset);
1412 g_free (name);
1413 if (FH_SOCK == -1)
1414 ERRNOR (EACCES, 0);
1415 fh->linear = LS_LINEAR_OPEN;
1416 FH_SUPER->u.ftp.ctl_connection_busy = 1;
1417 fh->u.ftp.append = 0;
1418 return 1;
1421 static int
1422 ftpfs_linear_read (struct vfs_class *me, struct vfs_s_fh *fh, void *buf, int len)
1424 int n;
1425 struct vfs_s_super *super = FH_SUPER;
1427 while ((n = read (FH_SOCK, buf, len))<0) {
1428 if ((errno == EINTR) && !got_interrupt())
1429 continue;
1430 break;
1433 if (n<0)
1434 ftpfs_linear_abort(me, fh);
1436 if (!n) {
1437 SUP.ctl_connection_busy = 0;
1438 close (FH_SOCK);
1439 FH_SOCK = -1;
1440 if ((ftpfs_get_reply (me, SUP.sock, NULL, 0) != COMPLETE))
1441 ERRNOR (E_REMOTE, -1);
1442 return 0;
1444 ERRNOR (errno, n);
1447 static void
1448 ftpfs_linear_close (struct vfs_class *me, struct vfs_s_fh *fh)
1450 if (FH_SOCK != -1)
1451 ftpfs_linear_abort(me, fh);
1454 static int ftpfs_ctl (void *fh, int ctlop, void *arg)
1456 (void) arg;
1458 switch (ctlop) {
1459 case VFS_CTL_IS_NOTREADY:
1461 int v;
1463 if (!FH->linear)
1464 vfs_die ("You may not do this");
1465 if (FH->linear == LS_LINEAR_CLOSED || FH->linear == LS_LINEAR_PREOPEN)
1466 return 0;
1468 v = vfs_s_select_on_two (FH->u.ftp.sock, 0);
1469 if (((v < 0) && (errno == EINTR)) || v == 0)
1470 return 1;
1471 return 0;
1473 default:
1474 return 0;
1478 static int
1479 ftpfs_send_command(struct vfs_class *me, const char *filename, const char *cmd, int flags)
1481 const char *rpath;
1482 char *p, *mpath = g_strdup(filename);
1483 struct vfs_s_super *super;
1484 int r;
1485 int flush_directory_cache = (flags & OPT_FLUSH);
1487 if (!(rpath = vfs_s_get_path_mangle(me, mpath, &super, 0))) {
1488 g_free(mpath);
1489 return -1;
1491 p = ftpfs_translate_path (me, super, rpath);
1492 r = ftpfs_command (me, super, WAIT_REPLY, cmd, p);
1493 g_free (p);
1494 vfs_stamp_create (&vfs_ftpfs_ops, super);
1495 if (flags & OPT_IGNORE_ERROR)
1496 r = COMPLETE;
1497 if (r != COMPLETE) {
1498 me->verrno = EPERM;
1499 g_free (mpath);
1500 return -1;
1502 if (flush_directory_cache)
1503 vfs_s_invalidate(me, super);
1504 g_free(mpath);
1505 return 0;
1508 /* This routine is called as the last step in load_setup */
1509 void
1510 ftpfs_init_passwd(void)
1512 ftpfs_anonymous_passwd = load_anon_passwd ();
1513 if (ftpfs_anonymous_passwd)
1514 return;
1516 /* If there is no anonymous ftp password specified
1517 * then we'll just use anonymous@
1518 * We don't send any other thing because:
1519 * - We want to remain anonymous
1520 * - We want to stop SPAM
1521 * - We don't want to let ftp sites to discriminate by the user,
1522 * host or country.
1524 ftpfs_anonymous_passwd = g_strdup ("anonymous@");
1527 static int ftpfs_chmod (struct vfs_class *me, const char *path, int mode)
1529 char buf[BUF_SMALL];
1531 g_snprintf(buf, sizeof(buf), "SITE CHMOD %4.4o /%%s", mode & 07777);
1532 return ftpfs_send_command(me, path, buf, OPT_FLUSH);
1535 static int ftpfs_chown (struct vfs_class *me, const char *path, int owner, int group)
1537 #if 0
1538 ftpfs_errno = EPERM;
1539 return -1;
1540 #else
1541 /* Everyone knows it is not possible to chown remotely, so why bother them.
1542 If someone's root, then copy/move will always try to chown it... */
1543 (void) me;
1544 (void) path;
1545 (void) owner;
1546 (void) group;
1547 return 0;
1548 #endif
1551 static int ftpfs_unlink (struct vfs_class *me, const char *path)
1553 return ftpfs_send_command(me, path, "DELE /%s", OPT_FLUSH);
1556 /* Return 1 if path is the same directory as the one we are in now */
1557 static int
1558 ftpfs_is_same_dir (struct vfs_class *me, struct vfs_s_super *super, const char *path)
1560 (void) me;
1562 if (!SUP.cwdir)
1563 return 0;
1564 if (strcmp (path, SUP.cwdir) == 0)
1565 return 1;
1566 return 0;
1569 static int
1570 ftpfs_chdir_internal (struct vfs_class *me, struct vfs_s_super *super, const char *remote_path)
1572 int r;
1573 char *p;
1575 if (!SUP.cwd_deferred && ftpfs_is_same_dir (me, super, remote_path))
1576 return COMPLETE;
1578 p = ftpfs_translate_path (me, super, remote_path);
1579 r = ftpfs_command (me, super, WAIT_REPLY, "CWD /%s", p);
1580 g_free (p);
1582 if (r != COMPLETE) {
1583 ftpfs_errno = EIO;
1584 } else {
1585 g_free(SUP.cwdir);
1586 SUP.cwdir = g_strdup (remote_path);
1587 SUP.cwd_deferred = 0;
1589 return r;
1592 static int ftpfs_rename (struct vfs_class *me, const char *path1, const char *path2)
1594 ftpfs_send_command(me, path1, "RNFR /%s", OPT_FLUSH);
1595 return ftpfs_send_command(me, path2, "RNTO /%s", OPT_FLUSH);
1598 static int ftpfs_mkdir (struct vfs_class *me, const char *path, mode_t mode)
1600 (void) mode; /* FIXME: should be used */
1602 return ftpfs_send_command(me, path, "MKD /%s", OPT_FLUSH);
1605 static int ftpfs_rmdir (struct vfs_class *me, const char *path)
1607 return ftpfs_send_command(me, path, "RMD /%s", OPT_FLUSH);
1610 static int
1611 ftpfs_fh_open (struct vfs_class *me, struct vfs_s_fh *fh, int flags,
1612 int mode)
1614 (void) mode;
1616 fh->u.ftp.append = 0;
1617 /* File will be written only, so no need to retrieve it from ftp server */
1618 if (((flags & O_WRONLY) == O_WRONLY) && !(flags & (O_RDONLY | O_RDWR))) {
1619 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1620 struct linger li;
1621 #else
1622 int li = 1;
1623 #endif
1624 char *name;
1626 /* ftpfs_linear_start() called, so data will be written
1627 * to local temporary file and stored to ftp server
1628 * by vfs_s_close later
1630 if (FH_SUPER->u.ftp.ctl_connection_busy) {
1631 if (!fh->ino->localname) {
1632 int handle = vfs_mkstemps (&fh->ino->localname, me->name,
1633 fh->ino->ent->name);
1634 if (handle == -1)
1635 return -1;
1636 close (handle);
1637 fh->u.ftp.append = flags & O_APPEND;
1639 return 0;
1641 name = vfs_s_fullpath (me, fh->ino);
1642 if (!name)
1643 return -1;
1644 fh->handle =
1645 ftpfs_open_data_connection (me, fh->ino->super,
1646 (flags & O_APPEND) ? "APPE" :
1647 "STOR", name, TYPE_BINARY, 0);
1648 g_free (name);
1650 if (fh->handle < 0)
1651 return -1;
1652 #ifdef HAVE_STRUCT_LINGER_L_LINGER
1653 li.l_onoff = 1;
1654 li.l_linger = 120;
1655 #endif
1656 setsockopt (fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof (li));
1658 if (fh->ino->localname) {
1659 unlink (fh->ino->localname);
1660 g_free (fh->ino->localname);
1661 fh->ino->localname = NULL;
1663 return 0;
1666 if (!fh->ino->localname)
1667 if (vfs_s_retrieve_file (me, fh->ino) == -1)
1668 return -1;
1669 if (!fh->ino->localname)
1670 vfs_die ("retrieve_file failed to fill in localname");
1671 return 0;
1674 static int ftpfs_fh_close (struct vfs_class *me, struct vfs_s_fh *fh)
1676 if (fh->handle != -1 && !fh->ino->localname){
1677 close (fh->handle);
1678 fh->handle = -1;
1679 /* File is stored to destination already, so
1680 * we prevent MEDATA->ftpfs_file_store() call from vfs_s_close ()
1682 fh->changed = 0;
1683 if (ftpfs_get_reply (me, fh->ino->SUP.sock, NULL, 0) != COMPLETE)
1684 ERRNOR (EIO, -1);
1685 vfs_s_invalidate (me, FH_SUPER);
1687 return 0;
1690 static void
1691 ftpfs_done (struct vfs_class *me)
1693 struct no_proxy_entry *np;
1695 (void) me;
1697 while (no_proxy) {
1698 np = no_proxy->next;
1699 g_free (no_proxy->domain);
1700 g_free (no_proxy);
1701 no_proxy = np;
1703 g_free (ftpfs_anonymous_passwd);
1704 g_free (ftpfs_proxy_host);
1707 static void
1708 ftpfs_fill_names (struct vfs_class *me, fill_names_f func)
1710 struct vfs_s_super *super = MEDATA->supers;
1711 char *name;
1713 while (super){
1714 name = g_strconcat ("/#ftp:", SUP.user, "@", SUP.host, "/", SUP.cwdir, (char *) NULL);
1715 (*func)(name);
1716 g_free (name);
1717 super = super->next;
1721 static char buffer[BUF_MEDIUM];
1722 static char *netrc;
1723 static const char *netrcp;
1725 /* This should match the keywords[] array below */
1726 typedef enum {
1727 NETRC_NONE = 0,
1728 NETRC_DEFAULT,
1729 NETRC_MACHINE,
1730 NETRC_LOGIN,
1731 NETRC_PASSWORD,
1732 NETRC_PASSWD,
1733 NETRC_ACCOUNT,
1734 NETRC_MACDEF,
1735 NETRC_UNKNOWN
1736 } keyword_t;
1738 static keyword_t ftpfs_netrc_next (void)
1740 char *p;
1741 keyword_t i;
1742 static const char *const keywords[] = { "default", "machine",
1743 "login", "password", "passwd", "account", "macdef", NULL
1747 while (1) {
1748 netrcp = skip_separators (netrcp);
1749 if (*netrcp != '\n')
1750 break;
1751 netrcp++;
1753 if (!*netrcp)
1754 return NETRC_NONE;
1755 p = buffer;
1756 if (*netrcp == '"') {
1757 for (netrcp++; *netrcp != '"' && *netrcp; netrcp++) {
1758 if (*netrcp == '\\')
1759 netrcp++;
1760 *p++ = *netrcp;
1762 } else {
1763 for (; *netrcp != '\n' && *netrcp != '\t' && *netrcp != ' ' &&
1764 *netrcp != ',' && *netrcp; netrcp++) {
1765 if (*netrcp == '\\')
1766 netrcp++;
1767 *p++ = *netrcp;
1770 *p = 0;
1771 if (!*buffer)
1772 return NETRC_NONE;
1774 i = NETRC_DEFAULT;
1775 while (keywords[i - 1]) {
1776 if (!strcmp (keywords[i - 1], buffer))
1777 return i;
1779 i++;
1782 return NETRC_UNKNOWN;
1785 static int ftpfs_netrc_bad_mode (const char *netrcname)
1787 static int be_angry = 1;
1788 struct stat mystat;
1790 if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077)) {
1791 if (be_angry) {
1792 message (1, MSG_ERROR,
1793 _("~/.netrc file has incorrect mode.\n"
1794 "Remove password or correct mode."));
1795 be_angry = 0;
1797 return 1;
1799 return 0;
1802 /* Scan .netrc until we find matching "machine" or "default"
1803 * domain is used for additional matching
1804 * No search is done after "default" in compliance with "man netrc"
1805 * Return 0 if found, -1 otherwise */
1806 static int ftpfs_find_machine (const char *host, const char *domain)
1808 keyword_t keyword;
1810 while ((keyword = ftpfs_netrc_next ()) != NETRC_NONE) {
1811 if (keyword == NETRC_DEFAULT)
1812 return 0;
1814 if (keyword == NETRC_MACDEF) {
1815 /* Scan for an empty line, which concludes "macdef" */
1816 do {
1817 while (*netrcp && *netrcp != '\n')
1818 netrcp++;
1819 if (*netrcp != '\n')
1820 break;
1821 netrcp++;
1822 } while (*netrcp && *netrcp != '\n');
1823 continue;
1826 if (keyword != NETRC_MACHINE)
1827 continue;
1829 /* Take machine name */
1830 if (ftpfs_netrc_next () == NETRC_NONE)
1831 break;
1833 if (g_strcasecmp (host, buffer)) {
1834 /* Try adding our domain to short names in .netrc */
1835 const char *host_domain = strchr (host, '.');
1836 if (!host_domain)
1837 continue;
1839 /* Compare domain part */
1840 if (g_strcasecmp (host_domain, domain))
1841 continue;
1843 /* Compare local part */
1844 if (g_strncasecmp (host, buffer, host_domain - host))
1845 continue;
1848 return 0;
1851 /* end of .netrc */
1852 return -1;
1855 /* Extract login and password from .netrc for the host.
1856 * pass may be NULL.
1857 * Returns 0 for success, -1 for error */
1858 static int ftpfs_netrc_lookup (const char *host, char **login, char **pass)
1860 char *netrcname;
1861 char *tmp_pass = NULL;
1862 char hostname[MAXHOSTNAMELEN];
1863 const char *domain;
1864 keyword_t keyword;
1865 static struct rupcache {
1866 struct rupcache *next;
1867 char *host;
1868 char *login;
1869 char *pass;
1870 } *rup_cache = NULL, *rupp;
1872 /* Initialize *login and *pass */
1873 if (!login)
1874 return 0;
1875 *login = NULL;
1876 if (pass)
1877 *pass = NULL;
1879 /* Look up in the cache first */
1880 for (rupp = rup_cache; rupp != NULL; rupp = rupp->next) {
1881 if (!strcmp (host, rupp->host)) {
1882 if (rupp->login)
1883 *login = g_strdup (rupp->login);
1884 if (pass && rupp->pass)
1885 *pass = g_strdup (rupp->pass);
1886 return 0;
1890 /* Load current .netrc */
1891 netrcname = mhl_str_dir_plus_file (home_dir, ".netrc");
1892 netrcp = netrc = load_file (netrcname);
1893 if (netrc == NULL) {
1894 g_free (netrcname);
1895 return 0;
1898 /* Find our own domain name */
1899 if (gethostname (hostname, sizeof (hostname)) < 0)
1900 *hostname = 0;
1901 if (!(domain = strchr (hostname, '.')))
1902 domain = "";
1904 /* Scan for "default" and matching "machine" keywords */
1905 ftpfs_find_machine (host, domain);
1907 /* Scan for keywords following "default" and "machine" */
1908 while (1) {
1909 int need_break = 0;
1910 keyword = ftpfs_netrc_next ();
1912 switch (keyword) {
1913 case NETRC_LOGIN:
1914 if (ftpfs_netrc_next () == NETRC_NONE) {
1915 need_break = 1;
1916 break;
1919 /* We have another name already - should not happen */
1920 if (*login) {
1921 need_break = 1;
1922 break;
1925 /* We have login name now */
1926 *login = g_strdup (buffer);
1927 break;
1929 case NETRC_PASSWORD:
1930 case NETRC_PASSWD:
1931 if (ftpfs_netrc_next () == NETRC_NONE) {
1932 need_break = 1;
1933 break;
1936 /* Ignore unsafe passwords */
1937 if (strcmp (*login, "anonymous") && strcmp (*login, "ftp")
1938 && ftpfs_netrc_bad_mode (netrcname)) {
1939 need_break = 1;
1940 break;
1943 /* Remember password. pass may be NULL, so use tmp_pass */
1944 if (tmp_pass == NULL)
1945 tmp_pass = g_strdup (buffer);
1946 break;
1948 case NETRC_ACCOUNT:
1949 /* "account" is followed by a token which we ignore */
1950 if (ftpfs_netrc_next () == NETRC_NONE) {
1951 need_break = 1;
1952 break;
1955 /* Ignore account, but warn user anyways */
1956 ftpfs_netrc_bad_mode (netrcname);
1957 break;
1959 default:
1960 /* Unexpected keyword or end of file */
1961 need_break = 1;
1962 break;
1965 if (need_break)
1966 break;
1969 g_free (netrc);
1970 g_free (netrcname);
1972 rupp = g_new (struct rupcache, 1);
1973 rupp->host = g_strdup (host);
1974 rupp->login = rupp->pass = 0;
1976 if (*login != NULL) {
1977 rupp->login = g_strdup (*login);
1979 if (tmp_pass != NULL)
1980 rupp->pass = g_strdup (tmp_pass);
1981 rupp->next = rup_cache;
1982 rup_cache = rupp;
1984 if (pass)
1985 *pass = tmp_pass;
1987 return 0;
1990 void
1991 init_ftpfs (void)
1993 static struct vfs_s_subclass ftpfs_subclass;
1995 ftpfs_subclass.flags = VFS_S_REMOTE;
1996 ftpfs_subclass.archive_same = ftpfs_archive_same;
1997 ftpfs_subclass.open_archive = ftpfs_open_archive;
1998 ftpfs_subclass.free_archive = ftpfs_free_archive;
1999 ftpfs_subclass.fh_open = ftpfs_fh_open;
2000 ftpfs_subclass.fh_close = ftpfs_fh_close;
2001 ftpfs_subclass.dir_load = ftpfs_dir_load;
2002 ftpfs_subclass.file_store = ftpfs_file_store;
2003 ftpfs_subclass.linear_start = ftpfs_linear_start;
2004 ftpfs_subclass.linear_read = ftpfs_linear_read;
2005 ftpfs_subclass.linear_close = ftpfs_linear_close;
2007 vfs_s_init_class (&vfs_ftpfs_ops, &ftpfs_subclass);
2008 vfs_ftpfs_ops.name = "ftpfs";
2009 vfs_ftpfs_ops.flags = VFSF_NOLINKS;
2010 vfs_ftpfs_ops.prefix = "ftp:";
2011 vfs_ftpfs_ops.done = &ftpfs_done;
2012 vfs_ftpfs_ops.fill_names = ftpfs_fill_names;
2013 vfs_ftpfs_ops.chmod = ftpfs_chmod;
2014 vfs_ftpfs_ops.chown = ftpfs_chown;
2015 vfs_ftpfs_ops.unlink = ftpfs_unlink;
2016 vfs_ftpfs_ops.rename = ftpfs_rename;
2017 vfs_ftpfs_ops.mkdir = ftpfs_mkdir;
2018 vfs_ftpfs_ops.rmdir = ftpfs_rmdir;
2019 vfs_ftpfs_ops.ctl = ftpfs_ctl;
2020 vfs_register_class (&vfs_ftpfs_ops);