]> sjero.net Git - wget/blob - src/ftp-basic.c
[svn] Added NTLM support.
[wget] / src / ftp-basic.c
1 /* Basic FTP routines.
2    Copyright (C) 1995, 1996, 1997, 1998, 2000 Free Software Foundation, Inc.
3
4 This file is part of GNU Wget.
5
6 GNU Wget is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10
11 GNU Wget is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Wget; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 In addition, as a special exception, the Free Software Foundation
21 gives permission to link the code of its release of Wget with the
22 OpenSSL project's "OpenSSL" library (or with modified versions of it
23 that use the same license as the "OpenSSL" library), and distribute
24 the linked executables.  You must obey the GNU General Public License
25 in all respects for all of the code used other than "OpenSSL".  If you
26 modify this file, you may extend this exception to your version of the
27 file, but you are not obligated to do so.  If you do not wish to do
28 so, delete this exception statement from your version.  */
29
30 #include <config.h>
31
32 #include <assert.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <errno.h>
36
37 #ifdef HAVE_STRING_H
38 # include <string.h>
39 #else
40 # include <strings.h>
41 #endif
42 #ifdef HAVE_UNISTD_H
43 # include <unistd.h>
44 #endif
45 #include <sys/types.h>
46
47 #include "wget.h"
48 #include "utils.h"
49 #include "connect.h"
50 #include "host.h"
51 #include "ftp.h"
52 #include "retr.h"
53
54 char ftp_last_respline[128];
55
56 \f
57 /* Get the response of FTP server and allocate enough room to handle
58    it.  <CR> and <LF> characters are stripped from the line, and the
59    line is 0-terminated.  All the response lines but the last one are
60    skipped.  The last line is determined as described in RFC959.  */
61 uerr_t
62 ftp_response (int fd, char **ret_line)
63 {
64   while (1)
65     {
66       char *line = fd_read_line (fd);
67       if (!line)
68         return FTPRERR;
69       if (opt.server_response)
70         logputs (LOG_NOTQUIET, escnonprint (line));
71       else
72         DEBUGP (("%s", escnonprint (line)));
73       if (ISDIGIT (line[0]) && ISDIGIT (line[1]) && ISDIGIT (line[2])
74           && line[3] == ' ')
75         {
76           char *p = line + strlen (line);
77           if (p > line && p[-1] == '\n')
78             *--p = '\0';
79           if (p > line && p[-1] == '\r')
80             *--p = '\0';
81           strncpy (ftp_last_respline, line, sizeof (ftp_last_respline));
82           ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
83           *ret_line = line;
84           return FTPOK;
85         }
86       xfree (line);
87     }
88 }
89
90 /* Returns the malloc-ed FTP request, ending with <CR><LF>, printing
91    it if printing is required.  If VALUE is NULL, just use
92    command<CR><LF>.  */
93 static char *
94 ftp_request (const char *command, const char *value)
95 {
96   char *res;
97   if (value)
98     res = concat_strings (command, " ", value, "\r\n", (char *) 0);
99   else
100     res = concat_strings (command, "\r\n", (char *) 0);
101   if (opt.server_response)
102     {
103       /* Hack: don't print out password.  */
104       if (strncmp (res, "PASS", 4) != 0)
105         logprintf (LOG_ALWAYS, "--> %s\n", res);
106       else
107         logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n\n");
108     }
109   else
110     DEBUGP (("\n--> %s\n", res));
111   return res;
112 }
113
114 /* Sends the USER and PASS commands to the server, to control
115    connection socket csock.  */
116 uerr_t
117 ftp_login (int csock, const char *acc, const char *pass)
118 {
119   uerr_t err;
120   char *request, *respline;
121   int nwritten;
122
123   /* Get greeting.  */
124   err = ftp_response (csock, &respline);
125   if (err != FTPOK)
126     return err;
127   if (*respline != '2')
128     {
129       xfree (respline);
130       return FTPSRVERR;
131     }
132   xfree (respline);
133   /* Send USER username.  */
134   request = ftp_request ("USER", acc);
135   nwritten = fd_write (csock, request, strlen (request), -1);
136   if (nwritten < 0)
137     {
138       xfree (request);
139       return WRITEFAILED;
140     }
141   xfree (request);
142   /* Get appropriate response.  */
143   err = ftp_response (csock, &respline);
144   if (err != FTPOK)
145     {
146       xfree (respline);
147       return err;
148     }
149   /* An unprobable possibility of logging without a password.  */
150   if (*respline == '2')
151     {
152       xfree (respline);
153       return FTPOK;
154     }
155   /* Else, only response 3 is appropriate.  */
156   if (*respline != '3')
157     {
158       xfree (respline);
159       return FTPLOGREFUSED;
160     }
161 #ifdef ENABLE_OPIE
162   {
163     static const char *skey_head[] = {
164       "331 s/key ",
165       "331 opiekey "
166     };
167     int i;
168     const char *seed = NULL;
169
170     for (i = 0; i < countof (skey_head); i++)
171       {
172         int l = strlen (skey_head[i]);
173         if (0 == strncasecmp (skey_head[i], respline, l))
174           {
175             seed = respline + l;
176             break;
177           }
178       }
179     if (seed)
180       {
181         int skey_sequence = 0;
182
183         /* Extract the sequence from SEED.  */
184         for (; ISDIGIT (*seed); seed++)
185           skey_sequence = 10 * skey_sequence + *seed - '0';
186         if (*seed == ' ')
187           ++seed;
188         else
189           {
190             xfree (respline);
191             return FTPLOGREFUSED;
192           }
193         /* Replace the password with the SKEY response to the
194            challenge.  */
195         pass = skey_response (skey_sequence, seed, pass);
196       }
197   }
198 #endif /* ENABLE_OPIE */
199   xfree (respline);
200   /* Send PASS password.  */
201   request = ftp_request ("PASS", pass);
202   nwritten = fd_write (csock, request, strlen (request), -1);
203   if (nwritten < 0)
204     {
205       xfree (request);
206       return WRITEFAILED;
207     }
208   xfree (request);
209   /* Get appropriate response.  */
210   err = ftp_response (csock, &respline);
211   if (err != FTPOK)
212     {
213       xfree (respline);
214       return err;
215     }
216   if (*respline != '2')
217     {
218       xfree (respline);
219       return FTPLOGINC;
220     }
221   xfree (respline);
222   /* All OK.  */
223   return FTPOK;
224 }
225
226 static void
227 ip_address_to_port_repr (const ip_address *addr, int port, char *buf, 
228                          size_t buflen)
229 {
230   unsigned char *ptr;
231
232   assert (addr != NULL);
233   assert (addr->type == IPV4_ADDRESS);
234   assert (buf != NULL);
235   /* buf must contain the argument of PORT (of the form a,b,c,d,e,f). */
236   assert (buflen >= 6 * 4);
237
238   ptr = ADDRESS_IPV4_DATA (addr);
239   snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1],
240             ptr[2], ptr[3], (port & 0xff00) >> 8, port & 0xff);
241   buf[buflen - 1] = '\0';
242 }
243
244 /* Bind a port and send the appropriate PORT command to the FTP
245    server.  Use acceptport after RETR, to get the socket of data
246    connection.  */
247 uerr_t
248 ftp_port (int csock, int *local_sock)
249 {
250   uerr_t err;
251   char *request, *respline;
252   ip_address addr;
253   int nwritten;
254   int port;
255   /* Must contain the argument of PORT (of the form a,b,c,d,e,f). */
256   char bytes[6 * 4 + 1];
257
258   /* Get the address of this side of the connection. */
259   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
260     return FTPSYSERR;
261
262   assert (addr.type == IPV4_ADDRESS);
263
264   /* Setting port to 0 lets the system choose a free port.  */
265   port = 0;
266
267   /* Bind the port.  */
268   *local_sock = bind_local (&addr, &port);
269   if (*local_sock < 0)
270     return FTPSYSERR;
271
272   /* Construct the argument of PORT (of the form a,b,c,d,e,f). */
273   ip_address_to_port_repr (&addr, port, bytes, sizeof (bytes));
274
275   /* Send PORT request.  */
276   request = ftp_request ("PORT", bytes);
277   nwritten = fd_write (csock, request, strlen (request), -1);
278   if (nwritten < 0)
279     {
280       xfree (request);
281       fd_close (*local_sock);
282       return WRITEFAILED;
283     }
284   xfree (request);
285
286   /* Get appropriate response.  */
287   err = ftp_response (csock, &respline);
288   if (err != FTPOK)
289     {
290       xfree (respline);
291       fd_close (*local_sock);
292       return err;
293     }
294   if (*respline != '2')
295     {
296       xfree (respline);
297       fd_close (*local_sock);
298       return FTPPORTERR;
299     }
300   xfree (respline);
301   return FTPOK;
302 }
303
304 #ifdef ENABLE_IPV6
305 static void
306 ip_address_to_lprt_repr (const ip_address *addr, int port, char *buf, 
307                          size_t buflen)
308 {
309   unsigned char *ptr;
310
311   assert (addr != NULL);
312   assert (addr->type == IPV4_ADDRESS || addr->type == IPV6_ADDRESS);
313   assert (buf != NULL);
314   /* buf must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
315   assert (buflen >= 21 * 4);
316
317   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
318   switch (addr->type) 
319     {
320       case IPV4_ADDRESS: 
321         ptr = ADDRESS_IPV4_DATA (addr);
322         snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d", 4, 4, 
323                   ptr[0], ptr[1], ptr[2], ptr[3], 2,
324                   (port & 0xff00) >> 8, port & 0xff);
325         buf[buflen - 1] = '\0';
326         break;
327       case IPV6_ADDRESS: 
328         ptr = ADDRESS_IPV6_DATA (addr);
329         snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
330                   6, 16, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7], 
331                   ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15], 2,
332                   (port & 0xff00) >> 8, port & 0xff);
333         buf[buflen - 1] = '\0';
334         break;
335     }
336 }
337
338 /* Bind a port and send the appropriate PORT command to the FTP
339    server.  Use acceptport after RETR, to get the socket of data
340    connection.  */
341 uerr_t
342 ftp_lprt (int csock, int *local_sock)
343 {
344   uerr_t err;
345   char *request, *respline;
346   ip_address addr;
347   int nwritten;
348   int port;
349   /* Must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
350   char bytes[21 * 4 + 1];
351
352   /* Get the address of this side of the connection. */
353   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
354     return FTPSYSERR;
355
356   assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
357
358   /* Setting port to 0 lets the system choose a free port.  */
359   port = 0;
360
361   /* Bind the port.  */
362   *local_sock = bind_local (&addr, &port);
363   if (*local_sock < 0)
364     return FTPSYSERR;
365
366   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
367   ip_address_to_lprt_repr (&addr, port, bytes, sizeof (bytes));
368
369   /* Send PORT request.  */
370   request = ftp_request ("LPRT", bytes);
371   nwritten = fd_write (csock, request, strlen (request), -1);
372   if (nwritten < 0)
373     {
374       xfree (request);
375       fd_close (*local_sock);
376       return WRITEFAILED;
377     }
378   xfree (request);
379   /* Get appropriate response.  */
380   err = ftp_response (csock, &respline);
381   if (err != FTPOK)
382     {
383       xfree (respline);
384       fd_close (*local_sock);
385       return err;
386     }
387   if (*respline != '2')
388     {
389       xfree (respline);
390       fd_close (*local_sock);
391       return FTPPORTERR;
392     }
393   xfree (respline);
394   return FTPOK;
395 }
396
397 static void
398 ip_address_to_eprt_repr (const ip_address *addr, int port, char *buf, 
399                          size_t buflen)
400 {
401   int afnum;
402
403   assert (addr != NULL);
404   assert (addr->type == IPV4_ADDRESS || addr->type == IPV6_ADDRESS);
405   assert (buf != NULL);
406   /* buf must contain the argument of EPRT (of the form |af|addr|port|). 
407    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
408    * 1 char for af (1-2) and 5 chars for port (0-65535) */
409   assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5); 
410
411   /* Construct the argument of EPRT (of the form |af|addr|port|). */
412   afnum = (addr->type == IPV4_ADDRESS ? 1 : 2);
413   snprintf (buf, buflen, "|%d|%s|%d|", afnum, pretty_print_address (addr), port);
414   buf[buflen - 1] = '\0';
415 }
416
417 /* Bind a port and send the appropriate PORT command to the FTP
418    server.  Use acceptport after RETR, to get the socket of data
419    connection.  */
420 uerr_t
421 ftp_eprt (int csock, int *local_sock)
422 {
423   uerr_t err;
424   char *request, *respline;
425   ip_address addr;
426   int nwritten;
427   int port;
428   /* Must contain the argument of EPRT (of the form |af|addr|port|). 
429    * 4 chars for the | separators, ENABLE_IPV6_ADDRSTRLEN chars for addr  
430    * 1 char for af (1-2) and 5 chars for port (0-65535) */
431   char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1];
432
433   /* Get the address of this side of the connection. */
434   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
435     return FTPSYSERR;
436
437   assert (addr.type == IPV4_ADDRESS || addr.type == IPV6_ADDRESS);
438
439   /* Setting port to 0 lets the system choose a free port.  */
440   port = 0;
441
442   /* Bind the port.  */
443   *local_sock = bind_local (&addr, &port);
444   if (*local_sock < 0)
445     return FTPSYSERR;
446
447   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
448   ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes));
449
450   /* Send PORT request.  */
451   request = ftp_request ("EPRT", bytes);
452   nwritten = fd_write (csock, request, strlen (request), -1);
453   if (nwritten < 0)
454     {
455       xfree (request);
456       fd_close (*local_sock);
457       return WRITEFAILED;
458     }
459   xfree (request);
460   /* Get appropriate response.  */
461   err = ftp_response (csock, &respline);
462   if (err != FTPOK)
463     {
464       xfree (respline);
465       fd_close (*local_sock);
466       return err;
467     }
468   if (*respline != '2')
469     {
470       xfree (respline);
471       fd_close (*local_sock);
472       return FTPPORTERR;
473     }
474   xfree (respline);
475   return FTPOK;
476 }
477 #endif
478
479 /* Similar to ftp_port, but uses `PASV' to initiate the passive FTP
480    transfer.  Reads the response from server and parses it.  Reads the
481    host and port addresses and returns them.  */
482 uerr_t
483 ftp_pasv (int csock, ip_address *addr, int *port)
484 {
485   char *request, *respline, *s;
486   int nwritten, i;
487   uerr_t err;
488   unsigned char tmp[6];
489
490   assert (addr != NULL);
491   assert (port != NULL);
492
493   xzero (*addr);
494
495   /* Form the request.  */
496   request = ftp_request ("PASV", NULL);
497   /* And send it.  */
498   nwritten = fd_write (csock, request, strlen (request), -1);
499   if (nwritten < 0)
500     {
501       xfree (request);
502       return WRITEFAILED;
503     }
504   xfree (request);
505   /* Get the server response.  */
506   err = ftp_response (csock, &respline);
507   if (err != FTPOK)
508     {
509       xfree (respline);
510       return err;
511     }
512   if (*respline != '2')
513     {
514       xfree (respline);
515       return FTPNOPASV;
516     }
517   /* Parse the request.  */
518   s = respline;
519   for (s += 4; *s && !ISDIGIT (*s); s++);
520   if (!*s)
521     return FTPINVPASV;
522   for (i = 0; i < 6; i++)
523     {
524       tmp[i] = 0;
525       for (; ISDIGIT (*s); s++)
526         tmp[i] = (*s - '0') + 10 * tmp[i];
527       if (*s == ',')
528         s++;
529       else if (i < 5)
530         {
531           /* When on the last number, anything can be a terminator.  */
532           xfree (respline);
533           return FTPINVPASV;
534         }
535     }
536   xfree (respline);
537
538   addr->type = IPV4_ADDRESS;
539   memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
540   *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
541
542   return FTPOK;
543 }
544
545 #ifdef ENABLE_IPV6
546 /* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
547    transfer.  Reads the response from server and parses it.  Reads the
548    host and port addresses and returns them.  */
549 uerr_t
550 ftp_lpsv (int csock, ip_address *addr, int *port)
551 {
552   char *request, *respline, *s;
553   int nwritten, i, af, addrlen, portlen;
554   uerr_t err;
555   unsigned char tmp[16];
556   unsigned char tmpprt[2];
557
558   assert (addr != NULL);
559   assert (port != NULL);
560
561   xzero (*addr);
562
563   /* Form the request.  */
564   request = ftp_request ("LPSV", NULL);
565
566   /* And send it.  */
567   nwritten = fd_write (csock, request, strlen (request), -1);
568   if (nwritten < 0)
569     {
570       xfree (request);
571       return WRITEFAILED;
572     }
573   xfree (request);
574
575   /* Get the server response.  */
576   err = ftp_response (csock, &respline);
577   if (err != FTPOK)
578     {
579       xfree (respline);
580       return err;
581     }
582   if (*respline != '2')
583     {
584       xfree (respline);
585       return FTPNOPASV;
586     }  
587
588   /* Parse the response.  */
589   s = respline;
590   for (s += 4; *s && !ISDIGIT (*s); s++);
591   if (!*s)
592     return FTPINVPASV;
593
594   /* First, get the address family */
595   af = 0;
596   for (; ISDIGIT (*s); s++)
597     af = (*s - '0') + 10 * af;
598
599   if (af != 4 && af != 6)
600     {
601       xfree (respline);
602       return FTPINVPASV;
603     }
604
605   if (!*s || *s++ != ',')
606     {
607       xfree (respline);
608       return FTPINVPASV;
609     }
610
611   /* Then, get the address length */
612   addrlen = 0;
613   for (; ISDIGIT (*s); s++)
614     addrlen = (*s - '0') + 10 * addrlen;
615
616   if (!*s || *s++ != ',')
617     {
618       xfree (respline);
619       return FTPINVPASV;
620     }
621
622   if (addrlen > 16)
623     {
624       xfree (respline);
625       return FTPINVPASV;
626     }
627
628   if ((af == 4 && addrlen != 4)
629       || (af == 6 && addrlen != 16))
630     {
631       xfree (respline);
632       return FTPINVPASV;
633     }
634
635   /* Now, we get the actual address */
636   for (i = 0; i < addrlen; i++)
637     {
638       tmp[i] = 0;
639       for (; ISDIGIT (*s); s++)
640         tmp[i] = (*s - '0') + 10 * tmp[i];
641       if (*s == ',')
642         s++;
643       else
644         {
645           xfree (respline);
646           return FTPINVPASV;
647         }
648     }
649
650   /* Now, get the port length */
651   portlen = 0;
652   for (; ISDIGIT (*s); s++)
653     portlen = (*s - '0') + 10 * portlen;
654
655   if (!*s || *s++ != ',')
656     {
657       xfree (respline);
658       return FTPINVPASV;
659     }
660
661   if (portlen > 2)
662     {
663       xfree (respline);
664       return FTPINVPASV;
665     }
666
667   /* Finally, we get the port number */
668   tmpprt[0] = 0;
669   for (; ISDIGIT (*s); s++)
670     tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
671
672   if (!*s || *s++ != ',')
673     {
674       xfree (respline);
675       return FTPINVPASV;
676     }
677
678   tmpprt[1] = 0;
679   for (; ISDIGIT (*s); s++)
680     tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
681
682   assert (s != NULL);
683
684   if (af == 4)
685     {
686       addr->type = IPV4_ADDRESS;
687       memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
688       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
689       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
690       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
691       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
692       DEBUGP (("*port is: %d\n", *port));
693     }
694   else
695     {
696       assert (af == 6);
697       addr->type = IPV6_ADDRESS;
698       memcpy (ADDRESS_IPV6_DATA (addr), tmp, 16);
699       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
700       DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr)));
701       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
702       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
703       DEBUGP (("*port is: %d\n", *port));
704     }
705
706   xfree (respline);
707   return FTPOK;
708 }
709
710 /* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
711    transfer.  Reads the response from server and parses it.  Reads the
712    host and port addresses and returns them.  */
713 uerr_t
714 ftp_epsv (int csock, ip_address *ip, int *port)
715 {
716   char *request, *respline, *start, delim, *s;
717   int nwritten, i;
718   uerr_t err;
719   int tport;
720
721   assert (ip != NULL);
722   assert (port != NULL);
723
724   /* IP already contains the IP address of the control connection's
725      peer, so we don't need to call socket_ip_address here.  */
726
727   /* Form the request.  */
728   /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
729   request = ftp_request ("EPSV", (ip->type == IPV4_ADDRESS ? "1" : "2"));
730
731   /* And send it.  */
732   nwritten = fd_write (csock, request, strlen (request), -1);
733   if (nwritten < 0)
734     {
735       xfree (request);
736       return WRITEFAILED;
737     }
738   xfree (request);
739
740   /* Get the server response.  */
741   err = ftp_response (csock, &respline);
742   if (err != FTPOK)
743     {
744       xfree (respline);
745       return err;
746     }
747   if (*respline != '2')
748     {
749       xfree (respline);
750       return FTPNOPASV;
751     }  
752
753   assert (respline != NULL);
754
755   DEBUGP(("respline is %s\n", respline));
756
757   /* Parse the response.  */
758   s = respline;
759
760   /* Skip the useless stuff and get what's inside the parentheses */
761   start = strchr (respline, '(');
762   if (start == NULL)
763     {
764       xfree (respline);
765       return FTPINVPASV;
766     }  
767
768   /* Skip the first two void fields */
769   s = start + 1;
770   delim = *s++;
771   if (delim < 33 || delim > 126)
772     {
773       xfree (respline);
774       return FTPINVPASV;
775     }  
776
777   for (i = 0; i < 2; i++)
778     {
779       if (*s++ != delim) 
780         {
781           xfree (respline);
782         return FTPINVPASV;
783         }  
784     }
785
786   /* Finally, get the port number */
787   tport = 0; 
788   for (i = 1; ISDIGIT (*s); s++) 
789     {
790       if (i > 5)
791         {
792           xfree (respline);
793           return FTPINVPASV;
794         }  
795       tport = (*s - '0') + 10 * tport;
796     }
797
798   /* Make sure that the response terminates correcty */
799   if (*s++ != delim)
800     {
801       xfree (respline);
802       return FTPINVPASV;
803     }  
804
805   if (*s++ != ')')
806     {
807       xfree (respline);
808       return FTPINVPASV;
809     }  
810
811   *port = tport;
812
813   xfree (respline);
814   return FTPOK;
815 }
816 #endif
817
818 /* Sends the TYPE request to the server.  */
819 uerr_t
820 ftp_type (int csock, int type)
821 {
822   char *request, *respline;
823   int nwritten;
824   uerr_t err;
825   char stype[2];
826
827   /* Construct argument.  */
828   stype[0] = type;
829   stype[1] = 0;
830   /* Send TYPE request.  */
831   request = ftp_request ("TYPE", stype);
832   nwritten = fd_write (csock, request, strlen (request), -1);
833   if (nwritten < 0)
834     {
835       xfree (request);
836       return WRITEFAILED;
837     }
838   xfree (request);
839   /* Get appropriate response.  */
840   err = ftp_response (csock, &respline);
841   if (err != FTPOK)
842     {
843       xfree (respline);
844       return err;
845     }
846   if (*respline != '2')
847     {
848       xfree (respline);
849       return FTPUNKNOWNTYPE;
850     }
851   xfree (respline);
852   /* All OK.  */
853   return FTPOK;
854 }
855
856 /* Changes the working directory by issuing a CWD command to the
857    server.  */
858 uerr_t
859 ftp_cwd (int csock, const char *dir)
860 {
861   char *request, *respline;
862   int nwritten;
863   uerr_t err;
864
865   /* Send CWD request.  */
866   request = ftp_request ("CWD", dir);
867   nwritten = fd_write (csock, request, strlen (request), -1);
868   if (nwritten < 0)
869     {
870       xfree (request);
871       return WRITEFAILED;
872     }
873   xfree (request);
874   /* Get appropriate response.  */
875   err = ftp_response (csock, &respline);
876   if (err != FTPOK)
877     {
878       xfree (respline);
879       return err;
880     }
881   if (*respline == '5')
882     {
883       xfree (respline);
884       return FTPNSFOD;
885     }
886   if (*respline != '2')
887     {
888       xfree (respline);
889       return FTPRERR;
890     }
891   xfree (respline);
892   /* All OK.  */
893   return FTPOK;
894 }
895
896 /* Sends REST command to the FTP server.  */
897 uerr_t
898 ftp_rest (int csock, wgint offset)
899 {
900   char *request, *respline;
901   int nwritten;
902   uerr_t err;
903
904   request = ftp_request ("REST", number_to_static_string (offset));
905   nwritten = fd_write (csock, request, strlen (request), -1);
906   if (nwritten < 0)
907     {
908       xfree (request);
909       return WRITEFAILED;
910     }
911   xfree (request);
912   /* Get appropriate response.  */
913   err = ftp_response (csock, &respline);
914   if (err != FTPOK)
915     {
916       xfree (respline);
917       return err;
918     }
919   if (*respline != '3')
920     {
921       xfree (respline);
922       return FTPRESTFAIL;
923     }
924   xfree (respline);
925   /* All OK.  */
926   return FTPOK;
927 }
928
929 /* Sends RETR command to the FTP server.  */
930 uerr_t
931 ftp_retr (int csock, const char *file)
932 {
933   char *request, *respline;
934   int nwritten;
935   uerr_t err;
936
937   /* Send RETR request.  */
938   request = ftp_request ("RETR", file);
939   nwritten = fd_write (csock, request, strlen (request), -1);
940   if (nwritten < 0)
941     {
942       xfree (request);
943       return WRITEFAILED;
944     }
945   xfree (request);
946   /* Get appropriate response.  */
947   err = ftp_response (csock, &respline);
948   if (err != FTPOK)
949     {
950       xfree (respline);
951       return err;
952     }
953   if (*respline == '5')
954     {
955       xfree (respline);
956       return FTPNSFOD;
957     }
958   if (*respline != '1')
959     {
960       xfree (respline);
961       return FTPRERR;
962     }
963   xfree (respline);
964   /* All OK.  */
965   return FTPOK;
966 }
967
968 /* Sends the LIST command to the server.  If FILE is NULL, send just
969    `LIST' (no space).  */
970 uerr_t
971 ftp_list (int csock, const char *file)
972 {
973   char *request, *respline;
974   int nwritten;
975   uerr_t err;
976
977   /* Send LIST request.  */
978   request = ftp_request ("LIST", file);
979   nwritten = fd_write (csock, request, strlen (request), -1);
980   if (nwritten < 0)
981     {
982       xfree (request);
983       return WRITEFAILED;
984     }
985   xfree (request);
986   /* Get appropriate respone.  */
987   err = ftp_response (csock, &respline);
988   if (err != FTPOK)
989     {
990       xfree (respline);
991       return err;
992     }
993   if (*respline == '5')
994     {
995       xfree (respline);
996       return FTPNSFOD;
997     }
998   if (*respline != '1')
999     {
1000       xfree (respline);
1001       return FTPRERR;
1002     }
1003   xfree (respline);
1004   /* All OK.  */
1005   return FTPOK;
1006 }
1007
1008 /* Sends the SYST command to the server. */
1009 uerr_t
1010 ftp_syst (int csock, enum stype *server_type)
1011 {
1012   char *request, *respline;
1013   int nwritten;
1014   uerr_t err;
1015
1016   /* Send SYST request.  */
1017   request = ftp_request ("SYST", NULL);
1018   nwritten = fd_write (csock, request, strlen (request), -1);
1019   if (nwritten < 0)
1020     {
1021       xfree (request);
1022       return WRITEFAILED;
1023     }
1024   xfree (request);
1025
1026   /* Get appropriate response.  */
1027   err = ftp_response (csock, &respline);
1028   if (err != FTPOK)
1029     {
1030       xfree (respline);
1031       return err;
1032     }
1033   if (*respline == '5')
1034     {
1035       xfree (respline);
1036       return FTPSRVERR;
1037     }
1038
1039   /* Skip the number (215, but 200 (!!!) in case of VMS) */
1040   strtok (respline, " ");
1041
1042   /* Which system type has been reported (we are interested just in the
1043      first word of the server response)?  */
1044   request = strtok (NULL, " ");
1045
1046   if (!strcasecmp (request, "VMS"))
1047     *server_type = ST_VMS;
1048   else if (!strcasecmp (request, "UNIX"))
1049     *server_type = ST_UNIX;
1050   else if (!strcasecmp (request, "WINDOWS_NT")
1051            || !strcasecmp (request, "WINDOWS2000"))
1052     *server_type = ST_WINNT;
1053   else if (!strcasecmp (request, "MACOS"))
1054     *server_type = ST_MACOS;
1055   else if (!strcasecmp (request, "OS/400"))
1056     *server_type = ST_OS400;
1057   else
1058     *server_type = ST_OTHER;
1059
1060   xfree (respline);
1061   /* All OK.  */
1062   return FTPOK;
1063 }
1064
1065 /* Sends the PWD command to the server. */
1066 uerr_t
1067 ftp_pwd (int csock, char **pwd)
1068 {
1069   char *request, *respline;
1070   int nwritten;
1071   uerr_t err;
1072
1073   /* Send PWD request.  */
1074   request = ftp_request ("PWD", NULL);
1075   nwritten = fd_write (csock, request, strlen (request), -1);
1076   if (nwritten < 0)
1077     {
1078       xfree (request);
1079       return WRITEFAILED;
1080     }
1081   xfree (request);
1082   /* Get appropriate response.  */
1083   err = ftp_response (csock, &respline);
1084   if (err != FTPOK)
1085     {
1086       xfree (respline);
1087       return err;
1088     }
1089   if (*respline == '5')
1090     {
1091       xfree (respline);
1092       return FTPSRVERR;
1093     }
1094
1095   /* Skip the number (257), leading citation mark, trailing citation mark
1096      and everything following it. */
1097   strtok (respline, "\"");
1098   request = strtok (NULL, "\"");
1099
1100   /* Has the `pwd' been already allocated?  Free! */
1101   xfree_null (*pwd);
1102
1103   *pwd = xstrdup (request);
1104
1105   xfree (respline);
1106   /* All OK.  */
1107   return FTPOK;
1108 }
1109
1110 /* Sends the SIZE command to the server, and returns the value in 'size'.
1111  * If an error occurs, size is set to zero. */
1112 uerr_t
1113 ftp_size (int csock, const char *file, wgint *size)
1114 {
1115   char *request, *respline;
1116   int nwritten;
1117   uerr_t err;
1118
1119   /* Send PWD request.  */
1120   request = ftp_request ("SIZE", file);
1121   nwritten = fd_write (csock, request, strlen (request), -1);
1122   if (nwritten < 0)
1123     {
1124       xfree (request);
1125       *size = 0;
1126       return WRITEFAILED;
1127     }
1128   xfree (request);
1129   /* Get appropriate response.  */
1130   err = ftp_response (csock, &respline);
1131   if (err != FTPOK)
1132     {
1133       xfree (respline);
1134       *size = 0;
1135       return err;
1136     }
1137   if (*respline == '5')
1138     {
1139       /* 
1140        * Probably means SIZE isn't supported on this server.
1141        * Error is nonfatal since SIZE isn't in RFC 959 
1142        */
1143       xfree (respline);
1144       *size = 0;
1145       return FTPOK;
1146     }
1147
1148   errno = 0;
1149   *size = str_to_wgint (respline + 4, NULL, 10);
1150   if (errno)
1151     {
1152       /* 
1153        * Couldn't parse the response for some reason.  On the (few)
1154        * tests I've done, the response is 213 <SIZE> with nothing else -
1155        * maybe something a bit more resilient is necessary.  It's not a
1156        * fatal error, however.
1157        */
1158       xfree (respline);
1159       *size = 0;
1160       return FTPOK;
1161     }
1162
1163   xfree (respline);
1164   /* All OK.  */
1165   return FTPOK;
1166 }
1167
1168 /* If URL's params are of the form "type=X", return character X.
1169    Otherwise, return 'I' (the default type).  */
1170 char
1171 ftp_process_type (const char *params)
1172 {
1173   if (params
1174       && 0 == strncasecmp (params, "type=", 5)
1175       && params[5] != '\0')
1176     return TOUPPER (params[5]);
1177   else
1178     return 'I';
1179 }