mc.sh & mc.csh creation fixed...
[midnight-commander.git] / vfs / ftpfs.c
blobb4d14023ee843b36064c3644e43f547ed200a271
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 /* Return 1 if FTP proxy should be used for this host, 0 otherwise */
576 static int
577 ftpfs_check_proxy (const char *host)
579 struct no_proxy_entry *npe;
581 if (!ftpfs_proxy_host || !*ftpfs_proxy_host || !host || !*host)
582 return 0; /* sanity check */
584 if (*host == '!')
585 return 1;
587 if (!ftpfs_always_use_proxy)
588 return 0;
590 if (!strchr (host, '.'))
591 return 0;
593 load_no_proxy_list ();
594 for (npe = no_proxy; npe; npe=npe->next) {
595 char *domain = npe->domain;
597 if (domain[0] == '.') {
598 int ld = strlen (domain);
599 int lh = strlen (host);
601 while (ld && lh && host[lh - 1] == domain[ld - 1]) {
602 ld--;
603 lh--;
606 if (!ld)
607 return 0;
608 } else
609 if (!g_strcasecmp (host, domain))
610 return 0;
613 return 1;
616 static void
617 ftpfs_get_proxy_host_and_port (char *proxy, char **host, int *port)
619 char *user, *dir;
621 dir =
622 vfs_split_url (proxy, host, &user, port, 0, FTP_COMMAND_PORT,
623 URL_ALLOW_ANON);
624 g_free (user);
625 g_free (dir);
628 static int
629 ftpfs_open_socket (vfs *me, vfs_s_super *super)
631 struct sockaddr_in server_address;
632 struct hostent *hp;
633 int my_socket;
634 char *host;
635 int port = SUP.port;
636 int free_host = 0;
638 /* Use a proxy host? */
639 host = SUP.host;
641 if (!host || !*host){
642 print_vfs_message (_("ftpfs: Invalid host name."));
643 my_errno = EINVAL;
644 return -1;
647 /* Hosts to connect to that start with a ! should use proxy */
648 if (SUP.proxy){
649 ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
650 free_host = 1;
653 /* Get host address */
654 memset ((char *) &server_address, 0, sizeof (server_address));
655 server_address.sin_family = AF_INET;
656 server_address.sin_addr.s_addr = inet_addr (host);
657 if (server_address.sin_addr.s_addr != -1)
658 server_address.sin_family = AF_INET;
659 else {
660 hp = gethostbyname (host);
661 if (hp == NULL){
662 print_vfs_message (_("ftpfs: Invalid host address."));
663 my_errno = EINVAL;
664 if (free_host)
665 g_free (host);
666 return -1;
668 server_address.sin_family = hp->h_addrtype;
670 /* We copy only 4 bytes, we can not trust hp->h_length, as it comes from the DNS */
671 memcpy ((char *) &server_address.sin_addr, (char *) hp->h_addr, 4);
674 server_address.sin_port = htons (port);
676 /* Connect */
677 if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
678 my_errno = errno;
679 if (free_host)
680 g_free (host);
681 return -1;
683 setup_source_route (my_socket, server_address.sin_addr.s_addr);
685 print_vfs_message (_("ftpfs: making connection to %s"), host);
686 if (free_host)
687 g_free (host);
689 enable_interrupt_key (); /* clear the interrupt flag */
691 if (connect (my_socket, (struct sockaddr *) &server_address,
692 sizeof (server_address)) < 0){
693 my_errno = errno;
694 if (errno == EINTR && got_interrupt ())
695 print_vfs_message (_("ftpfs: connection interrupted by user"));
696 else
697 print_vfs_message (_("ftpfs: connection to server failed: %s"),
698 unix_error_string(errno));
699 disable_interrupt_key();
700 close (my_socket);
701 return -1;
703 disable_interrupt_key();
704 return my_socket;
707 static int
708 open_archive_int (vfs *me, vfs_s_super *super)
710 int retry_seconds, count_down;
712 /* We do not want to use the passive if we are using proxies */
713 if (SUP.proxy)
714 SUP.use_passive_connection = 0;
716 retry_seconds = 0;
717 do {
718 SUP.failed_on_login = 0;
720 SUP.sock = ftpfs_open_socket (me, super);
721 if (SUP.sock == -1)
722 return -1;
724 if (login_server (me, super, NULL)) {
725 /* Logged in, no need to retry the connection */
726 break;
727 } else {
728 if (SUP.failed_on_login){
729 /* Close only the socket descriptor */
730 close (SUP.sock);
731 } else {
732 return -1;
734 if (ftpfs_retry_seconds){
735 retry_seconds = ftpfs_retry_seconds;
736 enable_interrupt_key ();
737 for (count_down = retry_seconds; count_down; count_down--){
738 print_vfs_message (_("Waiting to retry... %d (Control-C to cancel)"), count_down);
739 sleep (1);
740 if (got_interrupt ()){
741 /* my_errno = E; */
742 disable_interrupt_key ();
743 return 0;
746 disable_interrupt_key ();
749 } while (retry_seconds);
751 SUP.cwdir = ftpfs_get_current_directory (me, super);
752 if (!SUP.cwdir)
753 SUP.cwdir = g_strdup (PATH_SEP_STR);
754 return 0;
757 static int
758 open_archive (vfs *me, vfs_s_super *super, char *archive_name, char *op)
760 char *host, *user, *password;
761 int port;
763 ftp_split_url (strchr (op, ':') + 1, &host, &user, &port, &password);
765 SUP.host = host;
766 SUP.user = user;
767 SUP.port = port;
768 SUP.cwdir = NULL;
769 SUP.proxy = 0;
770 if (ftpfs_check_proxy (host))
771 SUP.proxy = ftpfs_proxy_host;
772 SUP.password = password;
773 SUP.use_passive_connection = ftpfs_use_passive_connections | source_route;
774 SUP.use_source_route = source_route;
775 SUP.strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
776 SUP.isbinary = TYPE_UNKNOWN;
777 SUP.remote_is_amiga = 0;
778 super->name = g_strdup("/");
779 super->root = vfs_s_new_inode (me, super, vfs_s_default_stat(me, S_IFDIR | 0755));
781 return open_archive_int (me, super);
784 static int
785 archive_same(vfs *me, vfs_s_super *super, char *archive_name, char *op, void *cookie)
787 char *host, *user;
788 int port;
790 ftp_split_url (strchr(op, ':') + 1, &host, &user, &port, 0);
792 port = ((strcmp (host, SUP.host) == 0) &&
793 (strcmp (user, SUP.user) == 0) &&
794 (port == SUP.port));
796 g_free (host);
797 g_free (user);
799 return port;
802 void
803 ftpfs_flushdir (void)
805 force_expiration = 1;
808 static int
809 dir_uptodate(vfs *me, vfs_s_inode *ino)
811 struct timeval tim;
813 if (force_expiration) {
814 force_expiration = 0;
815 return 0;
817 gettimeofday(&tim, NULL);
818 if (tim.tv_sec < ino->u.ftp.timestamp.tv_sec)
819 return 1;
820 return 0;
823 /* The returned directory should always contain a trailing slash */
824 static char *
825 ftpfs_get_current_directory (vfs *me, vfs_s_super *super)
827 char buf[BUF_8K], *bufp, *bufq;
829 if (command (me, super, NONE, "PWD") == COMPLETE &&
830 get_reply(me, SUP.sock, buf, sizeof(buf)) == COMPLETE) {
831 bufp = NULL;
832 for (bufq = buf; *bufq; bufq++)
833 if (*bufq == '"') {
834 if (!bufp) {
835 bufp = bufq + 1;
836 } else {
837 *bufq = 0;
838 if (*bufp) {
839 if (*(bufq - 1) != '/') {
840 *bufq++ = '/';
841 *bufq = 0;
843 if (*bufp == '/')
844 return g_strdup (bufp);
845 else {
846 /* If the remote server is an Amiga a leading slash
847 might be missing. MC needs it because it is used
848 as seperator between hostname and path internally. */
849 return g_strconcat( "/", bufp, 0);
851 } else {
852 my_errno = EIO;
853 return NULL;
858 my_errno = EIO;
859 return NULL;
863 /* Setup Passive ftp connection, we use it for source routed connections */
864 static int
865 setup_passive (vfs *me, vfs_s_super *super, int my_socket, struct sockaddr_in *sa)
867 int xa, xb, xc, xd, xe, xf;
868 char n [6];
869 char *c = reply_str;
871 if (command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
872 return 0;
874 /* Parse remote parameters */
875 for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++)
877 if (!*c)
878 return 0;
879 if (!isdigit ((unsigned char) *c))
880 return 0;
881 if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
882 return 0;
883 n [0] = (unsigned char) xa;
884 n [1] = (unsigned char) xb;
885 n [2] = (unsigned char) xc;
886 n [3] = (unsigned char) xd;
887 n [4] = (unsigned char) xe;
888 n [5] = (unsigned char) xf;
890 memcpy (&(sa->sin_addr.s_addr), (void *)n, 4);
891 memcpy (&(sa->sin_port), (void *)&n[4], 2);
892 setup_source_route (my_socket, sa->sin_addr.s_addr);
893 if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0)
894 return 0;
895 return 1;
898 static int
899 initconn (vfs *me, vfs_s_super *super)
901 struct sockaddr_in data_addr;
902 int data, len = sizeof(data_addr);
903 struct protoent *pe;
905 getsockname(SUP.sock, (struct sockaddr *) &data_addr, &len);
906 data_addr.sin_port = 0;
908 pe = getprotobyname("tcp");
909 if (pe == NULL)
910 ERRNOR (EIO, -1);
911 data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
912 if (data < 0)
913 ERRNOR (EIO, -1);
915 if (SUP.use_passive_connection){
916 if ((SUP.use_passive_connection = setup_passive (me, super, data, &data_addr)))
917 return data;
919 SUP.use_source_route = 0;
920 SUP.use_passive_connection = 0;
921 print_vfs_message (_("ftpfs: could not setup passive mode"));
924 /* If passive setup fails, fallback to active connections */
925 /* Active FTP connection */
926 if ((bind (data, (struct sockaddr *)&data_addr, len) == 0) &&
927 (getsockname (data, (struct sockaddr *) &data_addr, &len) == 0) &&
928 (listen (data, 1) == 0))
930 unsigned char *a = (unsigned char *)&data_addr.sin_addr;
931 unsigned char *p = (unsigned char *)&data_addr.sin_port;
933 if (command (me, super, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1],
934 a[2], a[3], p[0], p[1]) == COMPLETE)
935 return data;
937 close(data);
938 my_errno = EIO;
939 return -1;
942 static int
943 open_data_connection (vfs *me, vfs_s_super *super, char *cmd, char *remote,
944 int isbinary, int reget)
946 struct sockaddr_in from;
947 int s, j, data, fromlen = sizeof(from);
949 if ((s = initconn (me, super)) == -1)
950 return -1;
951 if (changetype (me, super, isbinary) == -1)
952 return -1;
953 if (reget > 0){
954 j = command (me, super, WAIT_REPLY, "REST %d", reget);
955 if (j != CONTINUE)
956 return -1;
958 if (remote) {
959 char * remote_path = translate_path (me, super, remote);
960 j = command (me, super, WAIT_REPLY, "%s /%s", cmd,
961 /* WarFtpD can't STORE //filename */
962 (*remote_path == '/') ? remote_path + 1 : remote_path);
963 g_free (remote_path);
964 } else
965 j = command (me, super, WAIT_REPLY, "%s", cmd);
966 if (j != PRELIM)
967 ERRNOR (EPERM, -1);
968 enable_interrupt_key();
969 if (SUP.use_passive_connection)
970 data = s;
971 else {
972 data = accept (s, (struct sockaddr *)&from, &fromlen);
973 close(s);
974 if (data < 0) {
975 my_errno = errno;
976 return -1;
979 disable_interrupt_key();
980 return data;
983 #define ABORT_TIMEOUT 5
984 static void
985 linear_abort (vfs *me, vfs_s_fh *fh)
987 vfs_s_super *super = FH_SUPER;
988 static unsigned char const ipbuf[3] = { IAC, IP, IAC };
989 fd_set mask;
990 char buf[1024];
991 int dsock = FH_SOCK;
992 FH_SOCK = -1;
993 SUP.control_connection_buzy = 0;
995 print_vfs_message (_("ftpfs: aborting transfer."));
996 if (send (SUP.sock, ipbuf, sizeof (ipbuf), MSG_OOB) != sizeof (ipbuf)) {
997 print_vfs_message (_("ftpfs: abort error: %s"),
998 unix_error_string (errno));
999 return;
1002 if (command (me, super, NONE, "%cABOR", DM) != COMPLETE) {
1003 print_vfs_message (_("ftpfs: abort failed"));
1004 return;
1006 if (dsock != -1) {
1007 FD_ZERO (&mask);
1008 FD_SET (dsock, &mask);
1009 if (select (dsock + 1, &mask, NULL, NULL, NULL) > 0) {
1010 struct timeval start_tim, tim;
1011 gettimeofday (&start_tim, NULL);
1012 /* flush the remaining data */
1013 while (read (dsock, buf, sizeof (buf)) > 0) {
1014 gettimeofday (&tim, NULL);
1015 if (tim.tv_sec > start_tim.tv_sec + ABORT_TIMEOUT) {
1016 /* server keeps sending, drop the connection and reconnect */
1017 reconnect (me, super);
1018 return;
1024 if (dsock != -1)
1025 close (dsock);
1027 if ((get_reply (me, SUP.sock, NULL, 0) == TRANSIENT) && (code == 426))
1028 get_reply (me, SUP.sock, NULL, 0);
1031 #if 0
1032 static void
1033 resolve_symlink_without_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1035 struct linklist *flist;
1036 struct direntry *fe, *fel;
1037 char tmp[MC_MAXPATHLEN];
1038 int depth;
1040 dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
1041 for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next) {
1042 /* flist->data->l_stat is alread initialized with 0 */
1043 fel = flist->data;
1044 if (S_ISLNK(fel->s.st_mode) && fel->linkname) {
1045 if (fel->linkname[0] == '/') {
1046 if (strlen (fel->linkname) >= MC_MAXPATHLEN)
1047 continue;
1048 strcpy (tmp, fel->linkname);
1049 } else {
1050 if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
1051 continue;
1052 strcpy (tmp, dir->remote_path);
1053 if (tmp[1] != '\0')
1054 strcat (tmp, "/");
1055 strcat (tmp + 1, fel->linkname);
1057 for ( depth = 0; depth < 100; depth++) { /* depth protects against recursive symbolic links */
1058 canonicalize_pathname (tmp);
1059 fe = _get_file_entry(bucket, tmp, 0, 0);
1060 if (fe) {
1061 if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0) {
1062 /* Symlink points to link which isn't resolved, yet. */
1063 if (fe->linkname[0] == '/') {
1064 if (strlen (fe->linkname) >= MC_MAXPATHLEN)
1065 break;
1066 strcpy (tmp, fe->linkname);
1067 } else {
1068 /* at this point tmp looks always like this
1069 /directory/filename, i.e. no need to check
1070 strrchr's return value */
1071 *(strrchr (tmp, '/') + 1) = '\0'; /* dirname */
1072 if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
1073 break;
1074 strcat (tmp, fe->linkname);
1076 continue;
1077 } else {
1078 fel->l_stat = g_new (struct stat, 1);
1079 if ( S_ISLNK (fe->s.st_mode))
1080 *fel->l_stat = *fe->l_stat;
1081 else
1082 *fel->l_stat = fe->s;
1083 (*fel->l_stat).st_ino = bucket->__inode_counter++;
1086 break;
1090 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1093 static void
1094 resolve_symlink_with_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1096 char buffer[2048] = "", *filename;
1097 int sock;
1098 FILE *fp;
1099 struct stat s;
1100 struct linklist *flist;
1101 struct direntry *fe;
1102 int switch_method = 0;
1104 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1105 if (strchr (dir->remote_path, ' ')) {
1106 if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE) {
1107 print_vfs_message(_("ftpfs: CWD failed."));
1108 return;
1110 sock = open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
1112 else
1113 sock = open_data_connection (bucket, "LIST -lLa",
1114 dir->remote_path, TYPE_ASCII, 0);
1116 if (sock == -1) {
1117 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1118 return;
1121 fp = fdopen(sock, "r");
1122 if (fp == NULL) {
1123 close(sock);
1124 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1125 return;
1127 enable_interrupt_key();
1128 flist = dir->file_list->next;
1129 while (1) {
1130 do {
1131 if (flist == dir->file_list)
1132 goto done;
1133 fe = flist->data;
1134 flist = flist->next;
1135 } while (!S_ISLNK(fe->s.st_mode));
1136 while (1) {
1137 if (fgets (buffer, sizeof (buffer), fp) == NULL)
1138 goto done;
1139 if (logfile){
1140 fputs (buffer, logfile);
1141 fflush (logfile);
1143 vfs_die("This code should be commented out\n");
1144 if (vfs_parse_ls_lga (buffer, &s, &filename, NULL)) {
1145 int r = strcmp(fe->name, filename);
1146 g_free(filename);
1147 if (r == 0) {
1148 if (S_ISLNK (s.st_mode)) {
1149 /* This server doesn't understand LIST -lLa */
1150 switch_method = 1;
1151 goto done;
1153 fe->l_stat = g_new (struct stat, 1);
1154 if (fe->l_stat == NULL)
1155 goto done;
1156 *fe->l_stat = s;
1157 (*fe->l_stat).st_ino = bucket->__inode_counter++;
1158 break;
1160 if (r < 0)
1161 break;
1165 done:
1166 while (fgets(buffer, sizeof(buffer), fp) != NULL);
1167 disable_interrupt_key();
1168 fclose(fp);
1169 get_reply(me, SUP.sock, NULL, 0);
1172 static void
1173 resolve_symlink(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1175 print_vfs_message(_("Resolving symlink..."));
1177 if (SUP.strict_rfc959_list_cmd)
1178 resolve_symlink_without_ls_options(me, super, dir);
1179 else
1180 resolve_symlink_with_ls_options(me, super, dir);
1182 #endif
1184 static int
1185 dir_load(vfs *me, vfs_s_inode *dir, char *remote_path)
1187 vfs_s_entry *ent;
1188 vfs_s_super *super = dir->super;
1189 int sock, num_entries = 0;
1190 #ifdef FIXME_LATER
1191 int has_symlinks = 0;
1192 #endif
1193 char buffer[BUF_8K];
1194 int cd_first;
1196 cd_first = ftpfs_first_cd_then_ls || (strchr (remote_path, ' ') != NULL)
1197 || (SUP.strict == RFC_STRICT);
1199 again:
1200 print_vfs_message(_("ftpfs: Reading FTP directory %s... %s%s"), remote_path,
1201 SUP.strict == RFC_STRICT ? _("(strict rfc959)") : "",
1202 cd_first ? _("(chdir first)") : "");
1204 if (cd_first) {
1205 char *p;
1207 p = translate_path (me, super, remote_path);
1209 if (ftpfs_chdir_internal (me, super, p) != COMPLETE) {
1210 g_free (p);
1211 my_errno = ENOENT;
1212 print_vfs_message(_("ftpfs: CWD failed."));
1213 return -1;
1215 g_free (p);
1218 gettimeofday(&dir->u.ftp.timestamp, NULL);
1219 dir->u.ftp.timestamp.tv_sec += ftpfs_directory_timeout;
1221 if (SUP.strict == RFC_STRICT)
1222 sock = open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
1223 else if (cd_first)
1224 /* Dirty hack to avoid autoprepending / to . */
1225 /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
1226 sock = open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
1227 else {
1228 /* Trailing "/." is necessary if remote_path is a symlink */
1229 char *path = concat_dir_and_file (remote_path, ".");
1230 sock = open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0);
1231 g_free (path);
1234 if (sock == -1)
1235 goto fallback;
1237 /* Clear the interrupt flag */
1238 enable_interrupt_key ();
1240 #if 1
1242 /* added 20001006 by gisburn
1243 * add dots '.' and '..'. This must be _executed_ before scanning the dir as the
1244 * code below may jump directly into error handling code (without executing
1245 * remaining code). And C doesn't have try {...} finally {}; :-)
1247 vfs_s_inode *parent = dir->ent->dir;
1249 if( parent==NULL )
1250 parent = dir;
1252 ent = vfs_s_generate_entry(me, ".", dir, 0);
1253 ent->ino->st=dir->st;
1254 num_entries++;
1255 vfs_s_insert_entry(me, dir, ent);
1257 ent = vfs_s_generate_entry(me, "..", parent, 0);
1258 ent->ino->st=parent->st;
1259 num_entries++;
1260 vfs_s_insert_entry(me, dir, ent);
1262 #endif
1264 while (1) {
1265 int i;
1266 int res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), sock);
1267 if (!res)
1268 break;
1270 if (res == EINTR) {
1271 me->verrno = ECONNRESET;
1272 close (sock);
1273 disable_interrupt_key();
1274 get_reply(me, SUP.sock, NULL, 0);
1275 print_vfs_message (_("%s: failure"), me->name);
1276 return -1;
1279 if (logfile){
1280 fputs (buffer, logfile);
1281 fputs ("\n", logfile);
1282 fflush (logfile);
1285 ent = vfs_s_generate_entry(me, NULL, dir, 0);
1286 i = ent->ino->st.st_nlink;
1287 if (!vfs_parse_ls_lga (buffer, &ent->ino->st, &ent->name, &ent->ino->linkname)) {
1288 vfs_s_free_entry (me, ent);
1289 continue;
1291 ent->ino->st.st_nlink = i; /* Ouch, we need to preserve our counts :-( */
1292 num_entries++;
1293 if ((!strcmp(ent->name, ".")) || (!strcmp (ent->name, ".."))) {
1294 g_free (ent->name);
1295 ent->name = NULL; /* Ouch, vfs_s_free_entry "knows" about . and .. being special :-( */
1296 vfs_s_free_entry (me, ent);
1297 continue;
1300 vfs_s_insert_entry(me, dir, ent);
1303 /* vfs_s_add_dots(me, dir, NULL);
1304 FIXME This really should be here; but we need to provide correct parent.
1305 Disabled for now, please fix it. Pavel
1307 close(sock);
1308 me->verrno = E_REMOTE;
1309 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE) || !num_entries)
1310 goto fallback;
1312 if (SUP.strict == RFC_AUTODETECT)
1313 SUP.strict = RFC_DARING;
1315 #ifdef FIXME_LATER
1316 if (has_symlinks) {
1317 if (resolve_symlinks)
1318 resolve_symlink(me, super, dcache);
1319 else
1320 dcache->symlink_status = FTPFS_UNRESOLVED_SYMLINKS;
1322 #endif
1323 print_vfs_message (_("%s: done."), me->name);
1324 return 0;
1326 fallback:
1327 if (SUP.strict == RFC_AUTODETECT) {
1328 /* It's our first attempt to get a directory listing from this
1329 server (UNIX style LIST command) */
1330 SUP.strict = RFC_STRICT;
1331 /* I hate goto, but recursive call needs another 8K on stack */
1332 /* return dir_load (me, dir, remote_path); */
1333 cd_first = 1;
1334 goto again;
1336 print_vfs_message(_("ftpfs: failed; nowhere to fallback to"));
1337 ERRNOR(-1, EACCES);
1340 static int
1341 file_store(vfs *me, vfs_s_fh *fh, char *name, char *localname)
1343 int h, sock, n;
1344 off_t total;
1345 #ifdef HAVE_STRUCT_LINGER
1346 struct linger li;
1347 #else
1348 int flag_one = 1;
1349 #endif
1350 char buffer[8192];
1351 struct stat s;
1352 vfs_s_super *super = FH_SUPER;
1354 h = open(localname, O_RDONLY);
1355 if (h == -1)
1356 ERRNOR (EIO, -1);
1357 fstat(h, &s);
1358 sock = open_data_connection(me, super, fh->u.ftp.append ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1359 if (sock < 0) {
1360 close(h);
1361 return -1;
1363 #ifdef HAVE_STRUCT_LINGER
1364 li.l_onoff = 1;
1365 li.l_linger = 120;
1366 setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li));
1367 #else
1368 setsockopt(sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
1369 #endif
1370 total = 0;
1372 enable_interrupt_key();
1373 while (1) {
1374 while ((n = read(h, buffer, sizeof(buffer))) < 0) {
1375 if (errno == EINTR) {
1376 if (got_interrupt()) {
1377 my_errno = EINTR;
1378 goto error_return;
1380 else
1381 continue;
1383 my_errno = errno;
1384 goto error_return;
1386 if (n == 0)
1387 break;
1388 while (write(sock, buffer, n) < 0) {
1389 if (errno == EINTR) {
1390 if (got_interrupt()) {
1391 my_errno = EINTR;
1392 goto error_return;
1394 else
1395 continue;
1397 my_errno = errno;
1398 goto error_return;
1400 total += n;
1401 print_vfs_message(_("ftpfs: storing file %lu (%lu)"),
1402 (unsigned long) total, (unsigned long) s.st_size);
1404 disable_interrupt_key();
1405 close(sock);
1406 close(h);
1407 if (get_reply (me, SUP.sock, NULL, 0) != COMPLETE)
1408 ERRNOR (EIO, -1);
1409 return 0;
1410 error_return:
1411 disable_interrupt_key();
1412 close(sock);
1413 close(h);
1414 get_reply(me, SUP.sock, NULL, 0);
1415 return -1;
1418 static int
1419 linear_start(vfs *me, vfs_s_fh *fh, int offset)
1421 char *name = vfs_s_fullpath (me, fh->ino);
1423 if (!name)
1424 return 0;
1425 FH_SOCK = open_data_connection(me, FH_SUPER, "RETR", name, TYPE_BINARY, offset);
1426 g_free (name);
1427 if (FH_SOCK == -1)
1428 ERRNOR (EACCES, 0);
1429 fh->linear = LS_LINEAR_OPEN;
1430 FH_SUPER->u.ftp.control_connection_buzy = 1;
1431 fh->u.ftp.append = 0;
1432 return 1;
1435 static int
1436 linear_read (vfs *me, vfs_s_fh *fh, void *buf, int len)
1438 int n;
1439 vfs_s_super *super = FH_SUPER;
1441 while ((n = read (FH_SOCK, buf, len))<0) {
1442 if ((errno == EINTR) && !got_interrupt())
1443 continue;
1444 break;
1447 if (n<0)
1448 linear_abort(me, fh);
1450 if (!n) {
1451 SUP.control_connection_buzy = 0;
1452 close (FH_SOCK);
1453 FH_SOCK = -1;
1454 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE))
1455 ERRNOR (E_REMOTE, -1);
1456 return 0;
1458 ERRNOR (errno, n);
1461 static void
1462 linear_close (vfs *me, vfs_s_fh *fh)
1464 if (FH_SOCK != -1)
1465 linear_abort(me, fh);
1468 static int ftpfs_ctl (void *fh, int ctlop, int arg)
1470 switch (ctlop) {
1471 case MCCTL_IS_NOTREADY:
1473 int v;
1475 if (!FH->linear)
1476 vfs_die ("You may not do this");
1477 if (FH->linear == LS_LINEAR_CLOSED)
1478 return 0;
1480 v = vfs_s_select_on_two (FH->u.ftp.sock, 0);
1481 if (((v < 0) && (errno == EINTR)) || v == 0)
1482 return 1;
1483 return 0;
1485 default:
1486 return 0;
1490 /* Warning: filename passed to this command is damaged */
1491 static int
1492 send_ftp_command(vfs *me, char *filename, char *cmd, int flags)
1494 char *rpath, *p;
1495 vfs_s_super *super;
1496 int r;
1497 int flush_directory_cache = (flags & OPT_FLUSH);
1499 if (!(rpath = vfs_s_get_path_mangle(me, filename, &super, 0)))
1500 return -1;
1501 p = translate_path (me, super, rpath);
1502 r = command (me, super, WAIT_REPLY, cmd, p);
1503 g_free (p);
1504 vfs_add_noncurrent_stamps (&vfs_ftpfs_ops, (vfsid) super, NULL);
1505 if (flags & OPT_IGNORE_ERROR)
1506 r = COMPLETE;
1507 if (r != COMPLETE)
1508 ERRNOR (EPERM, -1);
1509 if (flush_directory_cache)
1510 vfs_s_invalidate(me, super);
1511 return 0;
1514 /* This routine is called as the last step in load_setup */
1515 void
1516 ftpfs_init_passwd(void)
1518 ftpfs_anonymous_passwd = load_anon_passwd ();
1519 if (ftpfs_anonymous_passwd)
1520 return;
1522 /* If there is no anonymous ftp password specified
1523 * then we'll just use anonymous@
1524 * We don't send any other thing because:
1525 * - We want to remain anonymous
1526 * - We want to stop SPAM
1527 * - We don't want to let ftp sites to discriminate by the user,
1528 * host or country.
1530 ftpfs_anonymous_passwd = g_strdup ("anonymous@");
1533 static int ftpfs_chmod (vfs *me, char *path, int mode)
1535 char buf[BUF_SMALL];
1537 g_snprintf(buf, sizeof(buf), "SITE CHMOD %4.4o /%%s", mode & 07777);
1538 return send_ftp_command(me, path, buf, OPT_FLUSH);
1541 static int ftpfs_chown (vfs *me, char *path, int owner, int group)
1543 #if 0
1544 my_errno = EPERM;
1545 return -1;
1546 #else
1547 /* Everyone knows it is not possible to chown remotely, so why bother them.
1548 If someone's root, then copy/move will always try to chown it... */
1549 return 0;
1550 #endif
1553 static int ftpfs_unlink (vfs *me, char *path)
1555 return send_ftp_command(me, path, "DELE /%s", OPT_FLUSH);
1558 /* Return 1 if path is the same directory as the one we are in now */
1559 static int
1560 is_same_dir (vfs *me, vfs_s_super *super, const char *path)
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 (vfs *me, vfs_s_super *super, char *remote_path)
1572 int r;
1573 char *p;
1575 if (!SUP.cwd_defered && is_same_dir (me, super, remote_path))
1576 return COMPLETE;
1578 p = translate_path (me, super, remote_path);
1579 r = command (me, super, WAIT_REPLY, "CWD /%s", p);
1580 g_free (p);
1582 if (r != COMPLETE) {
1583 my_errno = EIO;
1584 } else {
1585 g_free(SUP.cwdir);
1586 SUP.cwdir = g_strdup (remote_path);
1587 SUP.cwd_defered = 0;
1589 return r;
1592 static int ftpfs_rename (vfs *me, char *path1, char *path2)
1594 send_ftp_command(me, path1, "RNFR /%s", OPT_FLUSH);
1595 return send_ftp_command(me, path2, "RNTO /%s", OPT_FLUSH);
1598 static int ftpfs_mkdir (vfs *me, char *path, mode_t mode)
1600 return send_ftp_command(me, path, "MKD /%s", OPT_FLUSH);
1603 static int ftpfs_rmdir (vfs *me, char *path)
1605 return send_ftp_command(me, path, "RMD /%s", OPT_FLUSH);
1608 static int ftpfs_fh_open (vfs *me, vfs_s_fh *fh, int flags, int mode)
1610 fh->u.ftp.append = 0;
1611 /* File will be written only, so no need to retrieve it from ftp server */
1612 if (((flags & O_WRONLY) == O_WRONLY) && !(flags & (O_RDONLY|O_RDWR))){
1613 #ifdef HAVE_STRUCT_LINGER
1614 struct linger li;
1615 #else
1616 int li = 1;
1617 #endif
1618 char * name;
1620 /* linear_start() called, so data will be written
1621 * to local temporary file and stored to ftp server
1622 * by vfs_s_close later
1624 if (FH_SUPER->u.ftp.control_connection_buzy){
1625 if (!fh->ino->localname){
1626 int handle = mc_mkstemps (&fh->ino->localname, me->name, NULL);
1627 if (handle == -1)
1628 return -1;
1629 close (handle);
1630 fh->u.ftp.append = flags & O_APPEND;
1632 return 0;
1634 name = vfs_s_fullpath (me, fh->ino);
1635 if (!name)
1636 return -1;
1637 fh->handle = open_data_connection(me, fh->ino->super,
1638 (flags & O_APPEND) ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1639 g_free (name);
1641 if (fh->handle < 0)
1642 return -1;
1643 #ifdef HAVE_STRUCT_LINGER
1644 li.l_onoff = 1;
1645 li.l_linger = 120;
1646 #endif
1647 setsockopt(fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
1649 if (fh->ino->localname){
1650 unlink (fh->ino->localname);
1651 g_free (fh->ino->localname);
1652 fh->ino->localname = NULL;
1654 return 0;
1657 if (!fh->ino->localname)
1658 if (vfs_s_retrieve_file (me, fh->ino)==-1)
1659 return -1;
1660 if (!fh->ino->localname)
1661 vfs_die( "retrieve_file failed to fill in localname" );
1662 return 0;
1665 static int ftpfs_fh_close (vfs *me, vfs_s_fh *fh)
1667 if (fh->handle != -1 && !fh->ino->localname){
1668 close (fh->handle);
1669 fh->handle = -1;
1670 /* File is stored to destination already, so
1671 * we prevent MEDATA->file_store() call from vfs_s_close ()
1673 fh->changed = 0;
1674 if (get_reply (me, fh->ino->SUP.sock, NULL, 0) != COMPLETE)
1675 ERRNOR (EIO, -1);
1676 vfs_s_invalidate (me, FH_SUPER);
1678 return 0;
1681 static struct vfs_s_data ftp_data = {
1682 NULL,
1685 NULL, /* logfile */
1687 NULL, /* init_inode */
1688 NULL, /* free_inode */
1689 NULL, /* init_entry */
1691 NULL, /* archive_check */
1692 archive_same,
1693 open_archive,
1694 free_archive,
1696 ftpfs_fh_open, /* fh_open */
1697 ftpfs_fh_close, /* fh_close */
1699 vfs_s_find_entry_linear,
1700 dir_load,
1701 dir_uptodate,
1702 file_store,
1704 linear_start,
1705 linear_read,
1706 linear_close
1709 static void
1710 ftpfs_fill_names (vfs *me, void (*func)(char *))
1712 struct vfs_s_super * super = ftp_data.supers;
1713 char *name;
1715 while (super){
1716 name = g_strconcat ("/#ftp:", SUP.user, "@", SUP.host, "/", SUP.cwdir, NULL);
1717 (*func)(name);
1718 g_free (name);
1719 super = super->next;
1723 vfs vfs_ftpfs_ops = {
1724 NULL, /* This is place of next pointer */
1725 "ftpfs",
1726 F_NET, /* flags */
1727 "ftp:", /* prefix */
1728 &ftp_data, /* data */
1729 0, /* errno */
1730 NULL, /* init */
1731 NULL, /* done */
1732 ftpfs_fill_names,
1733 NULL,
1735 vfs_s_open,
1736 vfs_s_close,
1737 vfs_s_read,
1738 vfs_s_write,
1740 vfs_s_opendir,
1741 vfs_s_readdir,
1742 vfs_s_closedir,
1743 vfs_s_telldir,
1744 vfs_s_seekdir,
1746 vfs_s_stat,
1747 vfs_s_lstat,
1748 vfs_s_fstat,
1750 ftpfs_chmod,
1751 ftpfs_chown, /* not really implemented but returns success */
1752 NULL,
1754 vfs_s_readlink,
1755 NULL,
1756 NULL,
1757 ftpfs_unlink,
1759 ftpfs_rename,
1760 vfs_s_chdir,
1761 vfs_s_ferrno,
1762 vfs_s_lseek,
1763 NULL,
1765 vfs_s_getid,
1766 vfs_s_nothingisopen,
1767 vfs_s_free,
1769 NULL,
1770 NULL,
1772 ftpfs_mkdir,
1773 ftpfs_rmdir,
1774 ftpfs_ctl,
1775 vfs_s_setctl
1777 MMAPNULL
1780 void ftpfs_set_debug (const char *file)
1782 logfile = fopen (file, "w+");
1783 if (logfile)
1784 ftp_data.logfile = logfile;
1787 static char buffer[BUF_MEDIUM];
1788 static char *netrc, *netrcp;
1790 /* This should match the keywords[] array below */
1791 typedef enum {
1792 NETRC_NONE = 0,
1793 NETRC_DEFAULT,
1794 NETRC_MACHINE,
1795 NETRC_LOGIN,
1796 NETRC_PASSWORD,
1797 NETRC_PASSWD,
1798 NETRC_ACCOUNT,
1799 NETRC_MACDEF,
1800 NETRC_UNKNOWN
1801 } keyword_t;
1803 static keyword_t netrc_next (void)
1805 char *p;
1806 keyword_t i;
1807 static const char *const keywords[] = { "default", "machine",
1808 "login", "password", "passwd", "account", "macdef", NULL
1812 while (1) {
1813 netrcp = skip_separators (netrcp);
1814 if (*netrcp != '\n')
1815 break;
1816 netrcp++;
1818 if (!*netrcp)
1819 return NETRC_NONE;
1820 p = buffer;
1821 if (*netrcp == '"') {
1822 for (netrcp++; *netrcp != '"' && *netrcp; netrcp++) {
1823 if (*netrcp == '\\')
1824 netrcp++;
1825 *p++ = *netrcp;
1827 } else {
1828 for (; *netrcp != '\n' && *netrcp != '\t' && *netrcp != ' ' &&
1829 *netrcp != ',' && *netrcp; netrcp++) {
1830 if (*netrcp == '\\')
1831 netrcp++;
1832 *p++ = *netrcp;
1835 *p = 0;
1836 if (!*buffer)
1837 return 0;
1839 i = NETRC_DEFAULT;
1840 while (keywords[i - 1]) {
1841 if (!strcmp (keywords[i - 1], buffer))
1842 return i;
1844 i++;
1847 return NETRC_UNKNOWN;
1850 static int netrc_has_incorrect_mode (char *netrcname, char *netrc)
1852 static int be_angry = 1;
1853 struct stat mystat;
1855 if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077)) {
1856 if (be_angry) {
1857 message_1s (1, MSG_ERROR,
1858 _("~/.netrc file has not correct mode.\n"
1859 "Remove password or correct mode."));
1860 be_angry = 0;
1862 return 1;
1864 return 0;
1867 /* Scan .netrc until we find matching "machine" or "default"
1868 * domain is used for additional matching
1869 * No search is done after "default" in compliance with "man netrc"
1870 * Return 0 if found, -1 otherwise */
1871 static int find_machine (const char *host, const char *domain)
1873 keyword_t keyword;
1875 while ((keyword = netrc_next ()) != NETRC_NONE) {
1876 if (keyword == NETRC_DEFAULT)
1877 return 0;
1879 if (keyword == NETRC_MACDEF) {
1880 /* Scan for an empty line, which concludes "macdef" */
1881 do {
1882 while (*netrcp && *netrcp != '\n')
1883 netrcp++;
1884 if (*netrcp != '\n')
1885 break;
1886 netrcp++;
1887 } while (*netrcp && *netrcp != '\n');
1888 continue;
1891 if (keyword != NETRC_MACHINE)
1892 continue;
1894 /* Take machine name */
1895 if (netrc_next () == NETRC_NONE)
1896 break;
1898 if (g_strcasecmp (host, buffer)) {
1899 /* Try adding our domain to short names in .netrc */
1900 char *host_domain = strchr (host, '.');
1901 if (!host_domain)
1902 continue;
1904 /* Compare domain part */
1905 if (g_strcasecmp (host_domain, domain))
1906 continue;
1908 /* Compare local part */
1909 if (g_strncasecmp (host, buffer, host_domain - host))
1910 continue;
1913 return 0;
1916 /* end of .netrc */
1917 return -1;
1920 /* Extract login and password from .netrc for the host.
1921 * pass may be NULL.
1922 * Returns 0 for success, -1 for error */
1923 static int lookup_netrc (const char *host, char **login, char **pass)
1925 char *netrcname;
1926 char *tmp_pass = NULL;
1927 char hostname[MAXHOSTNAMELEN], *domain;
1928 keyword_t keyword;
1929 static struct rupcache {
1930 struct rupcache *next;
1931 char *host;
1932 char *login;
1933 char *pass;
1934 } *rup_cache = NULL, *rupp;
1936 /* Initialize *login and *pass */
1937 if (!login)
1938 return 0;
1939 *login = NULL;
1940 if (pass)
1941 *pass = NULL;
1943 /* Look up in the cache first */
1944 for (rupp = rup_cache; rupp != NULL; rupp = rupp->next) {
1945 if (!strcmp (host, rupp->host)) {
1946 if (rupp->login)
1947 *login = g_strdup (rupp->login);
1948 if (pass && rupp->pass)
1949 *pass = g_strdup (rupp->pass);
1950 return 0;
1954 /* Load current .netrc */
1955 netrcname = concat_dir_and_file (home_dir, ".netrc");
1956 netrcp = netrc = load_file (netrcname);
1957 if (netrc == NULL) {
1958 g_free (netrcname);
1959 return 0;
1962 /* Find our own domain name */
1963 if (gethostname (hostname, sizeof (hostname)) < 0)
1964 *hostname = 0;
1965 if (!(domain = strchr (hostname, '.')))
1966 domain = "";
1968 /* Scan for "default" and matching "machine" keywords */
1969 find_machine (host, domain);
1971 /* Scan for keywords following "default" and "machine" */
1972 while (1) {
1973 int need_break = 0;
1974 keyword = netrc_next ();
1976 switch (keyword) {
1977 case NETRC_LOGIN:
1978 if (netrc_next () == NETRC_NONE) {
1979 need_break = 1;
1980 break;
1983 /* We have another name already - should not happen */
1984 if (*login) {
1985 need_break = 1;
1986 break;
1989 /* We have login name now */
1990 *login = g_strdup (buffer);
1991 break;
1993 case NETRC_PASSWORD:
1994 case NETRC_PASSWD:
1995 if (netrc_next () == NETRC_NONE) {
1996 need_break = 1;
1997 break;
2000 /* Ignore unsafe passwords */
2001 if (strcmp (*login, "anonymous") && strcmp (*login, "ftp")
2002 && netrc_has_incorrect_mode (netrcname, netrc)) {
2003 need_break = 1;
2004 break;
2007 /* Remember password. pass may be NULL, so use tmp_pass */
2008 if (tmp_pass == NULL)
2009 tmp_pass = g_strdup (buffer);
2010 break;
2012 case NETRC_ACCOUNT:
2013 /* "account" is followed by a token which we ignore */
2014 if (netrc_next () == NETRC_NONE) {
2015 need_break = 1;
2016 break;
2019 /* Ignore account, but warn user anyways */
2020 netrc_has_incorrect_mode (netrcname, netrc);
2021 break;
2023 default:
2024 /* Unexpected keyword or end of file */
2025 need_break = 1;
2026 break;
2029 if (need_break)
2030 break;
2033 g_free (netrc);
2034 g_free (netrcname);
2036 rupp = g_new (struct rupcache, 1);
2037 rupp->host = g_strdup (host);
2038 rupp->login = rupp->pass = 0;
2040 if (*login != NULL) {
2041 rupp->login = g_strdup (*login);
2043 if (tmp_pass != NULL)
2044 rupp->pass = g_strdup (tmp_pass);
2045 rupp->next = rup_cache;
2046 rup_cache = rupp;
2048 if (pass)
2049 *pass = tmp_pass;
2051 return 0;