* global.h: Move fcntl.h inclusion here. Define O_BINARY.
[midnight-commander.git] / vfs / ftpfs.c
blob6721ad875a07588547b7b778d59ffc481ec0fd92
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 $Id$
12 This program is free software; you can redistribute it and/or
13 modify it under the terms of the GNU Library General Public License
14 as published by the Free Software Foundation; either version 2 of
15 the License, or (at your option) any later version.
17 This program is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU Library General Public License for more details.
22 You should have received a copy of the GNU Library General Public
23 License along with this program; if not, write to the Free Software
24 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
26 /* FTPfs TODO:
28 - make it more robust - all the connects etc. should handle EADDRINUSE and
29 ERETRY (have I spelled these names correctly?)
30 - make the user able to flush a connection - all the caches will get empty
31 etc., (tarfs as well), we should give there a user selectable timeout
32 and assign a key sequence.
33 - use hash table instead of linklist to cache ftpfs directory.
35 What to do with this?
38 * NOTE: Usage of tildes is deprecated, consider:
39 * cd /#ftp:pavel@hobit
40 * cd ~
41 * And now: what do I want to do? Do I want to go to /home/pavel or to
42 * /#ftp:hobit/home/pavel? I think first has better sense...
45 int f = !strcmp( remote_path, "/~" );
46 if (f || !strncmp( remote_path, "/~/", 3 )) {
47 char *s;
48 s = concat_dir_and_file( qhome (*bucket), remote_path +3-f );
49 g_free (remote_path);
50 remote_path = s;
57 /* Namespace pollution: horrible */
59 #include <config.h>
60 #include <sys/types.h> /* POSIX-required by sys/socket.h and netdb.h */
61 #include <netdb.h> /* struct hostent */
62 #include <sys/socket.h> /* AF_INET */
63 #include <netinet/in.h> /* struct in_addr */
64 #ifdef HAVE_SETSOCKOPT
65 # include <netinet/ip.h> /* IP options */
66 #endif
67 #ifdef HAVE_ARPA_INET_H
68 #include <arpa/inet.h>
69 #endif
70 #include <arpa/ftp.h>
71 #include <arpa/telnet.h>
72 #include <sys/param.h>
74 #include "utilvfs.h"
76 #include "xdirentry.h"
77 #include "vfs.h"
78 #include "tcputil.h"
79 #include "../src/dialog.h"
80 #include "../src/setup.h" /* for load_anon_passwd */
81 #include "container.h"
82 #include "ftpfs.h"
83 #ifndef MAXHOSTNAMELEN
84 # define MAXHOSTNAMELEN 64
85 #endif
87 #define UPLOAD_ZERO_LENGTH_FILE
88 #define SUP super->u.ftp
89 #define FH_SOCK fh->u.ftp.sock
91 static int my_errno;
92 static int code;
94 /* Delay to retry a connection */
95 int ftpfs_retry_seconds = 30;
97 /* Method to use to connect to ftp sites */
98 int ftpfs_use_passive_connections = 1;
100 /* Method used to get directory listings:
101 * 1: try 'LIST -la <path>', if it fails
102 * fall back to CWD <path>; LIST
103 * 0: always use CWD <path>; LIST
105 int ftpfs_use_unix_list_options = 1;
107 /* First "CWD <path>", then "LIST -la ." */
108 int ftpfs_first_cd_then_ls;
110 /* Use the ~/.netrc */
111 int use_netrc = 1;
113 extern char *home_dir;
115 /* Anonymous setup */
116 char *ftpfs_anonymous_passwd = NULL;
117 int ftpfs_directory_timeout = 900;
119 /* Proxy host */
120 char *ftpfs_proxy_host = NULL;
122 /* wether we have to use proxy by default? */
123 int ftpfs_always_use_proxy;
125 /* source routing host */
126 extern int source_route;
128 /* Where we store the transactions */
129 static FILE *logfile = NULL;
131 /* If true, the directory cache is forced to reload */
132 static int force_expiration = 0;
134 #ifdef FIXME_LATER_ALIGATOR
135 static struct linklist *connections_list;
136 #endif
138 /* command wait_flag: */
139 #define NONE 0x00
140 #define WAIT_REPLY 0x01
141 #define WANT_STRING 0x02
142 static char reply_str [80];
144 /* char *translate_path (struct ftpfs_connection *bucket, char *remote_path)
145 Translate a Unix path, i.e. MC's internal path representation (e.g.
146 /somedir/somefile) to a path valid for the remote server. Every path
147 transfered to the remote server has to be mangled by this function
148 right prior to sending it.
149 Currently only Amiga ftp servers are handled in a special manner.
151 When the remote server is an amiga:
152 a) strip leading slash if necesarry
153 b) replace first occurance of ":/" with ":"
154 c) strip trailing "/."
157 static char *ftpfs_get_current_directory (vfs *me, vfs_s_super *super);
158 static int ftpfs_chdir_internal (vfs *me, vfs_s_super *super, char *remote_path);
159 static int command (vfs *me, vfs_s_super *super, int wait_reply, char *fmt, ...)
160 __attribute__ ((format (printf, 4, 5)));
161 static int ftpfs_open_socket (vfs *me, vfs_s_super *super);
162 static int login_server (vfs *me, vfs_s_super *super, const char *netrcpass);
163 static int lookup_netrc (const char *host, char **login, char **pass);
165 static char *
166 translate_path (vfs *me, vfs_s_super *super, const char *remote_path)
168 if (!SUP.remote_is_amiga)
169 return g_strdup (remote_path);
170 else {
171 char *ret, *p;
173 if (logfile) {
174 fprintf (logfile, "MC -- translate_path: %s\n", remote_path);
175 fflush (logfile);
178 /* strip leading slash(es) */
179 while (*remote_path == '/')
180 remote_path++;
183 * Don't change "/" into "", e.g. "CWD " would be
184 * invalid.
186 if (*remote_path == '\0')
187 return g_strdup (".");
189 ret = g_strdup (remote_path);
191 /* replace first occurance of ":/" with ":" */
192 if ((p = strchr (ret, ':')) && *(p + 1) == '/')
193 strcpy (p + 1, p + 2);
195 /* strip trailing "/." */
196 if ((p = strrchr (ret, '/')) && *(p + 1) == '.' && *(p + 2) == '\0')
197 *p = '\0';
198 return ret;
202 /* Extract the hostname and username from the path */
205 * path is in the form: [user@]hostname:port/remote-dir, e.g.:
206 * ftp://sunsite.unc.edu/pub/linux
207 * ftp://miguel@sphinx.nuclecu.unam.mx/c/nc
208 * ftp://tsx-11.mit.edu:8192/
209 * ftp://joe@foo.edu:11321/private
210 * If the user is empty, e.g. ftp://@roxanne/private, then your login name
211 * is supplied.
215 #define FTP_COMMAND_PORT 21
217 static void
218 ftp_split_url(char *path, char **host, char **user, int *port, char **pass)
220 char *p;
222 p = vfs_split_url (path, host, user, port, pass, FTP_COMMAND_PORT,
223 URL_ALLOW_ANON);
225 if (!*user) {
226 /* Look up user and password in netrc */
227 if (use_netrc)
228 lookup_netrc (*host, user, pass);
229 if (!*user)
230 *user = g_strdup ("anonymous");
233 /* Look up password in netrc for known user */
234 if (use_netrc && *user && pass && !*pass) {
235 char *new_user;
237 lookup_netrc (*host, &new_user, pass);
239 /* If user is different, remove password */
240 if (new_user && strcmp (*user, new_user)) {
241 g_free (*pass);
242 *pass = NULL;
245 g_free (new_user);
248 if (p)
249 g_free (p);
252 /* Returns a reply code, check /usr/include/arpa/ftp.h for possible values */
253 static int
254 get_reply (vfs *me, int sock, char *string_buf, int string_len)
256 char answer[BUF_1K];
257 int i;
259 for (;;) {
260 if (!vfs_s_get_line (me, sock, answer, sizeof (answer), '\n')){
261 if (string_buf)
262 *string_buf = 0;
263 code = 421;
264 return 4;
266 switch (sscanf(answer, "%d", &code)){
267 case 0:
268 if (string_buf) {
269 strncpy (string_buf, answer, string_len - 1);
270 *(string_buf + string_len - 1) = 0;
272 code = 500;
273 return 5;
274 case 1:
275 if (answer[3] == '-') {
276 while (1) {
277 if (!vfs_s_get_line (me, sock, answer, sizeof(answer), '\n')){
278 if (string_buf)
279 *string_buf = 0;
280 code = 421;
281 return 4;
283 if ((sscanf (answer, "%d", &i) > 0) &&
284 (code == i) && (answer[3] == ' '))
285 break;
288 if (string_buf){
289 strncpy (string_buf, answer, string_len - 1);
290 *(string_buf + string_len - 1) = 0;
292 return code / 100;
297 static int
298 reconnect (vfs *me, vfs_s_super *super)
300 int sock = ftpfs_open_socket (me, super);
301 if (sock != -1){
302 char *cwdir = SUP.cwdir;
303 close (SUP.sock);
304 SUP.sock = sock;
305 SUP.cwdir = NULL;
306 if (login_server (me, super, SUP.password)){
307 if (!cwdir)
308 return 1;
309 sock = ftpfs_chdir_internal (me, super, cwdir);
310 g_free (cwdir);
311 return sock == COMPLETE;
313 SUP.cwdir = cwdir;
315 return 0;
318 static int
319 command (vfs *me, vfs_s_super *super, int wait_reply, char *fmt, ...)
321 va_list ap;
322 char *str, *fmt_str;
323 int status;
324 int sock = SUP.sock;
326 va_start (ap, fmt);
327 fmt_str = g_strdup_vprintf (fmt, ap);
328 va_end (ap);
330 status = strlen (fmt_str);
331 str = g_realloc (fmt_str, status + 3);
332 strcpy (str + status, "\r\n");
334 if (logfile){
335 if (strncmp (str, "PASS ", 5) == 0){
336 fputs ("PASS <Password not logged>\r\n", logfile);
337 } else
338 fwrite (str, status + 2, 1, logfile);
340 fflush (logfile);
343 got_sigpipe = 0;
344 enable_interrupt_key ();
345 status = write (SUP.sock, str, status + 2);
347 if (status < 0){
348 code = 421;
350 if (errno == EPIPE){ /* Remote server has closed connection */
351 static int level = 0; /* login_server() use command() */
352 if (level == 0){
353 level = 1;
354 status = reconnect (me, super);
355 level = 0;
356 if (status && write (SUP.sock, str, status + 2) > 0)
357 goto ok;
359 got_sigpipe = 1;
361 g_free (str);
362 disable_interrupt_key ();
363 return TRANSIENT;
366 g_free (str);
367 disable_interrupt_key ();
369 if (wait_reply)
370 return get_reply (me, sock, (wait_reply & WANT_STRING) ? reply_str : NULL, sizeof (reply_str)-1);
371 return COMPLETE;
374 static void
375 free_archive (vfs *me, vfs_s_super *super)
377 if (SUP.sock != -1){
378 print_vfs_message (_("ftpfs: Disconnecting from %s"), SUP.host);
379 command(me, super, NONE, "QUIT");
380 close(SUP.sock);
382 g_free (SUP.host);
383 g_free (SUP.user);
384 g_free (SUP.cwdir);
385 g_free (SUP.password);
388 /* some defines only used by changetype */
389 /* These two are valid values for the second parameter */
390 #define TYPE_ASCII 0
391 #define TYPE_BINARY 1
393 /* This one is only used to initialize bucket->isbinary, don't use it as
394 second parameter to changetype. */
395 #define TYPE_UNKNOWN -1
397 static int
398 changetype (vfs *me, vfs_s_super *super, int binary)
400 if (binary != SUP.isbinary) {
401 if (command (me, super, WAIT_REPLY, "TYPE %c", binary ? 'I' : 'A') != COMPLETE)
402 ERRNOR (EIO, -1);
403 SUP.isbinary = binary;
405 return binary;
408 /* This routine logs the user in */
409 static int
410 login_server (vfs *me, vfs_s_super *super, const char *netrcpass)
412 char *pass;
413 char *op;
414 char *name; /* login user name */
415 int anon = 0;
416 char reply_string[BUF_MEDIUM];
418 SUP.isbinary = TYPE_UNKNOWN;
419 if (netrcpass)
420 op = g_strdup (netrcpass);
421 else {
422 if (!strcmp (SUP.user, "anonymous") ||
423 !strcmp (SUP.user, "ftp")) {
424 if (!ftpfs_anonymous_passwd)
425 ftpfs_init_passwd();
426 op = g_strdup (ftpfs_anonymous_passwd);
427 anon = 1;
428 } else {
429 char *p;
431 if (!SUP.password){
432 p = g_strconcat (_(" FTP: Password required for "), SUP.user,
433 " ", NULL);
434 op = vfs_get_password (p);
435 g_free (p);
436 if (op == NULL)
437 ERRNOR (EPERM, 0);
438 SUP.password = g_strdup (op);
439 } else
440 op = g_strdup (SUP.password);
444 if (!anon || logfile)
445 pass = op;
446 else {
447 pass = g_strconcat ("-", op, NULL);
448 wipe_password (op);
451 /* Proxy server accepts: username@host-we-want-to-connect*/
452 if (SUP.proxy){
453 name = g_strconcat (SUP.user, "@",
454 SUP.host[0] == '!' ? SUP.host+1 : SUP.host, NULL);
455 } else
456 name = g_strdup (SUP.user);
458 if (get_reply (me, SUP.sock, reply_string, sizeof (reply_string) - 1) == COMPLETE) {
459 g_strup (reply_string);
460 SUP.remote_is_amiga = strstr (reply_string, "AMIGA") != 0;
461 if (logfile) {
462 fprintf (logfile, "MC -- remote_is_amiga = %d\n", SUP.remote_is_amiga);
463 fflush (logfile);
466 print_vfs_message (_("ftpfs: sending login name"));
467 code = command (me, super, WAIT_REPLY, "USER %s", name);
469 switch (code){
470 case CONTINUE:
471 print_vfs_message (_("ftpfs: sending user password"));
472 if (command (me, super, WAIT_REPLY, "PASS %s", pass) != COMPLETE)
473 break;
475 case COMPLETE:
476 print_vfs_message (_("ftpfs: logged in"));
477 wipe_password (pass);
478 g_free (name);
479 return 1;
481 default:
482 SUP.failed_on_login = 1;
483 /* my_errno = E; */
484 if (SUP.password)
485 wipe_password (SUP.password);
486 SUP.password = 0;
488 goto login_fail;
491 message_2s (1, MSG_ERROR, _("ftpfs: Login incorrect for user %s "), SUP.user);
492 login_fail:
493 wipe_password (pass);
494 g_free (name);
495 ERRNOR (EPERM, 0);
498 #ifdef HAVE_SETSOCKOPT
499 static void
500 setup_source_route (int socket, int dest)
502 char buffer [20];
503 char *ptr = buffer;
505 if (!source_route)
506 return;
507 memset (buffer, 0, sizeof (buffer));
508 *ptr++ = IPOPT_LSRR;
509 *ptr++ = 3 + 8;
510 *ptr++ = 4; /* pointer */
512 /* First hop */
513 memcpy (ptr, (char *) &source_route, sizeof (int));
514 ptr += 4;
516 /* Second hop (ie, final destination) */
517 memcpy (ptr, (char *) &dest, sizeof (int));
518 ptr += 4;
519 while ((ptr - buffer) & 3)
520 ptr++;
521 if (setsockopt (socket, IPPROTO_IP, IP_OPTIONS,
522 buffer, ptr - buffer) < 0)
523 message_2s (1, MSG_ERROR, _(" Could not set source routing (%s)"), unix_error_string (errno));
525 #else
526 #define setup_source_route(x,y)
527 #endif
529 static struct no_proxy_entry {
530 char *domain;
531 void *next;
532 } *no_proxy;
534 static void
535 load_no_proxy_list (void)
537 /* FixMe: shouldn't be hardcoded!!! */
538 char s[BUF_LARGE]; /* provide for BUF_LARGE characters */
539 struct no_proxy_entry *np, *current = 0;
540 FILE *npf;
541 int c;
542 char *p;
543 static char *mc_file;
545 if (mc_file)
546 return;
548 mc_file = concat_dir_and_file (mc_home, "mc.no_proxy");
549 if (exist_file (mc_file) &&
550 (npf = fopen (mc_file, "r"))) {
551 while (fgets (s, sizeof(s), npf) || !(feof (npf) || ferror (npf))) {
552 if (!(p = strchr (s, '\n'))) { /* skip bogus entries */
553 while ((c = fgetc (npf)) != EOF && c != '\n')
555 continue;
558 if (p == s)
559 continue;
561 *p = '\0';
563 np = g_new (struct no_proxy_entry, 1);
564 np->domain = g_strdup (s);
565 np->next = NULL;
566 if (no_proxy)
567 current->next = np;
568 else
569 no_proxy = np;
570 current = np;
573 fclose (npf);
575 g_free (mc_file);
578 static int
579 ftpfs_check_proxy (const char *host)
581 struct no_proxy_entry *npe;
583 if (!ftpfs_proxy_host || !*ftpfs_proxy_host || !host || !*host)
584 return 0; /* sanity check */
586 if (*host == '!')
587 return 1;
589 if (!ftpfs_always_use_proxy)
590 return 0;
592 if (!strchr (host, '.'))
593 return 0;
595 load_no_proxy_list ();
596 for (npe = no_proxy; npe; npe=npe->next) {
597 char *domain = npe->domain;
599 if (domain[0] == '.') {
600 int ld = strlen (domain);
601 int lh = strlen (host);
603 while (ld && lh && host[lh - 1] == domain[ld - 1]) {
604 ld--;
605 lh--;
608 if (!ld)
609 return 0;
610 } else
611 if (!g_strcasecmp (host, domain))
612 return 0;
615 return 1;
618 static void
619 ftpfs_get_proxy_host_and_port (char *proxy, char **host, int *port)
621 char *user, *dir;
623 dir =
624 vfs_split_url (proxy, host, &user, port, 0, FTP_COMMAND_PORT,
625 URL_ALLOW_ANON);
626 g_free (user);
627 g_free (dir);
630 static int
631 ftpfs_open_socket (vfs *me, vfs_s_super *super)
633 struct sockaddr_in server_address;
634 struct hostent *hp;
635 int my_socket;
636 char *host;
637 int port = SUP.port;
638 int free_host = 0;
640 /* Use a proxy host? */
641 host = SUP.host;
643 if (!host || !*host){
644 print_vfs_message (_("ftpfs: Invalid host name."));
645 my_errno = EINVAL;
646 return -1;
649 /* Hosts to connect to that start with a ! should use proxy */
650 if (SUP.proxy){
651 ftpfs_get_proxy_host_and_port (ftpfs_proxy_host, &host, &port);
652 free_host = 1;
655 /* Get host address */
656 memset ((char *) &server_address, 0, sizeof (server_address));
657 server_address.sin_family = AF_INET;
658 server_address.sin_addr.s_addr = inet_addr (host);
659 if (server_address.sin_addr.s_addr != -1)
660 server_address.sin_family = AF_INET;
661 else {
662 hp = gethostbyname (host);
663 if (hp == NULL){
664 print_vfs_message (_("ftpfs: Invalid host address."));
665 my_errno = EINVAL;
666 if (free_host)
667 g_free (host);
668 return -1;
670 server_address.sin_family = hp->h_addrtype;
672 /* We copy only 4 bytes, we can not trust hp->h_length, as it comes from the DNS */
673 memcpy ((char *) &server_address.sin_addr, (char *) hp->h_addr, 4);
676 server_address.sin_port = htons (port);
678 /* Connect */
679 if ((my_socket = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
680 my_errno = errno;
681 if (free_host)
682 g_free (host);
683 return -1;
685 setup_source_route (my_socket, server_address.sin_addr.s_addr);
687 print_vfs_message (_("ftpfs: making connection to %s"), host);
688 if (free_host)
689 g_free (host);
691 enable_interrupt_key (); /* clear the interrupt flag */
693 if (connect (my_socket, (struct sockaddr *) &server_address,
694 sizeof (server_address)) < 0){
695 my_errno = errno;
696 if (errno == EINTR && got_interrupt ())
697 print_vfs_message (_("ftpfs: connection interrupted by user"));
698 else
699 print_vfs_message (_("ftpfs: connection to server failed: %s"),
700 unix_error_string(errno));
701 disable_interrupt_key();
702 close (my_socket);
703 return -1;
705 disable_interrupt_key();
706 return my_socket;
709 static int
710 open_archive_int (vfs *me, vfs_s_super *super)
712 int retry_seconds, count_down;
714 /* We do not want to use the passive if we are using proxies */
715 if (SUP.proxy)
716 SUP.use_passive_connection = 0;
718 retry_seconds = 0;
719 do {
720 SUP.failed_on_login = 0;
722 SUP.sock = ftpfs_open_socket (me, super);
723 if (SUP.sock == -1)
724 return -1;
726 if (login_server (me, super, NULL)) {
727 /* Logged in, no need to retry the connection */
728 break;
729 } else {
730 if (SUP.failed_on_login){
731 /* Close only the socket descriptor */
732 close (SUP.sock);
733 } else {
734 return -1;
736 if (ftpfs_retry_seconds){
737 retry_seconds = ftpfs_retry_seconds;
738 enable_interrupt_key ();
739 for (count_down = retry_seconds; count_down; count_down--){
740 print_vfs_message (_("Waiting to retry... %d (Control-C to cancel)"), count_down);
741 sleep (1);
742 if (got_interrupt ()){
743 /* my_errno = E; */
744 disable_interrupt_key ();
745 return 0;
748 disable_interrupt_key ();
751 } while (retry_seconds);
753 SUP.cwdir = ftpfs_get_current_directory (me, super);
754 if (!SUP.cwdir)
755 SUP.cwdir = g_strdup (PATH_SEP_STR);
756 return 0;
759 static int
760 open_archive (vfs *me, vfs_s_super *super, char *archive_name, char *op)
762 char *host, *user, *password;
763 int port;
765 ftp_split_url (strchr (op, ':') + 1, &host, &user, &port, &password);
767 SUP.host = host;
768 SUP.user = user;
769 SUP.port = port;
770 SUP.cwdir = NULL;
771 SUP.proxy = 0;
772 if (ftpfs_check_proxy (host))
773 SUP.proxy = ftpfs_proxy_host;
774 SUP.password = password;
775 SUP.use_passive_connection = ftpfs_use_passive_connections | source_route;
776 SUP.use_source_route = source_route;
777 SUP.strict = ftpfs_use_unix_list_options ? RFC_AUTODETECT : RFC_STRICT;
778 SUP.isbinary = TYPE_UNKNOWN;
779 SUP.remote_is_amiga = 0;
780 super->name = g_strdup("/");
781 super->root = vfs_s_new_inode (me, super, vfs_s_default_stat(me, S_IFDIR | 0755));
783 return open_archive_int (me, super);
786 static int
787 archive_same(vfs *me, vfs_s_super *super, char *archive_name, char *op, void *cookie)
789 char *host, *user;
790 int port;
792 ftp_split_url (strchr(op, ':') + 1, &host, &user, &port, 0);
794 port = ((strcmp (host, SUP.host) == 0) &&
795 (strcmp (user, SUP.user) == 0) &&
796 (port == SUP.port));
798 g_free (host);
799 g_free (user);
801 return port;
804 void
805 ftpfs_flushdir (void)
807 force_expiration = 1;
810 static int
811 dir_uptodate(vfs *me, vfs_s_inode *ino)
813 struct timeval tim;
815 if (force_expiration) {
816 force_expiration = 0;
817 return 0;
819 gettimeofday(&tim, NULL);
820 if (tim.tv_sec < ino->u.ftp.timestamp.tv_sec)
821 return 1;
822 return 0;
825 /* The returned directory should always contain a trailing slash */
826 static char *
827 ftpfs_get_current_directory (vfs *me, vfs_s_super *super)
829 char buf[BUF_8K], *bufp, *bufq;
831 if (command (me, super, NONE, "PWD") == COMPLETE &&
832 get_reply(me, SUP.sock, buf, sizeof(buf)) == COMPLETE) {
833 bufp = NULL;
834 for (bufq = buf; *bufq; bufq++)
835 if (*bufq == '"') {
836 if (!bufp) {
837 bufp = bufq + 1;
838 } else {
839 *bufq = 0;
840 if (*bufp) {
841 if (*(bufq - 1) != '/') {
842 *bufq++ = '/';
843 *bufq = 0;
845 if (*bufp == '/')
846 return g_strdup (bufp);
847 else {
848 /* If the remote server is an Amiga a leading slash
849 might be missing. MC needs it because it is used
850 as seperator between hostname and path internally. */
851 return g_strconcat( "/", bufp, 0);
853 } else {
854 my_errno = EIO;
855 return NULL;
860 my_errno = EIO;
861 return NULL;
865 /* Setup Passive ftp connection, we use it for source routed connections */
866 static int
867 setup_passive (vfs *me, vfs_s_super *super, int my_socket, struct sockaddr_in *sa)
869 int xa, xb, xc, xd, xe, xf;
870 char n [6];
871 char *c = reply_str;
873 if (command (me, super, WAIT_REPLY | WANT_STRING, "PASV") != COMPLETE)
874 return 0;
876 /* Parse remote parameters */
877 for (c = reply_str + 4; (*c) && (!isdigit ((unsigned char) *c)); c++)
879 if (!*c)
880 return 0;
881 if (!isdigit ((unsigned char) *c))
882 return 0;
883 if (sscanf (c, "%d,%d,%d,%d,%d,%d", &xa, &xb, &xc, &xd, &xe, &xf) != 6)
884 return 0;
885 n [0] = (unsigned char) xa;
886 n [1] = (unsigned char) xb;
887 n [2] = (unsigned char) xc;
888 n [3] = (unsigned char) xd;
889 n [4] = (unsigned char) xe;
890 n [5] = (unsigned char) xf;
892 memcpy (&(sa->sin_addr.s_addr), (void *)n, 4);
893 memcpy (&(sa->sin_port), (void *)&n[4], 2);
894 setup_source_route (my_socket, sa->sin_addr.s_addr);
895 if (connect (my_socket, (struct sockaddr *) sa, sizeof (struct sockaddr_in)) < 0)
896 return 0;
897 return 1;
900 static int
901 initconn (vfs *me, vfs_s_super *super)
903 struct sockaddr_in data_addr;
904 int data, len = sizeof(data_addr);
905 struct protoent *pe;
907 getsockname(SUP.sock, (struct sockaddr *) &data_addr, &len);
908 data_addr.sin_port = 0;
910 pe = getprotobyname("tcp");
911 if (pe == NULL)
912 ERRNOR (EIO, -1);
913 data = socket (AF_INET, SOCK_STREAM, pe->p_proto);
914 if (data < 0)
915 ERRNOR (EIO, -1);
917 if (SUP.use_passive_connection){
918 if ((SUP.use_passive_connection = setup_passive (me, super, data, &data_addr)))
919 return data;
921 SUP.use_source_route = 0;
922 SUP.use_passive_connection = 0;
923 print_vfs_message (_("ftpfs: could not setup passive mode"));
926 /* If passive setup fails, fallback to active connections */
927 /* Active FTP connection */
928 if ((bind (data, (struct sockaddr *)&data_addr, len) == 0) &&
929 (getsockname (data, (struct sockaddr *) &data_addr, &len) == 0) &&
930 (listen (data, 1) == 0))
932 unsigned char *a = (unsigned char *)&data_addr.sin_addr;
933 unsigned char *p = (unsigned char *)&data_addr.sin_port;
935 if (command (me, super, WAIT_REPLY, "PORT %d,%d,%d,%d,%d,%d", a[0], a[1],
936 a[2], a[3], p[0], p[1]) == COMPLETE)
937 return data;
939 close(data);
940 my_errno = EIO;
941 return -1;
944 static int
945 open_data_connection (vfs *me, vfs_s_super *super, char *cmd, char *remote,
946 int isbinary, int reget)
948 struct sockaddr_in from;
949 int s, j, data, fromlen = sizeof(from);
951 if ((s = initconn (me, super)) == -1)
952 return -1;
953 if (changetype (me, super, isbinary) == -1)
954 return -1;
955 if (reget > 0){
956 j = command (me, super, WAIT_REPLY, "REST %d", reget);
957 if (j != CONTINUE)
958 return -1;
960 if (remote) {
961 char * remote_path = translate_path (me, super, remote);
962 j = command (me, super, WAIT_REPLY, "%s /%s", cmd,
963 /* WarFtpD can't STORE //filename */
964 (*remote_path == '/') ? remote_path + 1 : remote_path);
965 g_free (remote_path);
966 } else
967 j = command (me, super, WAIT_REPLY, "%s", cmd);
968 if (j != PRELIM)
969 ERRNOR (EPERM, -1);
970 enable_interrupt_key();
971 if (SUP.use_passive_connection)
972 data = s;
973 else {
974 data = accept (s, (struct sockaddr *)&from, &fromlen);
975 close(s);
976 if (data < 0) {
977 my_errno = errno;
978 return -1;
981 disable_interrupt_key();
982 return data;
985 static void
986 linear_abort (vfs *me, vfs_s_fh *fh)
988 vfs_s_super *super = FH_SUPER;
989 static unsigned char const ipbuf[3] = { IAC, IP, IAC };
990 fd_set mask;
991 char buf[1024];
992 int dsock = FH_SOCK;
993 FH_SOCK = -1;
994 SUP.control_connection_buzy = 0;
996 print_vfs_message(_("ftpfs: aborting transfer."));
997 if (send(SUP.sock, ipbuf, sizeof(ipbuf), MSG_OOB) != sizeof(ipbuf)) {
998 print_vfs_message(_("ftpfs: abort error: %s"), 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 while (read(dsock, buf, sizeof(buf)) > 0);
1012 if ((get_reply(me, SUP.sock, NULL, 0) == TRANSIENT) && (code == 426))
1013 get_reply(me, SUP.sock, NULL, 0);
1016 #if 0
1017 static void
1018 resolve_symlink_without_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1020 struct linklist *flist;
1021 struct direntry *fe, *fel;
1022 char tmp[MC_MAXPATHLEN];
1023 int depth;
1025 dir->symlink_status = FTPFS_RESOLVING_SYMLINKS;
1026 for (flist = dir->file_list->next; flist != dir->file_list; flist = flist->next) {
1027 /* flist->data->l_stat is alread initialized with 0 */
1028 fel = flist->data;
1029 if (S_ISLNK(fel->s.st_mode) && fel->linkname) {
1030 if (fel->linkname[0] == '/') {
1031 if (strlen (fel->linkname) >= MC_MAXPATHLEN)
1032 continue;
1033 strcpy (tmp, fel->linkname);
1034 } else {
1035 if ((strlen (dir->remote_path) + strlen (fel->linkname)) >= MC_MAXPATHLEN)
1036 continue;
1037 strcpy (tmp, dir->remote_path);
1038 if (tmp[1] != '\0')
1039 strcat (tmp, "/");
1040 strcat (tmp + 1, fel->linkname);
1042 for ( depth = 0; depth < 100; depth++) { /* depth protects against recursive symbolic links */
1043 canonicalize_pathname (tmp);
1044 fe = _get_file_entry(bucket, tmp, 0, 0);
1045 if (fe) {
1046 if (S_ISLNK (fe->s.st_mode) && fe->l_stat == 0) {
1047 /* Symlink points to link which isn't resolved, yet. */
1048 if (fe->linkname[0] == '/') {
1049 if (strlen (fe->linkname) >= MC_MAXPATHLEN)
1050 break;
1051 strcpy (tmp, fe->linkname);
1052 } else {
1053 /* at this point tmp looks always like this
1054 /directory/filename, i.e. no need to check
1055 strrchr's return value */
1056 *(strrchr (tmp, '/') + 1) = '\0'; /* dirname */
1057 if ((strlen (tmp) + strlen (fe->linkname)) >= MC_MAXPATHLEN)
1058 break;
1059 strcat (tmp, fe->linkname);
1061 continue;
1062 } else {
1063 fel->l_stat = g_new (struct stat, 1);
1064 if ( S_ISLNK (fe->s.st_mode))
1065 *fel->l_stat = *fe->l_stat;
1066 else
1067 *fel->l_stat = fe->s;
1068 (*fel->l_stat).st_ino = bucket->__inode_counter++;
1071 break;
1075 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1078 static void
1079 resolve_symlink_with_ls_options(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1081 char buffer[2048] = "", *filename;
1082 int sock;
1083 FILE *fp;
1084 struct stat s;
1085 struct linklist *flist;
1086 struct direntry *fe;
1087 int switch_method = 0;
1089 dir->symlink_status = FTPFS_RESOLVED_SYMLINKS;
1090 if (strchr (dir->remote_path, ' ')) {
1091 if (ftpfs_chdir_internal (bucket, dir->remote_path) != COMPLETE) {
1092 print_vfs_message(_("ftpfs: CWD failed."));
1093 return;
1095 sock = open_data_connection (bucket, "LIST -lLa", ".", TYPE_ASCII, 0);
1097 else
1098 sock = open_data_connection (bucket, "LIST -lLa",
1099 dir->remote_path, TYPE_ASCII, 0);
1101 if (sock == -1) {
1102 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1103 return;
1106 fp = fdopen(sock, "r");
1107 if (fp == NULL) {
1108 close(sock);
1109 print_vfs_message(_("ftpfs: couldn't resolve symlink"));
1110 return;
1112 enable_interrupt_key();
1113 flist = dir->file_list->next;
1114 while (1) {
1115 do {
1116 if (flist == dir->file_list)
1117 goto done;
1118 fe = flist->data;
1119 flist = flist->next;
1120 } while (!S_ISLNK(fe->s.st_mode));
1121 while (1) {
1122 if (fgets (buffer, sizeof (buffer), fp) == NULL)
1123 goto done;
1124 if (logfile){
1125 fputs (buffer, logfile);
1126 fflush (logfile);
1128 vfs_die("This code should be commented out\n");
1129 if (vfs_parse_ls_lga (buffer, &s, &filename, NULL)) {
1130 int r = strcmp(fe->name, filename);
1131 g_free(filename);
1132 if (r == 0) {
1133 if (S_ISLNK (s.st_mode)) {
1134 /* This server doesn't understand LIST -lLa */
1135 switch_method = 1;
1136 goto done;
1138 fe->l_stat = g_new (struct stat, 1);
1139 if (fe->l_stat == NULL)
1140 goto done;
1141 *fe->l_stat = s;
1142 (*fe->l_stat).st_ino = bucket->__inode_counter++;
1143 break;
1145 if (r < 0)
1146 break;
1150 done:
1151 while (fgets(buffer, sizeof(buffer), fp) != NULL);
1152 disable_interrupt_key();
1153 fclose(fp);
1154 get_reply(me, SUP.sock, NULL, 0);
1157 static void
1158 resolve_symlink(vfs *me, vfs_s_super *super, vfs_s_inode *dir)
1160 print_vfs_message(_("Resolving symlink..."));
1162 if (SUP.strict_rfc959_list_cmd)
1163 resolve_symlink_without_ls_options(me, super, dir);
1164 else
1165 resolve_symlink_with_ls_options(me, super, dir);
1167 #endif
1169 static int
1170 dir_load(vfs *me, vfs_s_inode *dir, char *remote_path)
1172 vfs_s_entry *ent;
1173 vfs_s_super *super = dir->super;
1174 int sock, num_entries = 0;
1175 #ifdef FIXME_LATER
1176 int has_symlinks = 0;
1177 #endif
1178 char buffer[BUF_8K];
1179 int cd_first;
1181 cd_first = ftpfs_first_cd_then_ls || (strchr (remote_path, ' ') != NULL)
1182 || (SUP.strict == RFC_STRICT);
1184 again:
1185 print_vfs_message(_("ftpfs: Reading FTP directory %s... %s%s"), remote_path,
1186 SUP.strict == RFC_STRICT ? _("(strict rfc959)") : "",
1187 cd_first ? _("(chdir first)") : "");
1189 if (cd_first) {
1190 char *p;
1192 p = translate_path (me, super, remote_path);
1194 if (ftpfs_chdir_internal (me, super, p) != COMPLETE) {
1195 g_free (p);
1196 my_errno = ENOENT;
1197 print_vfs_message(_("ftpfs: CWD failed."));
1198 return -1;
1200 g_free (p);
1203 gettimeofday(&dir->u.ftp.timestamp, NULL);
1204 dir->u.ftp.timestamp.tv_sec += ftpfs_directory_timeout;
1206 if (SUP.strict == RFC_STRICT)
1207 sock = open_data_connection (me, super, "LIST", 0, TYPE_ASCII, 0);
1208 else if (cd_first)
1209 /* Dirty hack to avoid autoprepending / to . */
1210 /* Wu-ftpd produces strange output for '/' if 'LIST -la .' used */
1211 sock = open_data_connection (me, super, "LIST -la", 0, TYPE_ASCII, 0);
1212 else {
1213 /* Trailing "/." is necessary if remote_path is a symlink */
1214 char *path = concat_dir_and_file (remote_path, ".");
1215 sock = open_data_connection (me, super, "LIST -la", path, TYPE_ASCII, 0);
1216 g_free (path);
1219 if (sock == -1)
1220 goto fallback;
1222 /* Clear the interrupt flag */
1223 enable_interrupt_key ();
1225 #if 1
1227 /* added 20001006 by gisburn
1228 * add dots '.' and '..'. This must be _executed_ before scanning the dir as the
1229 * code below may jump directly into error handling code (without executing
1230 * remaining code). And C doesn't have try {...} finally {}; :-)
1232 vfs_s_inode *parent = dir->ent->dir;
1234 if( parent==NULL )
1235 parent = dir;
1237 ent = vfs_s_generate_entry(me, ".", dir, 0);
1238 ent->ino->st=dir->st;
1239 num_entries++;
1240 vfs_s_insert_entry(me, dir, ent);
1242 ent = vfs_s_generate_entry(me, "..", parent, 0);
1243 ent->ino->st=parent->st;
1244 num_entries++;
1245 vfs_s_insert_entry(me, dir, ent);
1247 #endif
1249 while (1) {
1250 int i;
1251 int res = vfs_s_get_line_interruptible (me, buffer, sizeof (buffer), sock);
1252 if (!res)
1253 break;
1255 if (res == EINTR) {
1256 me->verrno = ECONNRESET;
1257 close (sock);
1258 disable_interrupt_key();
1259 get_reply(me, SUP.sock, NULL, 0);
1260 print_vfs_message (_("%s: failure"), me->name);
1261 return -1;
1264 if (logfile){
1265 fputs (buffer, logfile);
1266 fputs ("\n", logfile);
1267 fflush (logfile);
1270 ent = vfs_s_generate_entry(me, NULL, dir, 0);
1271 i = ent->ino->st.st_nlink;
1272 if (!vfs_parse_ls_lga (buffer, &ent->ino->st, &ent->name, &ent->ino->linkname)) {
1273 vfs_s_free_entry (me, ent);
1274 continue;
1276 ent->ino->st.st_nlink = i; /* Ouch, we need to preserve our counts :-( */
1277 num_entries++;
1278 if ((!strcmp(ent->name, ".")) || (!strcmp (ent->name, ".."))) {
1279 g_free (ent->name);
1280 ent->name = NULL; /* Ouch, vfs_s_free_entry "knows" about . and .. being special :-( */
1281 vfs_s_free_entry (me, ent);
1282 continue;
1285 vfs_s_insert_entry(me, dir, ent);
1288 /* vfs_s_add_dots(me, dir, NULL);
1289 FIXME This really should be here; but we need to provide correct parent.
1290 Disabled for now, please fix it. Pavel
1292 close(sock);
1293 me->verrno = E_REMOTE;
1294 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE) || !num_entries)
1295 goto fallback;
1297 if (SUP.strict == RFC_AUTODETECT)
1298 SUP.strict = RFC_DARING;
1300 #ifdef FIXME_LATER
1301 if (has_symlinks) {
1302 if (resolve_symlinks)
1303 resolve_symlink(me, super, dcache);
1304 else
1305 dcache->symlink_status = FTPFS_UNRESOLVED_SYMLINKS;
1307 #endif
1308 print_vfs_message (_("%s: done."), me->name);
1309 return 0;
1311 fallback:
1312 if (SUP.strict == RFC_AUTODETECT) {
1313 /* It's our first attempt to get a directory listing from this
1314 server (UNIX style LIST command) */
1315 SUP.strict = RFC_STRICT;
1316 /* I hate goto, but recursive call needs another 8K on stack */
1317 /* return dir_load (me, dir, remote_path); */
1318 cd_first = 1;
1319 goto again;
1321 print_vfs_message(_("ftpfs: failed; nowhere to fallback to"));
1322 ERRNOR(-1, EACCES);
1325 static int
1326 file_store(vfs *me, vfs_s_fh *fh, char *name, char *localname)
1328 int h, sock, n;
1329 off_t total;
1330 #ifdef HAVE_STRUCT_LINGER
1331 struct linger li;
1332 #else
1333 int flag_one = 1;
1334 #endif
1335 char buffer[8192];
1336 struct stat s;
1337 vfs_s_super *super = FH_SUPER;
1339 h = open(localname, O_RDONLY);
1340 if (h == -1)
1341 ERRNOR (EIO, -1);
1342 fstat(h, &s);
1343 sock = open_data_connection(me, super, fh->u.ftp.append ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1344 if (sock < 0) {
1345 close(h);
1346 return -1;
1348 #ifdef HAVE_STRUCT_LINGER
1349 li.l_onoff = 1;
1350 li.l_linger = 120;
1351 setsockopt(sock, SOL_SOCKET, SO_LINGER, (char *) &li, sizeof(li));
1352 #else
1353 setsockopt(sock, SOL_SOCKET, SO_LINGER, &flag_one, sizeof (flag_one));
1354 #endif
1355 total = 0;
1357 enable_interrupt_key();
1358 while (1) {
1359 while ((n = read(h, buffer, sizeof(buffer))) < 0) {
1360 if (errno == EINTR) {
1361 if (got_interrupt()) {
1362 my_errno = EINTR;
1363 goto error_return;
1365 else
1366 continue;
1368 my_errno = errno;
1369 goto error_return;
1371 if (n == 0)
1372 break;
1373 while (write(sock, buffer, n) < 0) {
1374 if (errno == EINTR) {
1375 if (got_interrupt()) {
1376 my_errno = EINTR;
1377 goto error_return;
1379 else
1380 continue;
1382 my_errno = errno;
1383 goto error_return;
1385 total += n;
1386 print_vfs_message(_("ftpfs: storing file %lu (%lu)"),
1387 (unsigned long) total, (unsigned long) s.st_size);
1389 disable_interrupt_key();
1390 close(sock);
1391 close(h);
1392 if (get_reply (me, SUP.sock, NULL, 0) != COMPLETE)
1393 ERRNOR (EIO, -1);
1394 return 0;
1395 error_return:
1396 disable_interrupt_key();
1397 close(sock);
1398 close(h);
1399 get_reply(me, SUP.sock, NULL, 0);
1400 return -1;
1403 //#define FH_SOCK fh->u.ftp.sock
1405 static int
1406 linear_start(vfs *me, vfs_s_fh *fh, int offset)
1408 char *name = vfs_s_fullpath (me, fh->ino);
1410 if (!name)
1411 return 0;
1412 FH_SOCK = open_data_connection(me, FH_SUPER, "RETR", name, TYPE_BINARY, offset);
1413 g_free (name);
1414 if (FH_SOCK == -1)
1415 ERRNOR (EACCES, 0);
1416 fh->linear = LS_LINEAR_OPEN;
1417 FH_SUPER->u.ftp.control_connection_buzy = 1;
1418 fh->u.ftp.append = 0;
1419 return 1;
1422 static int
1423 linear_read (vfs *me, vfs_s_fh *fh, void *buf, int len)
1425 int n;
1426 vfs_s_super *super = FH_SUPER;
1428 while ((n = read (FH_SOCK, buf, len))<0) {
1429 if ((errno == EINTR) && !got_interrupt())
1430 continue;
1431 break;
1434 if (n<0)
1435 linear_abort(me, fh);
1437 if (!n) {
1438 SUP.control_connection_buzy = 0;
1439 close (FH_SOCK);
1440 FH_SOCK = -1;
1441 if ((get_reply (me, SUP.sock, NULL, 0) != COMPLETE))
1442 ERRNOR (E_REMOTE, -1);
1443 return 0;
1445 ERRNOR (errno, n);
1448 static void
1449 linear_close (vfs *me, vfs_s_fh *fh)
1451 if (FH_SOCK != -1)
1452 linear_abort(me, fh);
1455 static int ftpfs_ctl (void *fh, int ctlop, int arg)
1457 switch (ctlop) {
1458 case MCCTL_IS_NOTREADY:
1460 int v;
1462 if (!FH->linear)
1463 vfs_die ("You may not do this");
1464 if (FH->linear == LS_LINEAR_CLOSED)
1465 return 0;
1467 v = vfs_s_select_on_two (FH->u.ftp.sock, 0);
1468 if (((v < 0) && (errno == EINTR)) || v == 0)
1469 return 1;
1470 return 0;
1472 default:
1473 return 0;
1477 /* Warning: filename passed to this command is damaged */
1478 static int
1479 send_ftp_command(vfs *me, char *filename, char *cmd, int flags)
1481 char *rpath, *p;
1482 vfs_s_super *super;
1483 int r;
1484 int flush_directory_cache = (flags & OPT_FLUSH);
1486 if (!(rpath = vfs_s_get_path_mangle(me, filename, &super, 0)))
1487 return -1;
1488 p = translate_path (me, super, rpath);
1489 r = command (me, super, WAIT_REPLY, cmd, p);
1490 g_free (p);
1491 vfs_add_noncurrent_stamps (&vfs_ftpfs_ops, (vfsid) super, NULL);
1492 if (flags & OPT_IGNORE_ERROR)
1493 r = COMPLETE;
1494 if (r != COMPLETE)
1495 ERRNOR (EPERM, -1);
1496 if (flush_directory_cache)
1497 vfs_s_invalidate(me, super);
1498 return 0;
1501 /* This routine is called as the last step in load_setup */
1502 void
1503 ftpfs_init_passwd(void)
1505 ftpfs_anonymous_passwd = load_anon_passwd ();
1506 if (ftpfs_anonymous_passwd)
1507 return;
1509 /* If there is no anonymous ftp password specified
1510 * then we'll just use anonymous@
1511 * We don't send any other thing because:
1512 * - We want to remain anonymous
1513 * - We want to stop SPAM
1514 * - We don't want to let ftp sites to discriminate by the user,
1515 * host or country.
1517 ftpfs_anonymous_passwd = g_strdup ("anonymous@");
1520 static int ftpfs_chmod (vfs *me, char *path, int mode)
1522 char buf[BUF_SMALL];
1524 g_snprintf(buf, sizeof(buf), "SITE CHMOD %4.4o /%%s", mode & 07777);
1525 return send_ftp_command(me, path, buf, OPT_FLUSH);
1528 static int ftpfs_chown (vfs *me, char *path, int owner, int group)
1530 #if 0
1531 my_errno = EPERM;
1532 return -1;
1533 #else
1534 /* Everyone knows it is not possible to chown remotely, so why bother them.
1535 If someone's root, then copy/move will always try to chown it... */
1536 return 0;
1537 #endif
1540 static int ftpfs_unlink (vfs *me, char *path)
1542 return send_ftp_command(me, path, "DELE /%s", OPT_FLUSH);
1545 /* Return 1 if path is the same directory as the one we are in now */
1546 static int
1547 is_same_dir (vfs *me, vfs_s_super *super, const char *path)
1549 if (!SUP.cwdir)
1550 return 0;
1551 if (strcmp (path, SUP.cwdir) == 0)
1552 return 1;
1553 return 0;
1556 static int
1557 ftpfs_chdir_internal (vfs *me, vfs_s_super *super, char *remote_path)
1559 int r;
1560 char *p;
1562 if (!SUP.cwd_defered && is_same_dir (me, super, remote_path))
1563 return COMPLETE;
1565 p = translate_path (me, super, remote_path);
1566 r = command (me, super, WAIT_REPLY, "CWD /%s", p);
1567 g_free (p);
1569 if (r != COMPLETE) {
1570 my_errno = EIO;
1571 } else {
1572 g_free(SUP.cwdir);
1573 SUP.cwdir = g_strdup (remote_path);
1574 SUP.cwd_defered = 0;
1576 return r;
1579 static int ftpfs_rename (vfs *me, char *path1, char *path2)
1581 send_ftp_command(me, path1, "RNFR /%s", OPT_FLUSH);
1582 return send_ftp_command(me, path2, "RNTO /%s", OPT_FLUSH);
1585 static int ftpfs_mkdir (vfs *me, char *path, mode_t mode)
1587 return send_ftp_command(me, path, "MKD /%s", OPT_FLUSH);
1590 static int ftpfs_rmdir (vfs *me, char *path)
1592 return send_ftp_command(me, path, "RMD /%s", OPT_FLUSH);
1595 static int ftpfs_fh_open (vfs *me, vfs_s_fh *fh, int flags, int mode)
1597 fh->u.ftp.append = 0;
1598 /* File will be written only, so no need to retrieve it from ftp server */
1599 if (((flags & O_WRONLY) == O_WRONLY) && !(flags & (O_RDONLY|O_RDWR))){
1600 #ifdef HAVE_STRUCT_LINGER
1601 struct linger li;
1602 #else
1603 int li = 1;
1604 #endif
1605 char * name;
1607 /* linear_start() called, so data will be written
1608 * to local temporary file and stored to ftp server
1609 * by vfs_s_close later
1611 if (FH_SUPER->u.ftp.control_connection_buzy){
1612 if (!fh->ino->localname){
1613 int handle = mc_mkstemps (&fh->ino->localname, me->name, NULL);
1614 if (handle == -1)
1615 return -1;
1616 close (handle);
1617 fh->u.ftp.append = flags & O_APPEND;
1619 return 0;
1621 name = vfs_s_fullpath (me, fh->ino);
1622 if (!name)
1623 return -1;
1624 fh->handle = open_data_connection(me, fh->ino->super,
1625 (flags & O_APPEND) ? "APPE" : "STOR", name, TYPE_BINARY, 0);
1626 g_free (name);
1628 if (fh->handle < 0)
1629 return -1;
1630 #ifdef HAVE_STRUCT_LINGER
1631 li.l_onoff = 1;
1632 li.l_linger = 120;
1633 #endif
1634 setsockopt(fh->handle, SOL_SOCKET, SO_LINGER, &li, sizeof(li));
1636 if (fh->ino->localname){
1637 unlink (fh->ino->localname);
1638 g_free (fh->ino->localname);
1639 fh->ino->localname = NULL;
1641 return 0;
1644 if (!fh->ino->localname)
1645 if (vfs_s_retrieve_file (me, fh->ino)==-1)
1646 return -1;
1647 if (!fh->ino->localname)
1648 vfs_die( "retrieve_file failed to fill in localname" );
1649 return 0;
1652 static int ftpfs_fh_close (vfs *me, vfs_s_fh *fh)
1654 if (fh->handle != -1 && !fh->ino->localname){
1655 close (fh->handle);
1656 fh->handle = -1;
1657 /* File is stored to destination already, so
1658 * we prevent MEDATA->file_store() call from vfs_s_close ()
1660 fh->changed = 0;
1661 if (get_reply (me, fh->ino->SUP.sock, NULL, 0) != COMPLETE)
1662 ERRNOR (EIO, -1);
1663 vfs_s_invalidate (me, FH_SUPER);
1665 return 0;
1668 static struct vfs_s_data ftp_data = {
1669 NULL,
1672 NULL, /* logfile */
1674 NULL, /* init_inode */
1675 NULL, /* free_inode */
1676 NULL, /* init_entry */
1678 NULL, /* archive_check */
1679 archive_same,
1680 open_archive,
1681 free_archive,
1683 ftpfs_fh_open, /* fh_open */
1684 ftpfs_fh_close, /* fh_close */
1686 vfs_s_find_entry_linear,
1687 dir_load,
1688 dir_uptodate,
1689 file_store,
1691 linear_start,
1692 linear_read,
1693 linear_close
1696 static void
1697 ftpfs_fill_names (vfs *me, void (*func)(char *))
1699 struct vfs_s_super * super = ftp_data.supers;
1700 char *name;
1702 while (super){
1703 name = g_strconcat ("/#ftp:", SUP.user, "@", SUP.host, "/", SUP.cwdir, NULL);
1704 (*func)(name);
1705 g_free (name);
1706 super = super->next;
1710 vfs vfs_ftpfs_ops = {
1711 NULL, /* This is place of next pointer */
1712 "ftpfs",
1713 F_NET, /* flags */
1714 "ftp:", /* prefix */
1715 &ftp_data, /* data */
1716 0, /* errno */
1717 NULL, /* init */
1718 NULL, /* done */
1719 ftpfs_fill_names,
1720 NULL,
1722 vfs_s_open,
1723 vfs_s_close,
1724 vfs_s_read,
1725 vfs_s_write,
1727 vfs_s_opendir,
1728 vfs_s_readdir,
1729 vfs_s_closedir,
1730 vfs_s_telldir,
1731 vfs_s_seekdir,
1733 vfs_s_stat,
1734 vfs_s_lstat,
1735 vfs_s_fstat,
1737 ftpfs_chmod,
1738 ftpfs_chown, /* not really implemented but returns success */
1739 NULL,
1741 vfs_s_readlink,
1742 NULL,
1743 NULL,
1744 ftpfs_unlink,
1746 ftpfs_rename,
1747 vfs_s_chdir,
1748 vfs_s_ferrno,
1749 vfs_s_lseek,
1750 NULL,
1752 vfs_s_getid,
1753 vfs_s_nothingisopen,
1754 vfs_s_free,
1756 NULL,
1757 NULL,
1759 ftpfs_mkdir,
1760 ftpfs_rmdir,
1761 ftpfs_ctl,
1762 vfs_s_setctl
1764 MMAPNULL
1767 void ftpfs_set_debug (const char *file)
1769 logfile = fopen (file, "w+");
1770 if (logfile)
1771 ftp_data.logfile = logfile;
1774 static char buffer[BUF_MEDIUM];
1775 static char *netrc, *netrcp;
1777 /* This should match the keywords[] array below */
1778 typedef enum {
1779 NETRC_NONE = 0,
1780 NETRC_DEFAULT,
1781 NETRC_MACHINE,
1782 NETRC_LOGIN,
1783 NETRC_PASSWORD,
1784 NETRC_PASSWD,
1785 NETRC_ACCOUNT,
1786 NETRC_MACDEF,
1787 NETRC_UNKNOWN
1788 } keyword_t;
1790 static keyword_t netrc_next (void)
1792 char *p;
1793 keyword_t i;
1794 static const char *const keywords[] = { "default", "machine",
1795 "login", "password", "passwd", "account", "macdef", NULL
1799 while (1) {
1800 netrcp = skip_separators (netrcp);
1801 if (*netrcp != '\n')
1802 break;
1803 netrcp++;
1805 if (!*netrcp)
1806 return NETRC_NONE;
1807 p = buffer;
1808 if (*netrcp == '"') {
1809 for (netrcp++; *netrcp != '"' && *netrcp; netrcp++) {
1810 if (*netrcp == '\\')
1811 netrcp++;
1812 *p++ = *netrcp;
1814 } else {
1815 for (; *netrcp != '\n' && *netrcp != '\t' && *netrcp != ' ' &&
1816 *netrcp != ',' && *netrcp; netrcp++) {
1817 if (*netrcp == '\\')
1818 netrcp++;
1819 *p++ = *netrcp;
1822 *p = 0;
1823 if (!*buffer)
1824 return 0;
1826 i = NETRC_DEFAULT;
1827 while (keywords[i - 1]) {
1828 if (!strcmp (keywords[i - 1], buffer))
1829 return i;
1831 i++;
1834 return NETRC_UNKNOWN;
1837 static int netrc_has_incorrect_mode (char *netrcname, char *netrc)
1839 static int be_angry = 1;
1840 struct stat mystat;
1842 if (stat (netrcname, &mystat) >= 0 && (mystat.st_mode & 077)) {
1843 if (be_angry) {
1844 message_1s (1, MSG_ERROR,
1845 _("~/.netrc file has not correct mode.\n"
1846 "Remove password or correct mode."));
1847 be_angry = 0;
1849 return 1;
1851 return 0;
1854 /* Scan .netrc until we find matching "machine" or "default"
1855 * domain is used for additional matching
1856 * No search is done after "default" in compliance with "man netrc"
1857 * Return 0 if found, -1 otherwise */
1858 static int find_machine (const char *host, const char *domain)
1860 keyword_t keyword;
1862 while ((keyword = netrc_next ()) != NETRC_NONE) {
1863 if (keyword == NETRC_DEFAULT)
1864 return 0;
1866 if (keyword == NETRC_MACDEF) {
1867 /* Scan for an empty line, which concludes "macdef" */
1868 do {
1869 while (*netrcp && *netrcp != '\n')
1870 netrcp++;
1871 if (*netrcp != '\n')
1872 break;
1873 netrcp++;
1874 } while (*netrcp && *netrcp != '\n');
1875 continue;
1878 if (keyword != NETRC_MACHINE)
1879 continue;
1881 /* Take machine name */
1882 if (netrc_next () == NETRC_NONE)
1883 break;
1885 if (g_strcasecmp (host, buffer)) {
1886 /* Try adding our domain to short names in .netrc */
1887 char *host_domain = strchr (host, '.');
1888 if (!host_domain)
1889 continue;
1891 /* Compare domain part */
1892 if (g_strcasecmp (host_domain, domain))
1893 continue;
1895 /* Compare local part */
1896 if (g_strncasecmp (host, buffer, host_domain - host))
1897 continue;
1900 return 0;
1903 /* end of .netrc */
1904 return -1;
1907 /* Extract login and password from .netrc for the host.
1908 * pass may be NULL.
1909 * Returns 0 for success, -1 for error */
1910 static int lookup_netrc (const char *host, char **login, char **pass)
1912 char *netrcname;
1913 char *tmp_pass = NULL;
1914 char hostname[MAXHOSTNAMELEN], *domain;
1915 keyword_t keyword;
1916 static struct rupcache {
1917 struct rupcache *next;
1918 char *host;
1919 char *login;
1920 char *pass;
1921 } *rup_cache = NULL, *rupp;
1923 /* Initialize *login and *pass */
1924 if (!login)
1925 return 0;
1926 *login = NULL;
1927 if (pass)
1928 *pass = NULL;
1930 /* Look up in the cache first */
1931 for (rupp = rup_cache; rupp != NULL; rupp = rupp->next) {
1932 if (!strcmp (host, rupp->host)) {
1933 if (rupp->login)
1934 *login = g_strdup (rupp->login);
1935 if (pass && rupp->pass)
1936 *pass = g_strdup (rupp->pass);
1937 return 0;
1941 /* Load current .netrc */
1942 netrcname = concat_dir_and_file (home_dir, ".netrc");
1943 netrcp = netrc = load_file (netrcname);
1944 if (netrc == NULL) {
1945 g_free (netrcname);
1946 return 0;
1949 /* Find our own domain name */
1950 if (gethostname (hostname, sizeof (hostname)) < 0)
1951 *hostname = 0;
1952 if (!(domain = strchr (hostname, '.')))
1953 domain = "";
1955 /* Scan for "default" and matching "machine" keywords */
1956 find_machine (host, domain);
1958 /* Scan for keywords following "default" and "machine" */
1959 while (1) {
1960 int need_break = 0;
1961 keyword = netrc_next ();
1963 switch (keyword) {
1964 case NETRC_LOGIN:
1965 if (netrc_next () == NETRC_NONE) {
1966 need_break = 1;
1967 break;
1970 /* We have another name already - should not happen */
1971 if (*login) {
1972 need_break = 1;
1973 break;
1976 /* We have login name now */
1977 *login = g_strdup (buffer);
1978 break;
1980 case NETRC_PASSWORD:
1981 case NETRC_PASSWD:
1982 if (netrc_next () == NETRC_NONE) {
1983 need_break = 1;
1984 break;
1987 /* Ignore unsafe passwords */
1988 if (strcmp (*login, "anonymous") && strcmp (*login, "ftp")
1989 && netrc_has_incorrect_mode (netrcname, netrc)) {
1990 need_break = 1;
1991 break;
1994 /* Remember password. pass may be NULL, so use tmp_pass */
1995 if (tmp_pass == NULL)
1996 tmp_pass = g_strdup (buffer);
1997 break;
1999 case NETRC_ACCOUNT:
2000 /* "account" is followed by a token which we ignore */
2001 if (netrc_next () == NETRC_NONE) {
2002 need_break = 1;
2003 break;
2006 /* Ignore account, but warn user anyways */
2007 netrc_has_incorrect_mode (netrcname, netrc);
2008 break;
2010 default:
2011 /* Unexpected keyword or end of file */
2012 need_break = 1;
2013 break;
2016 if (need_break)
2017 break;
2020 g_free (netrc);
2021 g_free (netrcname);
2023 rupp = g_new (struct rupcache, 1);
2024 rupp->host = g_strdup (host);
2025 rupp->login = rupp->pass = 0;
2027 if (*login != NULL) {
2028 rupp->login = g_strdup (*login);
2030 if (tmp_pass != NULL)
2031 rupp->pass = g_strdup (tmp_pass);
2032 rupp->next = rup_cache;
2033 rup_cache = rupp;
2035 if (pass)
2036 *pass = tmp_pass;
2038 return 0;