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