small fix...
[midnight-commander.git] / vfs / ftpfs.c
blobbda8178595106f56174f168e2f80b477f6a38dd3
1 /* Virtual File System: FTP file system.
2 Copyright (C) 1995 The Free Software Foundation
4 Written by: 1995 Ching Hui
5 1995 Jakub Jelinek
6 1995, 1996, 1997 Miguel de Icaza
7 1997 Norbert Warmuth
8 1998 Pavel Machek
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU Library General Public License
12 as published by the Free Software Foundation; either version 2 of
13 the License, or (at your option) any later version.
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU Library General Public License for more details.
20 You should have received a copy of the GNU Library General Public
21 License along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
24 /* FTPfs TODO:
26 - make it more robust - all the connects etc. should handle EADDRINUSE and
27 ERETRY (have I spelled these names correctly?)
28 - make the user able to flush a connection - all the caches will get empty
29 etc., (tarfs as well), we should give there a user selectable timeout
30 and assign a key sequence.
31 - use hash table instead of linklist to cache ftpfs directory.
33 What to do with this?
36 * NOTE: Usage of tildes is deprecated, consider:
37 * cd /#ftp:pavel@hobit
38 * cd ~
39 * And now: what do I want to do? Do I want to go to /home/pavel or to
40 * /#ftp:hobit/home/pavel? I think first has better sense...
43 int f = !strcmp( remote_path, "/~" );
44 if (f || !strncmp( remote_path, "/~/", 3 )) {
45 char *s;
46 s = concat_dir_and_file( qhome (*bucket), remote_path +3-f );
47 g_free (remote_path);
48 remote_path = s;
55 /* Namespace pollution: horrible */
57 #include <config.h>
58 #include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
59 #include <netdb.h> /* struct hostent */
60 #include <sys/socket.h> /* AF_INET */
61 #include <netinet/in.h> /* struct in_addr */
62 #ifdef HAVE_SETSOCKOPT
63 # include <netinet/ip.h> /* IP options */
64 #endif
65 #ifdef HAVE_ARPA_INET_H
66 #include <arpa/inet.h>
67 #endif
68 #include <arpa/ftp.h>
69 #include <arpa/telnet.h>
70 #include <sys/param.h>
72 #include "utilvfs.h"
74 #include "xdirentry.h"
75 #include "vfs.h"
76 #include "tcputil.h"
77 #include "../src/dialog.h"
78 #include "../src/setup.h" /* for load_anon_passwd */
79 #include "ftpfs.h"
80 #ifndef MAXHOSTNAMELEN
81 # define MAXHOSTNAMELEN 64
82 #endif
84 #define UPLOAD_ZERO_LENGTH_FILE
85 #define SUP super->u.ftp
86 #define FH_SOCK fh->u.ftp.sock
88 static int my_errno;
89 static int code;
91 /* Delay to retry a connection */
92 int ftpfs_retry_seconds = 30;
94 /* Method to use to connect to ftp sites */
95 int ftpfs_use_passive_connections = 1;
97 /* Method used to get directory listings:
98 * 1: try 'LIST -la <path>', if it fails
99 * fall back to CWD <path>; LIST
100 * 0: always use CWD <path>; LIST
102 int ftpfs_use_unix_list_options = 1;
104 /* First "CWD <path>", then "LIST -la ." */
105 int ftpfs_first_cd_then_ls;
107 /* Use the ~/.netrc */
108 int use_netrc = 1;
110 extern char *home_dir;
112 /* Anonymous setup */
113 char *ftpfs_anonymous_passwd = NULL;
114 int ftpfs_directory_timeout = 900;
116 /* Proxy host */
117 char *ftpfs_proxy_host = NULL;
119 /* wether we have to use proxy by default? */
120 int ftpfs_always_use_proxy;
122 /* source routing host */
123 extern int source_route;
125 /* Where we store the transactions */
126 static FILE *logfile = NULL;
128 /* If true, the directory cache is forced to reload */
129 static int force_expiration = 0;
131 #ifdef FIXME_LATER_ALIGATOR
132 static struct linklist *connections_list;
133 #endif
135 /* command wait_flag: */
136 #define NONE 0x00
137 #define WAIT_REPLY 0x01
138 #define WANT_STRING 0x02
139 static char reply_str [80];
141 /* char *translate_path (struct ftpfs_connection *bucket, char *remote_path)
142 Translate a Unix path, i.e. MC's internal path representation (e.g.
143 /somedir/somefile) to a path valid for the remote server. Every path
144 transfered to the remote server has to be mangled by this function
145 right prior to sending it.
146 Currently only Amiga ftp servers are handled in a special manner.
148 When the remote server is an amiga:
149 a) strip leading slash if necesarry
150 b) replace first occurance of ":/" with ":"
151 c) strip trailing "/."
154 static char *ftpfs_get_current_directory (vfs *me, vfs_s_super *super);
155 static int ftpfs_chdir_internal (vfs *me, vfs_s_super *super, char *remote_path);
156 static int command (vfs *me, vfs_s_super *super, int wait_reply, char *fmt, ...)
157 __attribute__ ((format (printf, 4, 5)));
158 static int ftpfs_open_socket (vfs *me, vfs_s_super *super);
159 static int login_server (vfs *me, vfs_s_super *super, const char *netrcpass);
160 static int lookup_netrc (const char *host, char **login, char **pass);
162 static char *
163 translate_path (vfs *me, vfs_s_super *super, const char *remote_path)
165 if (!SUP.remote_is_amiga)
166 return g_strdup (remote_path);
167 else {
168 char *ret, *p;
170 if (logfile) {
171 fprintf (logfile, "MC -- translate_path: %s\n", remote_path);
172 fflush (logfile);
175 /* strip leading slash(es) */
176 while (*remote_path == '/')
177 remote_path++;
180 * Don't change "/" into "", e.g. "CWD " would be
181 * invalid.
183 if (*remote_path == '\0')
184 return g_strdup (".");
186 ret = g_strdup (remote_path);
188 /* replace first occurance of ":/" with ":" */
189 if ((p = strchr (ret, ':')) && *(p + 1) == '/')
190 strcpy (p + 1, p + 2);
192 /* strip trailing "/." */
193 if ((p = strrchr (ret, '/')) && *(p + 1) == '.' && *(p + 2) == '\0')
194 *p = '\0';
195 return ret;
199 /* Extract the hostname and username from the path */
202 * path is in the form: [user@]hostname:port/remote-dir, e.g.:
203 * ftp://sunsite.unc.edu/pub/linux
204 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
205 * ftp://tsx-11.mit.edu:8192/
206 * ftp://joe@foo.edu:11321/private
207 * If the user is empty, e.g. ftp://@roxanne/private, then your login name
208 * is supplied.
212 #define FTP_COMMAND_PORT 21
214 static void
215 ftp_split_url(char *path, char **host, char **user, int *port, char **pass)
217 char *p;
219 p = vfs_split_url (path, host, user, port, pass, FTP_COMMAND_PORT,
220 URL_ALLOW_ANON);
222 if (!*user) {
223 /* Look up user and password in netrc */
224 if (use_netrc)
225 lookup_netrc (*host, user, pass);
226 if (!*user)
227 *user = g_strdup ("anonymous");
230 /* Look up password in netrc for known user */
231 if (use_netrc && *user && pass && !*pass) {
232 char *new_user;
234 lookup_netrc (*host, &new_user, pass);
236 /* If user is different, remove password */
237 if (new_user && strcmp (*user, new_user)) {
238 g_free (*pass);
239 *pass = NULL;
242 g_free (new_user);
245 if (p)
246 g_free (p);
249 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
250 static int
251 get_reply (vfs *me, int sock, char *string_buf, int string_len)
253 char answer[BUF_1K];
254 int i;
256 for (;;) {
257 if (!vfs_s_get_line (me, sock, answer, sizeof (answer), '\n')){
258 if (string_buf)
259 *string_buf = 0;
260 code = 421;
261 return 4;
263 switch (sscanf(answer, "%d", &code)){
264 case 0:
265 if (string_buf) {
266 strncpy (string_buf, answer, string_len - 1);
267 *(string_buf + string_len - 1) = 0;
269 code = 500;
270 return 5;
271 case 1:
272 if (answer[3] == '-') {
273 while (1) {
274 if (!vfs_s_get_line (me, sock, answer, sizeof(answer), '\n')){
275 if (string_buf)
276 *string_buf = 0;
277 code = 421;
278 return 4;
280 if ((sscanf (answer, "%d", &i) > 0) &&
281 (code == i) && (answer[3] == ' '))
282 break;
285 if (string_buf){
286 strncpy (string_buf, answer, string_len - 1);
287 *(string_buf + string_len - 1) = 0;
289 return code / 100;
294 static int
295 reconnect (vfs *me, vfs_s_super *super)
297 int sock = ftpfs_open_socket (me, super);
298 if (sock != -1){
299 char *cwdir = SUP.cwdir;
300 close (SUP.sock);
301 SUP.sock = sock;
302 SUP.cwdir = NULL;
303 if (login_server (me, super, SUP.password)){
304 if (!cwdir)
305 return 1;
306 sock = ftpfs_chdir_internal (me, super, cwdir);
307 g_free (cwdir);
308 return sock == COMPLETE;
310 SUP.cwdir = cwdir;
312 return 0;
315 static int
316 command (vfs *me, vfs_s_super *super, int wait_reply, char *fmt, ...)
318 va_list ap;
319 char *str, *fmt_str;
320 int status;
321 int sock = SUP.sock;
323 va_start (ap, fmt);
324 fmt_str = g_strdup_vprintf (fmt, ap);
325 va_end (ap);
327 status = strlen (fmt_str);
328 str = g_realloc (fmt_str, status + 3);
329 strcpy (str + status, "\r\n");
331 if (logfile){
332 if (strncmp (str, "PASS ", 5) == 0){
333 fputs ("PASS <Password not logged>\r\n", logfile);
334 } else
335 fwrite (str, status + 2, 1, logfile);
337 fflush (logfile);
340 got_sigpipe = 0;
341 enable_interrupt_key ();
342 status = write (SUP.sock, str, status + 2);
344 if (status < 0){
345 code = 421;
347 if (errno == EPIPE){ /* Remote server has closed connection */
348 static int level = 0; /* login_server() use command() */
349 if (level == 0){
350 level = 1;
351 status = reconnect (me, super);
352 level = 0;
353 if (status && write (SUP.sock, str, status + 2) > 0)
354 goto ok;
356 got_sigpipe = 1;
358 g_free (str);
359 disable_interrupt_key ();
360 return TRANSIENT;
363 g_free (str);
364 disable_interrupt_key ();
366 if (wait_reply)
367 return get_reply (me, sock, (wait_reply & WANT_STRING) ? reply_str : NULL, sizeof (reply_str)-1);
368 return COMPLETE;
371 static void
372 free_archive (vfs *me, vfs_s_super *super)
374 if (SUP.sock != -1){
375 print_vfs_message (_("ftpfs: Disconnecting from %s"), SUP.host);
376 command(me, super, NONE, "QUIT");
377 close(SUP.sock);
379 g_free (SUP.host);
380 g_free (SUP.user);
381 g_free (SUP.cwdir);
382 g_free (SUP.password);
385 /* some defines only used by changetype */
386 /* These two are valid values for the second parameter */
387 #define TYPE_ASCII 0
388 #define TYPE_BINARY 1
390 /* This one is only used to initialize bucket->isbinary, don't use it as
391 second parameter to changetype. */
392 #define TYPE_UNKNOWN -1
394 static int
395 changetype (vfs *me, vfs_s_super *super, int binary)
397 if (binary != SUP.isbinary) {
398 if (command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE)
399 ERRNOR (EIO, -1);
400 SUP.isbinary = binary;
402 return binary;
405 /* This routine logs the user in */
406 static int
407 login_server (vfs *me, vfs_s_super *super, const char *netrcpass)
409 char *pass;
410 char *op;
411 char *name; /* login user name */
412 int anon = 0;
413 char reply_string[BUF_MEDIUM];
415 SUP.isbinary = TYPE_UNKNOWN;
416 if (netrcpass)
417 op = g_strdup (netrcpass);
418 else {
419 if (!strcmp (SUP.user, "anonymous") ||
420 !strcmp (SUP.user, "ftp")) {
421 if (!ftpfs_anonymous_passwd)
422 ftpfs_init_passwd();
423 op = g_strdup (ftpfs_anonymous_passwd);
424 anon = 1;
425 } else {
426 char *p;
428 if (!SUP.password){
429 p = g_strconcat (_(" FTP: Password required for "), SUP.user,
430 " ", NULL);
431 op = vfs_get_password (p);
432 g_free (p);
433 if (op == NULL)
434 ERRNOR (EPERM, 0);
435 SUP.password = g_strdup (op);
436 } else
437 op = g_strdup (SUP.password);
441 if (!anon || logfile)
442 pass = op;
443 else {
444 pass = g_strconcat ("-", op, NULL);
445 wipe_password (op);
448 /* Proxy server accepts: username@host-we-want-to-connect*/
449 if (SUP.proxy){
450 name = g_strconcat (SUP.user, "@",
451 SUP.host[0] == '!' ? SUP.host+1 : SUP.host, NULL);
452 } else
453 name = g_strdup (SUP.user);
455 if (get_reply (me, SUP.sock, reply_string, sizeof (reply_string) - 1) == COMPLETE) {
456 g_strup (reply_string);
457 SUP.remote_is_amiga = strstr (reply_string, "AMIGA") != 0;
458 if (logfile) {
459 fprintf (logfile, "MC -- remote_is_amiga = %d\n", SUP.remote_is_amiga);
460 fflush (logfile);
463 print_vfs_message (_("ftpfs: sending login name"));
464 code = command (me, super, WAIT_REPLY, "USER %s", name);
466 switch (code){
467 case CONTINUE:
468 print_vfs_message (_("ftpfs: sending user password"));
469 if (command (me, super, WAIT_REPLY, "PASS %s", pass) != COMPLETE)
470 break;
472 case COMPLETE:
473 print_vfs_message (_("ftpfs: logged in"));
474 wipe_password (pass);
475 g_free (name);
476 return 1;
478 default:
479 SUP.failed_on_login = 1;
480 /* my_errno = E; */
481 if (SUP.password)
482 wipe_password (SUP.password);
483 SUP.password = 0;
485 goto login_fail;
488 message_2s (1, MSG_ERROR, _("ftpfs: Login incorrect for user %s "), SUP.user);
489 login_fail:
490 wipe_password (pass);
491 g_free (name);
492 ERRNOR (EPERM, 0);
495 #ifdef HAVE_SETSOCKOPT
496 static void
497 setup_source_route (int socket, int dest)
499 char buffer [20];
500 char *ptr = buffer;
502 if (!source_route)
503 return;
504 memset (buffer, 0, sizeof (buffer));
505 *ptr++ = IPOPT_LSRR;
506 *ptr++ = 3 + 8;
507 *ptr++ = 4; /* pointer */
509 /* First hop */
510 memcpy (ptr, (char *) &source_route, sizeof (int));
511 ptr += 4;
513 /* Second hop (ie, final destination) */
514 memcpy (ptr, (char *) &dest, sizeof (int));
515 ptr += 4;
516 while ((ptr - buffer) & 3)
517 ptr++;
518 if (setsockopt (socket, IPPROTO_IP, IP_OPTIONS,
519 buffer, ptr - buffer) < 0)
520 message_2s (1, MSG_ERROR, _(" Could not set source routing (%s)"), unix_error_string (errno));
522 #else
523 #define setup_source_route(x,y)
524 #endif
526 static struct no_proxy_entry {
527 char *domain;
528 void *next;
529 } *no_proxy;
531 static void
532 load_no_proxy_list (void)
534 /* FixMe: shouldn't be hardcoded!!! */
535 char s[BUF_LARGE]; /* provide for BUF_LARGE characters */
536 struct no_proxy_entry *np, *current = 0;
537 FILE *npf;
538 int c;
539 char *p;
540 static char *mc_file;
542 if (mc_file)
543 return;
545 mc_file = concat_dir_and_file (mc_home, "mc.no_proxy");
546 if (exist_file (mc_file) &&
547 (npf = fopen (mc_file, "r"))) {
548 while (fgets (s, sizeof(s), npf) || !(feof (npf) || ferror (npf))) {
549 if (!(p = strchr (s, '\n'))) { /* skip bogus entries */
550 while ((c = fgetc (npf)) != EOF && c != '\n')
552 continue;
555 if (p == s)
556 continue;
558 *p = '\0';
560 np = g_new (struct no_proxy_entry, 1);
561 np->domain = g_strdup (s);
562 np->next = NULL;
563 if (no_proxy)
564 current->next = np;
565 else
566 no_proxy = np;
567 current = np;
570 fclose (npf);
572 g_free (mc_file);
575 static int
576 ftpfs_check_proxy (const char *host)
578 struct no_proxy_entry *npe;
580 if (!ftpfs_proxy_host || !*ftpfs_proxy_host || !host || !*host)
581 return 0; /* sanity check */
583 if (*host == '!')
584 return 1;
586 if (!ftpfs_always_use_proxy)
587 return 0;
589 if (!strchr (host, '.'))
590 return 0;
592 load_no_proxy_list ();
593 for (npe = no_proxy; npe; npe=npe->next) {
594 char *domain = npe->domain;
596 if (domain[0] == '.') {
597 int ld = strlen (domain);
598 int lh = strlen (host);
600 while (ld && lh && host[lh - 1] == domain[ld - 1]) {
601 ld--;
602 lh--;
605 if (!ld)
606 return 0;
607 } else
608 if (!g_strcasecmp (host, domain))
609 return 0;
612 return 1;
615 static void
616 ftpfs_get_proxy_host_and_port (char *proxy, char **host, int *port)
618 char *user, *dir;
620 dir =
621 vfs_split_url (proxy, host, &user, port, 0, FTP_COMMAND_PORT,
622 URL_ALLOW_ANON);
623 g_free (user);
624 g_free (dir);
627 static int
628 ftpfs_open_socket (vfs *me, vfs_s_super *super)
630 struct sockaddr_in server_address;
631 struct hostent *hp;
632 int my_socket;
633 char *host;
634 int port = SUP.port;
635 int free_host = 0;
637 /* Use a proxy host? */
638 host = SUP.host;
640 if (!host || !*host){
641 print_vfs_message (_("ftpfs: Invalid host name."));
642 my_errno = EINVAL;
643 return -1;
646 /* Hosts to connect to that start with a ! should use proxy */
647 if (SUP.proxy){
648 ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
649 free_host = 1;
652 /* Get host address */
653 memset ((char *) &server_address, 0, sizeof (server_address));
654 server_address.sin_family = AF_INET;
655 server_address.sin_addr.s_addr = inet_addr (host);
656 if (server_address.sin_addr.s_addr != -1)
657 server_address.sin_family = AF_INET;
658 else {
659 hp = gethostbyname (host);
660 if (hp == NULL){
661 print_vfs_message (_("ftpfs: Invalid host address."));
662 my_errno = EINVAL;
663 if (free_host)
664 g_free (host);
665 return -1;
667 server_address.sin_family = hp->h_addrtype;
669 /* We copy only 4 bytes, we can not trust hp->h_length, as it comes from the DNS */
670 memcpy ((char *) &server_address.sin_addr, (char *) hp->h_addr, 4);
673 server_address.sin_port = htons (port);
675 /* Connect */
676 if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
677 my_errno = errno;
678 if (free_host)
679 g_free (host);
680 return -1;
682 setup_source_route (my_socket, server_address.sin_addr.s_addr);
684 print_vfs_message (_("ftpfs: making connection to %s"), host);
685 if (free_host)
686 g_free (host);
688 enable_interrupt_key (); /* clear the interrupt flag */
690 if (connect (my_socket, (struct sockaddr *) &server_address,
691 sizeof (server_address)) < 0){
692 my_errno = errno;
693 if (errno == EINTR && got_interrupt ())
694 print_vfs_message (_("ftpfs: connection interrupted by user"));
695 else
696 print_vfs_message (_("ftpfs: connection to server failed: %s"),
697 unix_error_string(errno));
698 disable_interrupt_key();
699 close (my_socket);
700 return -1;
702 disable_interrupt_key();
703 return my_socket;
706 static int
707 open_archive_int (vfs *me, vfs_s_super *super)
709 int retry_seconds, count_down;
711 /* We do not want to use the passive if we are using proxies */
712 if (SUP.proxy)
713 SUP.use_passive_connection = 0;
715 retry_seconds = 0;
716 do {
717 SUP.failed_on_login = 0;
719 SUP.sock = ftpfs_open_socket (me, super);
720 if (SUP.sock == -1)
721 return -1;
723 if (login_server (me, super, NULL)) {
724 /* Logged in, no need to retry the connection */
725 break;
726 } else {
727 if (SUP.failed_on_login){
728 /* Close only the socket descriptor */
729 close (SUP.sock);
730 } else {
731 return -1;
733 if (ftpfs_retry_seconds){
734 retry_seconds = ftpfs_retry_seconds;
735 enable_interrupt_key ();
736 for (count_down = retry_seconds; count_down; count_down--){
737 print_vfs_message (_("Waiting to retry... %d (Control-C to cancel)"), count_down);
738 sleep (1);
739 if (got_interrupt ()){
740 /* my_errno = E; */
741 disable_interrupt_key ();
742 return 0;
745 disable_interrupt_key ();
748 } while (retry_seconds);
750 SUP.cwdir = ftpfs_get_current_directory (me, super);
751 if (!SUP.cwdir)
752 SUP.cwdir = g_strdup (PATH_SEP_STR);
753 return 0;
756 static int
757 open_archive (vfs *me, vfs_s_super *super, char *archive_name, char *op)
759 char *host, *user, *password;
760 int port;
762 ftp_split_url (strchr (op, ':') + 1, &host, &user, &port, &password);
764 SUP.host = host;
765 SUP.user = user;
766 SUP.port = port;
767 SUP.cwdir = NULL;
768 SUP.proxy = 0;
769 if (ftpfs_check_proxy (host))
770 SUP.proxy = ftpfs_proxy_host;
771 SUP.password = password;
772 SUP.use_passive_connection = ftpfs_use_passive_connections | source_route;
773 SUP.use_source_route = source_route;
774 SUP.strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
775 SUP.isbinary = TYPE_UNKNOWN;
776 SUP.remote_is_amiga = 0;
777 super->name = g_strdup("/");
778 super->root = vfs_s_new_inode (me, super, vfs_s_default_stat(me, S_IFDIR | 0755));
780 return open_archive_int (me, super);
783 static int
784 archive_same(vfs *me, vfs_s_super *super, char *archive_name, char *op, void *cookie)
786 char *host, *user;
787 int port;
789 ftp_split_url (strchr(op, ':') + 1, &host, &user, &port, 0);
791 port = ((strcmp (host, SUP.host) == 0) &&
792 (strcmp (user, SUP.user) == 0) &&
793 (port == SUP.port));
795 g_free (host);
796 g_free (user);
798 return port;
801 void
802 ftpfs_flushdir (void)
804 force_expiration = 1;
807 static int
808 dir_uptodate(vfs *me, vfs_s_inode *ino)
810 struct timeval tim;
812 if (force_expiration) {
813 force_expiration = 0;
814 return 0;
816 gettimeofday(&tim, NULL);
817 if (tim.tv_sec < ino->u.ftp.timestamp.tv_sec)
818 return 1;
819 return 0;
822 /* The returned directory should always contain a trailing slash */
823 static char *
824 ftpfs_get_current_directory (vfs *me, vfs_s_super *super)
826 char buf[BUF_8K], *bufp, *bufq;
828 if (command (me, super, NONE, "PWD") == COMPLETE &&
829 get_reply(me, SUP.sock, buf, sizeof(buf)) == COMPLETE) {
830 bufp = NULL;
831 for (bufq = buf; *bufq; bufq++)
832 if (*bufq == '"') {
833 if (!bufp) {
834 bufp = bufq + 1;
835 } else {
836 *bufq = 0;
837 if (*bufp) {
838 if (*(bufq - 1) != '/') {
839 *bufq++ = '/';
840 *bufq = 0;
842 if (*bufp == '/')
843 return g_strdup (bufp);
844 else {
845 /* If the remote server is an Amiga a leading slash
846 might be missing. MC needs it because it is used
847 as seperator between hostname and path internally. */
848 return g_strconcat( "/", bufp, 0);
850 } else {
851 my_errno = EIO;
852 return NULL;
857 my_errno = EIO;
858 return NULL;
862 /* Setup Passive ftp connection, we use it for source routed connections */
863 static int
864 setup_passive (vfs *me, vfs_s_super *super, int my_socket, struct sockaddr_in *sa)
866 int xa, xb, xc, xd, xe, xf;
867 char n [6];
868 char *c = reply_str;
870 if (command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
871 return 0;
873 /* Parse remote parameters */
874 for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++)
876 if (!*c)
877 return 0;
878 if (!isdigit ((unsigned char) *c))
879 return 0;
880 if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
881 return 0;
882 n [0] = (unsigned char) xa;
883 n [1] = (unsigned char) xb;
884 n [2] = (unsigned char) xc;
885 n [3] = (unsigned char) xd;
886 n [4] = (unsigned char) xe;
887 n [5] = (unsigned char) xf;
889 memcpy (&(sa->sin_addr.s_addr), (void *)n, 4);
890 memcpy (&(sa->sin_port), (void *)&n[4], 2);
891 setup_source_route (my_socket, sa->sin_addr.s_addr);
892 if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0)
893 return 0;
894 return 1;
897 static int
898 initconn (vfs *me, vfs_s_super *super)
900 struct sockaddr_in data_addr;
901 int data, len = sizeof(data_addr);
902 struct protoent *pe;
904 getsockname(SUP.sock, (struct sockaddr *) &data_addr, &len);
905 data_addr.sin_port = 0;
907 pe = getprotobyname("tcp");
908 if (pe == NULL)
909 ERRNOR (EIO, -1);
910 data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
911 if (data < 0)
912 ERRNOR (EIO, -1);
914 if (SUP.use_passive_connection){
915 if ((SUP.use_passive_connection = setup_passive (me, super, data, &data_addr)))
916 return data;
918 SUP.use_source_route = 0;
919 SUP.use_passive_connection = 0;
920 print_vfs_message (_("ftpfs: could not setup passive mode"));
923 /* If passive setup fails, fallback to active connections */
924 /* Active FTP connection */
925 if ((bind (data, (struct sockaddr *)&data_addr, len) == 0) &&
926 (getsockname (data, (struct sockaddr *) &data_addr, &len) == 0) &&
927 (listen (data, 1) == 0))
929 unsigned char *a = (unsigned char *)&data_addr.sin_addr;
930 unsigned char *p = (unsigned char *)&data_addr.sin_port;
932 if (command (me, super, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1],
933 a[2], a[3], p[0], p[1]) == COMPLETE)
934 return data;
936 close(data);
937 my_errno = EIO;
938 return -1;
941 static int
942 open_data_connection (vfs *me, vfs_s_super *super, char *cmd, char *remote,
943 int isbinary, int reget)
945 struct sockaddr_in from;
946 int s, j, data, fromlen = sizeof(from);
948 if ((s = initconn (me, super)) == -1)
949 return -1;
950 if (changetype (me, super, isbinary) == -1)
951 return -1;
952 if (reget > 0){
953 j = command (me, super, WAIT_REPLY, "REST %d", reget);
954 if (j != CONTINUE)
955 return -1;
957 if (remote) {
958 char * remote_path = translate_path (me, super, remote);
959 j = command (me, super, WAIT_REPLY, "%s /%s", cmd,
960 /* WarFtpD can't STORE //filename */
961 (*remote_path == '/') ? remote_path + 1 : remote_path);
962 g_free (remote_path);
963 } else
964 j = command (me, super, WAIT_REPLY, "%s", cmd);
965 if (j != PRELIM)
966 ERRNOR (EPERM, -1);
967 enable_interrupt_key();
968 if (SUP.use_passive_connection)
969 data = s;
970 else {
971 data = accept (s, (struct sockaddr *)&from, &fromlen);
972 close(s);
973 if (data < 0) {
974 my_errno = errno;
975 return -1;
978 disable_interrupt_key();
979 return data;
982 #define ABORT_TIMEOUT 5
983 static void
984 linear_abort (vfs *me, vfs_s_fh *fh)
986 vfs_s_super *super = FH_SUPER;
987 static unsigned char const ipbuf[3] = { IAC, IP, IAC };
988 fd_set mask;
989 char buf[1024];
990 int dsock = FH_SOCK;
991 FH_SOCK = -1;
992 SUP.control_connection_buzy = 0;
994 print_vfs_message (_("ftpfs: aborting transfer."));
995 if (send (SUP.sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf)) {
996 print_vfs_message (_("ftpfs: abort error: %s"),
997 unix_error_string (errno));
998 return;
1001 if (command (me, super, NONE, "%cABOR", DM) != COMPLETE) {
1002 print_vfs_message (_("ftpfs: abort failed"));
1003 return;
1005 if (dsock != -1) {
1006 FD_ZERO (&mask);
1007 FD_SET (dsock, &mask);
1008 if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0) {
1009 struct timeval start_tim, tim;
1010 gettimeofday (&start_tim, NULL);
1011 /* flush the remaining data */
1012 while (read (dsock, buf, sizeof (buf)) > 0) {
1013 gettimeofday (&tim, NULL);
1014 if (tim.tv_sec > start_tim.tv_sec + ABORT_TIMEOUT) {
1015 /* server keeps sending, drop the connection and reconnect */
1016 reconnect (me, super);
1017 return;
1022 if ((get_reply (me, SUP.sock, NULL, 0) == TRANSIENT) && (code == 426))
1023 get_reply (me, SUP.sock, NULL, 0);
1026 #if 0
1027 static void
1028 resolve_symlink_without_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1030 struct linklist *flist;
1031 struct direntry *fe, *fel;
1032 char tmp[MC_MAXPATHLEN];
1033 int depth;
1035 dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
1036 for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next) {
1037 /* flist->data->l_stat is alread initialized with 0 */
1038 fel = flist->data;
1039 if (S_ISLNK(fel->s.st_mode) && fel->linkname) {
1040 if (fel->linkname[0] == '/') {
1041 if (strlen (fel->linkname) >= MC_MAXPATHLEN)
1042 continue;
1043 strcpy (tmp, fel->linkname);
1044 } else {
1045 if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
1046 continue;
1047 strcpy (tmp, dir->remote_path);
1048 if (tmp[1] != '\0')
1049 strcat (tmp, "/");
1050 strcat (tmp + 1, fel->linkname);
1052 for ( depth = 0; depth < 100; depth++) { /* depth protects against recursive symbolic links */
1053 canonicalize_pathname (tmp);
1054 fe = _get_file_entry(bucket, tmp, 0, 0);
1055 if (fe) {
1056 if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0) {
1057 /* Symlink points to link which isn't resolved, yet. */
1058 if (fe->linkname[0] == '/') {
1059 if (strlen (fe->linkname) >= MC_MAXPATHLEN)
1060 break;
1061 strcpy (tmp, fe->linkname);
1062 } else {
1063 /* at this point tmp looks always like this
1064 /directory/filename, i.e. no need to check
1065 strrchr's return value */
1066 *(strrchr (tmp, '/') + 1) = '\0'; /* dirname */
1067 if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
1068 break;
1069 strcat (tmp, fe->linkname);
1071 continue;
1072 } else {
1073 fel->l_stat = g_new (struct stat, 1);
1074 if ( S_ISLNK (fe->s.st_mode))
1075 *fel->l_stat = *fe->l_stat;
1076 else
1077 *fel->l_stat = fe->s;
1078 (*fel->l_stat).st_ino = bucket->__inode_counter++;
1081 break;
1085 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1088 static void
1089 resolve_symlink_with_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1091 char buffer[2048] = "", *filename;
1092 int sock;
1093 FILE *fp;
1094 struct stat s;
1095 struct linklist *flist;
1096 struct direntry *fe;
1097 int switch_method = 0;
1099 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1100 if (strchr (dir->remote_path, ' ')) {
1101 if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE) {
1102 print_vfs_message(_("ftpfs: CWD failed."));
1103 return;
1105 sock = open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
1107 else
1108 sock = open_data_connection (bucket, "LIST -lLa",
1109 dir->remote_path, TYPE_ASCII, 0);
1111 if (sock == -1) {
1112 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1113 return;
1116 fp = fdopen(sock, "r");
1117 if (fp == NULL) {
1118 close(sock);
1119 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1120 return;
1122 enable_interrupt_key();
1123 flist = dir->file_list->next;
1124 while (1) {
1125 do {
1126 if (flist == dir->file_list)
1127 goto done;
1128 fe = flist->data;
1129 flist = flist->next;
1130 } while (!S_ISLNK(fe->s.st_mode));
1131 while (1) {
1132 if (fgets (buffer, sizeof (buffer), fp) == NULL)
1133 goto done;
1134 if (logfile){
1135 fputs (buffer, logfile);
1136 fflush (logfile);
1138 vfs_die("This code should be commented out\n");
1139 if (vfs_parse_ls_lga (buffer, &s, &filename, NULL)) {
1140 int r = strcmp(fe->name, filename);
1141 g_free(filename);
1142 if (r == 0) {
1143 if (S_ISLNK (s.st_mode)) {
1144 /* This server doesn't understand LIST -lLa */
1145 switch_method = 1;
1146 goto done;
1148 fe->l_stat = g_new (struct stat, 1);
1149 if (fe->l_stat == NULL)
1150 goto done;
1151 *fe->l_stat = s;
1152 (*fe->l_stat).st_ino = bucket->__inode_counter++;
1153 break;
1155 if (r < 0)
1156 break;
1160 done:
1161 while (fgets(buffer, sizeof(buffer), fp) != NULL);
1162 disable_interrupt_key();
1163 fclose(fp);
1164 get_reply(me, SUP.sock, NULL, 0);
1167 static void
1168 resolve_symlink(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1170 print_vfs_message(_("Resolving symlink..."));
1172 if (SUP.strict_rfc959_list_cmd)
1173 resolve_symlink_without_ls_options(me, super, dir);
1174 else
1175 resolve_symlink_with_ls_options(me, super, dir);
1177 #endif
1179 static int
1180 dir_load(vfs *me, vfs_s_inode *dir, char *remote_path)
1182 vfs_s_entry *ent;
1183 vfs_s_super *super = dir->super;
1184 int sock, num_entries = 0;
1185 #ifdef FIXME_LATER
1186 int has_symlinks = 0;
1187 #endif
1188 char buffer[BUF_8K];
1189 int cd_first;
1191 cd_first = ftpfs_first_cd_then_ls || (strchr (remote_path, ' ') != NULL)
1192 || (SUP.strict == RFC_STRICT);
1194 again:
1195 print_vfs_message(_("ftpfs: Reading FTP directory %s... %s%s"), remote_path,
1196 SUP.strict == RFC_STRICT ? _("(strict rfc959)") : "",
1197 cd_first ? _("(chdir first)") : "");
1199 if (cd_first) {
1200 char *p;
1202 p = translate_path (me, super, remote_path);
1204 if (ftpfs_chdir_internal (me, super, p) != COMPLETE) {
1205 g_free (p);
1206 my_errno = ENOENT;
1207 print_vfs_message(_("ftpfs: CWD failed."));
1208 return -1;
1210 g_free (p);
1213 gettimeofday(&dir->u.ftp.timestamp, NULL);
1214 dir->u.ftp.timestamp.tv_sec += ftpfs_directory_timeout;
1216 if (SUP.strict == RFC_STRICT)
1217 sock = open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
1218 else if (cd_first)
1219 /* Dirty hack to avoid autoprepending / to . */
1220 /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
1221 sock = open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
1222 else {
1223 /* Trailing "/." is necessary if remote_path is a symlink */
1224 char *path = concat_dir_and_file (remote_path, ".");
1225 sock = open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0);
1226 g_free (path);
1229 if (sock == -1)
1230 goto fallback;
1232 /* Clear the interrupt flag */
1233 enable_interrupt_key ();
1235 #if 1
1237 /* added 20001006 by gisburn
1238 * add dots '.' and '..'. This must be _executed_ before scanning the dir as the
1239 * code below may jump directly into error handling code (without executing
1240 * remaining code). And C doesn't have try {...} finally {}; :-)
1242 vfs_s_inode *parent = dir->ent->dir;
1244 if( parent==NULL )
1245 parent = dir;
1247 ent = vfs_s_generate_entry(me, ".", dir, 0);
1248 ent->ino->st=dir->st;
1249 num_entries++;
1250 vfs_s_insert_entry(me, dir, ent);
1252 ent = vfs_s_generate_entry(me, "..", parent, 0);
1253 ent->ino->st=parent->st;
1254 num_entries++;
1255 vfs_s_insert_entry(me, dir, ent);
1257 #endif
1259 while (1) {
1260 int i;
1261 int res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), sock);
1262 if (!res)
1263 break;
1265 if (res == EINTR) {
1266 me->verrno = ECONNRESET;
1267 close (sock);
1268 disable_interrupt_key();
1269 get_reply(me, SUP.sock, NULL, 0);
1270 print_vfs_message (_("%s: failure"), me->name);
1271 return -1;
1274 if (logfile){
1275 fputs (buffer, logfile);
1276 fputs ("\n", logfile);
1277 fflush (logfile);
1280 ent = vfs_s_generate_entry(me, NULL, dir, 0);
1281 i = ent->ino->st.st_nlink;
1282 if (!vfs_parse_ls_lga (buffer, &ent->ino->st, &ent->name, &ent->ino->linkname)) {
1283 vfs_s_free_entry (me, ent);
1284 continue;
1286 ent->ino->st.st_nlink = i; /* Ouch, we need to preserve our counts :-( */
1287 num_entries++;
1288 if ((!strcmp(ent->name, ".")) || (!strcmp (ent->name, ".."))) {
1289 g_free (ent->name);
1290 ent->name = NULL; /* Ouch, vfs_s_free_entry "knows" about . and .. being special :-( */
1291 vfs_s_free_entry (me, ent);
1292 continue;
1295 vfs_s_insert_entry(me, dir, ent);
1298 /* vfs_s_add_dots(me, dir, NULL);
1299 FIXME This really should be here; but we need to provide correct parent.
1300 Disabled for now, please fix it. Pavel
1302 close(sock);
1303 me->verrno = E_REMOTE;
1304 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE) || !num_entries)
1305 goto fallback;
1307 if (SUP.strict == RFC_AUTODETECT)
1308 SUP.strict = RFC_DARING;
1310 #ifdef FIXME_LATER
1311 if (has_symlinks) {
1312 if (resolve_symlinks)
1313 resolve_symlink(me, super, dcache);
1314 else
1315 dcache->symlink_status = FTPFS_UNRESOLVED_SYMLINKS;
1317 #endif
1318 print_vfs_message (_("%s: done."), me->name);
1319 return 0;
1321 fallback:
1322 if (SUP.strict == RFC_AUTODETECT) {
1323 /* It's our first attempt to get a directory listing from this
1324 server (UNIX style LIST command) */
1325 SUP.strict = RFC_STRICT;
1326 /* I hate goto, but recursive call needs another 8K on stack */
1327 /* return dir_load (me, dir, remote_path); */
1328 cd_first = 1;
1329 goto again;
1331 print_vfs_message(_("ftpfs: failed; nowhere to fallback to"));
1332 ERRNOR(-1, EACCES);
1335 static int
1336 file_store(vfs *me, vfs_s_fh *fh, char *name, char *localname)
1338 int h, sock, n;
1339 off_t total;
1340 #ifdef HAVE_STRUCT_LINGER
1341 struct linger li;
1342 #else
1343 int flag_one = 1;
1344 #endif
1345 char buffer[8192];
1346 struct stat s;
1347 vfs_s_super *super = FH_SUPER;
1349 h = open(localname, O_RDONLY);
1350 if (h == -1)
1351 ERRNOR (EIO, -1);
1352 fstat(h, &s);
1353 sock = open_data_connection(me, super, fh->u.ftp.append ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1354 if (sock < 0) {
1355 close(h);
1356 return -1;
1358 #ifdef HAVE_STRUCT_LINGER
1359 li.l_onoff = 1;
1360 li.l_linger = 120;
1361 setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li));
1362 #else
1363 setsockopt(sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
1364 #endif
1365 total = 0;
1367 enable_interrupt_key();
1368 while (1) {
1369 while ((n = read(h, buffer, sizeof(buffer))) < 0) {
1370 if (errno == EINTR) {
1371 if (got_interrupt()) {
1372 my_errno = EINTR;
1373 goto error_return;
1375 else
1376 continue;
1378 my_errno = errno;
1379 goto error_return;
1381 if (n == 0)
1382 break;
1383 while (write(sock, buffer, n) < 0) {
1384 if (errno == EINTR) {
1385 if (got_interrupt()) {
1386 my_errno = EINTR;
1387 goto error_return;
1389 else
1390 continue;
1392 my_errno = errno;
1393 goto error_return;
1395 total += n;
1396 print_vfs_message(_("ftpfs: storing file %lu (%lu)"),
1397 (unsigned long) total, (unsigned long) s.st_size);
1399 disable_interrupt_key();
1400 close(sock);
1401 close(h);
1402 if (get_reply (me, SUP.sock, NULL, 0) != COMPLETE)
1403 ERRNOR (EIO, -1);
1404 return 0;
1405 error_return:
1406 disable_interrupt_key();
1407 close(sock);
1408 close(h);
1409 get_reply(me, SUP.sock, NULL, 0);
1410 return -1;
1413 //#define FH_SOCK fh->u.ftp.sock
1415 static int
1416 linear_start(vfs *me, vfs_s_fh *fh, int offset)
1418 char *name = vfs_s_fullpath (me, fh->ino);
1420 if (!name)
1421 return 0;
1422 FH_SOCK = open_data_connection(me, FH_SUPER, "RETR", name, TYPE_BINARY, offset);
1423 g_free (name);
1424 if (FH_SOCK == -1)
1425 ERRNOR (EACCES, 0);
1426 fh->linear = LS_LINEAR_OPEN;
1427 FH_SUPER->u.ftp.control_connection_buzy = 1;
1428 fh->u.ftp.append = 0;
1429 return 1;
1432 static int
1433 linear_read (vfs *me, vfs_s_fh *fh, void *buf, int len)
1435 int n;
1436 vfs_s_super *super = FH_SUPER;
1438 while ((n = read (FH_SOCK, buf, len))<0) {
1439 if ((errno == EINTR) && !got_interrupt())
1440 continue;
1441 break;
1444 if (n<0)
1445 linear_abort(me, fh);
1447 if (!n) {
1448 SUP.control_connection_buzy = 0;
1449 close (FH_SOCK);
1450 FH_SOCK = -1;
1451 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE))
1452 ERRNOR (E_REMOTE, -1);
1453 return 0;
1455 ERRNOR (errno, n);
1458 static void
1459 linear_close (vfs *me, vfs_s_fh *fh)
1461 if (FH_SOCK != -1)
1462 linear_abort(me, fh);
1465 static int ftpfs_ctl (void *fh, int ctlop, int arg)
1467 switch (ctlop) {
1468 case MCCTL_IS_NOTREADY:
1470 int v;
1472 if (!FH->linear)
1473 vfs_die ("You may not do this");
1474 if (FH->linear == LS_LINEAR_CLOSED)
1475 return 0;
1477 v = vfs_s_select_on_two (FH->u.ftp.sock, 0);
1478 if (((v < 0) && (errno == EINTR)) || v == 0)
1479 return 1;
1480 return 0;
1482 default:
1483 return 0;
1487 /* Warning: filename passed to this command is damaged */
1488 static int
1489 send_ftp_command(vfs *me, char *filename, char *cmd, int flags)
1491 char *rpath, *p;
1492 vfs_s_super *super;
1493 int r;
1494 int flush_directory_cache = (flags & OPT_FLUSH);
1496 if (!(rpath = vfs_s_get_path_mangle(me, filename, &super, 0)))
1497 return -1;
1498 p = translate_path (me, super, rpath);
1499 r = command (me, super, WAIT_REPLY, cmd, p);
1500 g_free (p);
1501 vfs_add_noncurrent_stamps (&vfs_ftpfs_ops, (vfsid) super, NULL);
1502 if (flags & OPT_IGNORE_ERROR)
1503 r = COMPLETE;
1504 if (r != COMPLETE)
1505 ERRNOR (EPERM, -1);
1506 if (flush_directory_cache)
1507 vfs_s_invalidate(me, super);
1508 return 0;
1511 /* This routine is called as the last step in load_setup */
1512 void
1513 ftpfs_init_passwd(void)
1515 ftpfs_anonymous_passwd = load_anon_passwd ();
1516 if (ftpfs_anonymous_passwd)
1517 return;
1519 /* If there is no anonymous ftp password specified
1520 * then we'll just use anonymous@
1521 * We don't send any other thing because:
1522 * - We want to remain anonymous
1523 * - We want to stop SPAM
1524 * - We don't want to let ftp sites to discriminate by the user,
1525 * host or country.
1527 ftpfs_anonymous_passwd = g_strdup ("anonymous@");
1530 static int ftpfs_chmod (vfs *me, char *path, int mode)
1532 char buf[BUF_SMALL];
1534 g_snprintf(buf, sizeof(buf), "SITE CHMOD %4.4o /%%s", mode & 07777);
1535 return send_ftp_command(me, path, buf, OPT_FLUSH);
1538 static int ftpfs_chown (vfs *me, char *path, int owner, int group)
1540 #if 0
1541 my_errno = EPERM;
1542 return -1;
1543 #else
1544 /* Everyone knows it is not possible to chown remotely, so why bother them.
1545 If someone's root, then copy/move will always try to chown it... */
1546 return 0;
1547 #endif
1550 static int ftpfs_unlink (vfs *me, char *path)
1552 return send_ftp_command(me, path, "DELE /%s", OPT_FLUSH);
1555 /* Return 1 if path is the same directory as the one we are in now */
1556 static int
1557 is_same_dir (vfs *me, vfs_s_super *super, const char *path)
1559 if (!SUP.cwdir)
1560 return 0;
1561 if (strcmp (path, SUP.cwdir) == 0)
1562 return 1;
1563 return 0;
1566 static int
1567 ftpfs_chdir_internal (vfs *me, vfs_s_super *super, char *remote_path)
1569 int r;
1570 char *p;
1572 if (!SUP.cwd_defered && is_same_dir (me, super, remote_path))
1573 return COMPLETE;
1575 p = translate_path (me, super, remote_path);
1576 r = command (me, super, WAIT_REPLY, "CWD /%s", p);
1577 g_free (p);
1579 if (r != COMPLETE) {
1580 my_errno = EIO;
1581 } else {
1582 g_free(SUP.cwdir);
1583 SUP.cwdir = g_strdup (remote_path);
1584 SUP.cwd_defered = 0;
1586 return r;
1589 static int ftpfs_rename (vfs *me, char *path1, char *path2)
1591 send_ftp_command(me, path1, "RNFR /%s", OPT_FLUSH);
1592 return send_ftp_command(me, path2, "RNTO /%s", OPT_FLUSH);
1595 static int ftpfs_mkdir (vfs *me, char *path, mode_t mode)
1597 return send_ftp_command(me, path, "MKD /%s", OPT_FLUSH);
1600 static int ftpfs_rmdir (vfs *me, char *path)
1602 return send_ftp_command(me, path, "RMD /%s", OPT_FLUSH);
1605 static int ftpfs_fh_open (vfs *me, vfs_s_fh *fh, int flags, int mode)
1607 fh->u.ftp.append = 0;
1608 /* File will be written only, so no need to retrieve it from ftp server */
1609 if (((flags & O_WRONLY) == O_WRONLY) && !(flags & (O_RDONLY|O_RDWR))){
1610 #ifdef HAVE_STRUCT_LINGER
1611 struct linger li;
1612 #else
1613 int li = 1;
1614 #endif
1615 char * name;
1617 /* linear_start() called, so data will be written
1618 * to local temporary file and stored to ftp server
1619 * by vfs_s_close later
1621 if (FH_SUPER->u.ftp.control_connection_buzy){
1622 if (!fh->ino->localname){
1623 int handle = mc_mkstemps (&fh->ino->localname, me->name, NULL);
1624 if (handle == -1)
1625 return -1;
1626 close (handle);
1627 fh->u.ftp.append = flags & O_APPEND;
1629 return 0;
1631 name = vfs_s_fullpath (me, fh->ino);
1632 if (!name)
1633 return -1;
1634 fh->handle = open_data_connection(me, fh->ino->super,
1635 (flags & O_APPEND) ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1636 g_free (name);
1638 if (fh->handle < 0)
1639 return -1;
1640 #ifdef HAVE_STRUCT_LINGER
1641 li.l_onoff = 1;
1642 li.l_linger = 120;
1643 #endif
1644 setsockopt(fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
1646 if (fh->ino->localname){
1647 unlink (fh->ino->localname);
1648 g_free (fh->ino->localname);
1649 fh->ino->localname = NULL;
1651 return 0;
1654 if (!fh->ino->localname)
1655 if (vfs_s_retrieve_file (me, fh->ino)==-1)
1656 return -1;
1657 if (!fh->ino->localname)
1658 vfs_die( "retrieve_file failed to fill in localname" );
1659 return 0;
1662 static int ftpfs_fh_close (vfs *me, vfs_s_fh *fh)
1664 if (fh->handle != -1 && !fh->ino->localname){
1665 close (fh->handle);
1666 fh->handle = -1;
1667 /* File is stored to destination already, so
1668 * we prevent MEDATA->file_store() call from vfs_s_close ()
1670 fh->changed = 0;
1671 if (get_reply (me, fh->ino->SUP.sock, NULL, 0) != COMPLETE)
1672 ERRNOR (EIO, -1);
1673 vfs_s_invalidate (me, FH_SUPER);
1675 return 0;
1678 static struct vfs_s_data ftp_data = {
1679 NULL,
1682 NULL, /* logfile */
1684 NULL, /* init_inode */
1685 NULL, /* free_inode */
1686 NULL, /* init_entry */
1688 NULL, /* archive_check */
1689 archive_same,
1690 open_archive,
1691 free_archive,
1693 ftpfs_fh_open, /* fh_open */
1694 ftpfs_fh_close, /* fh_close */
1696 vfs_s_find_entry_linear,
1697 dir_load,
1698 dir_uptodate,
1699 file_store,
1701 linear_start,
1702 linear_read,
1703 linear_close
1706 static void
1707 ftpfs_fill_names (vfs *me, void (*func)(char *))
1709 struct vfs_s_super * super = ftp_data.supers;
1710 char *name;
1712 while (super){
1713 name = g_strconcat ("/#ftp:", SUP.user, "@", SUP.host, "/", SUP.cwdir, NULL);
1714 (*func)(name);
1715 g_free (name);
1716 super = super->next;
1720 vfs vfs_ftpfs_ops = {
1721 NULL, /* This is place of next pointer */
1722 "ftpfs",
1723 F_NET, /* flags */
1724 "ftp:", /* prefix */
1725 &ftp_data, /* data */
1726 0, /* errno */
1727 NULL, /* init */
1728 NULL, /* done */
1729 ftpfs_fill_names,
1730 NULL,
1732 vfs_s_open,
1733 vfs_s_close,
1734 vfs_s_read,
1735 vfs_s_write,
1737 vfs_s_opendir,
1738 vfs_s_readdir,
1739 vfs_s_closedir,
1740 vfs_s_telldir,
1741 vfs_s_seekdir,
1743 vfs_s_stat,
1744 vfs_s_lstat,
1745 vfs_s_fstat,
1747 ftpfs_chmod,
1748 ftpfs_chown, /* not really implemented but returns success */
1749 NULL,
1751 vfs_s_readlink,
1752 NULL,
1753 NULL,
1754 ftpfs_unlink,
1756 ftpfs_rename,
1757 vfs_s_chdir,
1758 vfs_s_ferrno,
1759 vfs_s_lseek,
1760 NULL,
1762 vfs_s_getid,
1763 vfs_s_nothingisopen,
1764 vfs_s_free,
1766 NULL,
1767 NULL,
1769 ftpfs_mkdir,
1770 ftpfs_rmdir,
1771 ftpfs_ctl,
1772 vfs_s_setctl
1774 MMAPNULL
1777 void ftpfs_set_debug (const char *file)
1779 logfile = fopen (file, "w+");
1780 if (logfile)
1781 ftp_data.logfile = logfile;
1784 static char buffer[BUF_MEDIUM];
1785 static char *netrc, *netrcp;
1787 /* This should match the keywords[] array below */
1788 typedef enum {
1789 NETRC_NONE = 0,
1790 NETRC_DEFAULT,
1791 NETRC_MACHINE,
1792 NETRC_LOGIN,
1793 NETRC_PASSWORD,
1794 NETRC_PASSWD,
1795 NETRC_ACCOUNT,
1796 NETRC_MACDEF,
1797 NETRC_UNKNOWN
1798 } keyword_t;
1800 static keyword_t netrc_next (void)
1802 char *p;
1803 keyword_t i;
1804 static const char *const keywords[] = { "default", "machine",
1805 "login", "password", "passwd", "account", "macdef", NULL
1809 while (1) {
1810 netrcp = skip_separators (netrcp);
1811 if (*netrcp != '\n')
1812 break;
1813 netrcp++;
1815 if (!*netrcp)
1816 return NETRC_NONE;
1817 p = buffer;
1818 if (*netrcp == '"') {
1819 for (netrcp++; *netrcp != '"' && *netrcp; netrcp++) {
1820 if (*netrcp == '\\')
1821 netrcp++;
1822 *p++ = *netrcp;
1824 } else {
1825 for (; *netrcp != '\n' && *netrcp != '\t' && *netrcp != ' ' &&
1826 *netrcp != ',' && *netrcp; netrcp++) {
1827 if (*netrcp == '\\')
1828 netrcp++;
1829 *p++ = *netrcp;
1832 *p = 0;
1833 if (!*buffer)
1834 return 0;
1836 i = NETRC_DEFAULT;
1837 while (keywords[i - 1]) {
1838 if (!strcmp (keywords[i - 1], buffer))
1839 return i;
1841 i++;
1844 return NETRC_UNKNOWN;
1847 static int netrc_has_incorrect_mode (char *netrcname, char *netrc)
1849 static int be_angry = 1;
1850 struct stat mystat;
1852 if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077)) {
1853 if (be_angry) {
1854 message_1s (1, MSG_ERROR,
1855 _("~/.netrc file has not correct mode.\n"
1856 "Remove password or correct mode."));
1857 be_angry = 0;
1859 return 1;
1861 return 0;
1864 /* Scan .netrc until we find matching "machine" or "default"
1865 * domain is used for additional matching
1866 * No search is done after "default" in compliance with "man netrc"
1867 * Return 0 if found, -1 otherwise */
1868 static int find_machine (const char *host, const char *domain)
1870 keyword_t keyword;
1872 while ((keyword = netrc_next ()) != NETRC_NONE) {
1873 if (keyword == NETRC_DEFAULT)
1874 return 0;
1876 if (keyword == NETRC_MACDEF) {
1877 /* Scan for an empty line, which concludes "macdef" */
1878 do {
1879 while (*netrcp && *netrcp != '\n')
1880 netrcp++;
1881 if (*netrcp != '\n')
1882 break;
1883 netrcp++;
1884 } while (*netrcp && *netrcp != '\n');
1885 continue;
1888 if (keyword != NETRC_MACHINE)
1889 continue;
1891 /* Take machine name */
1892 if (netrc_next () == NETRC_NONE)
1893 break;
1895 if (g_strcasecmp (host, buffer)) {
1896 /* Try adding our domain to short names in .netrc */
1897 char *host_domain = strchr (host, '.');
1898 if (!host_domain)
1899 continue;
1901 /* Compare domain part */
1902 if (g_strcasecmp (host_domain, domain))
1903 continue;
1905 /* Compare local part */
1906 if (g_strncasecmp (host, buffer, host_domain - host))
1907 continue;
1910 return 0;
1913 /* end of .netrc */
1914 return -1;
1917 /* Extract login and password from .netrc for the host.
1918 * pass may be NULL.
1919 * Returns 0 for success, -1 for error */
1920 static int lookup_netrc (const char *host, char **login, char **pass)
1922 char *netrcname;
1923 char *tmp_pass = NULL;
1924 char hostname[MAXHOSTNAMELEN], *domain;
1925 keyword_t keyword;
1926 static struct rupcache {
1927 struct rupcache *next;
1928 char *host;
1929 char *login;
1930 char *pass;
1931 } *rup_cache = NULL, *rupp;
1933 /* Initialize *login and *pass */
1934 if (!login)
1935 return 0;
1936 *login = NULL;
1937 if (pass)
1938 *pass = NULL;
1940 /* Look up in the cache first */
1941 for (rupp = rup_cache; rupp != NULL; rupp = rupp->next) {
1942 if (!strcmp (host, rupp->host)) {
1943 if (rupp->login)
1944 *login = g_strdup (rupp->login);
1945 if (pass && rupp->pass)
1946 *pass = g_strdup (rupp->pass);
1947 return 0;
1951 /* Load current .netrc */
1952 netrcname = concat_dir_and_file (home_dir, ".netrc");
1953 netrcp = netrc = load_file (netrcname);
1954 if (netrc == NULL) {
1955 g_free (netrcname);
1956 return 0;
1959 /* Find our own domain name */
1960 if (gethostname (hostname, sizeof (hostname)) < 0)
1961 *hostname = 0;
1962 if (!(domain = strchr (hostname, '.')))
1963 domain = "";
1965 /* Scan for "default" and matching "machine" keywords */
1966 find_machine (host, domain);
1968 /* Scan for keywords following "default" and "machine" */
1969 while (1) {
1970 int need_break = 0;
1971 keyword = netrc_next ();
1973 switch (keyword) {
1974 case NETRC_LOGIN:
1975 if (netrc_next () == NETRC_NONE) {
1976 need_break = 1;
1977 break;
1980 /* We have another name already - should not happen */
1981 if (*login) {
1982 need_break = 1;
1983 break;
1986 /* We have login name now */
1987 *login = g_strdup (buffer);
1988 break;
1990 case NETRC_PASSWORD:
1991 case NETRC_PASSWD:
1992 if (netrc_next () == NETRC_NONE) {
1993 need_break = 1;
1994 break;
1997 /* Ignore unsafe passwords */
1998 if (strcmp (*login, "anonymous") && strcmp (*login, "ftp")
1999 && netrc_has_incorrect_mode (netrcname, netrc)) {
2000 need_break = 1;
2001 break;
2004 /* Remember password. pass may be NULL, so use tmp_pass */
2005 if (tmp_pass == NULL)
2006 tmp_pass = g_strdup (buffer);
2007 break;
2009 case NETRC_ACCOUNT:
2010 /* "account" is followed by a token which we ignore */
2011 if (netrc_next () == NETRC_NONE) {
2012 need_break = 1;
2013 break;
2016 /* Ignore account, but warn user anyways */
2017 netrc_has_incorrect_mode (netrcname, netrc);
2018 break;
2020 default:
2021 /* Unexpected keyword or end of file */
2022 need_break = 1;
2023 break;
2026 if (need_break)
2027 break;
2030 g_free (netrc);
2031 g_free (netrcname);
2033 rupp = g_new (struct rupcache, 1);
2034 rupp->host = g_strdup (host);
2035 rupp->login = rupp->pass = 0;
2037 if (*login != NULL) {
2038 rupp->login = g_strdup (*login);
2040 if (tmp_pass != NULL)
2041 rupp->pass = g_strdup (tmp_pass);
2042 rupp->next = rup_cache;
2043 rup_cache = rupp;
2045 if (pass)
2046 *pass = tmp_pass;
2048 return 0;