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