]> sjero.net Git - wget/blob - src/ftp-basic.c
[svn] Simplify the ip_address data union. Always use inet_ntop for printing
[wget] / src / ftp-basic.c
1 /* Basic FTP routines.
2    Copyright (C) 1996-2005 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 Foundation, Inc.,
18 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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->family == AF_INET);
254   /* buf must contain the argument of PORT (of the form a,b,c,d,e,f). */
255   assert (buflen >= 6 * 4);
256
257   ptr = IP_INADDR_DATA (addr);
258   snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1],
259             ptr[2], ptr[3], (port & 0xff00) >> 8, port & 0xff);
260   buf[buflen - 1] = '\0';
261 }
262
263 /* Bind a port and send the appropriate PORT command to the FTP
264    server.  Use acceptport after RETR, to get the socket of data
265    connection.  */
266 uerr_t
267 ftp_port (int csock, int *local_sock)
268 {
269   uerr_t err;
270   char *request, *respline;
271   ip_address addr;
272   int nwritten;
273   int port;
274   /* Must contain the argument of PORT (of the form a,b,c,d,e,f). */
275   char bytes[6 * 4 + 1];
276
277   /* Get the address of this side of the connection. */
278   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
279     return FTPSYSERR;
280
281   assert (addr.family == AF_INET);
282
283   /* Setting port to 0 lets the system choose a free port.  */
284   port = 0;
285
286   /* Bind the port.  */
287   *local_sock = bind_local (&addr, &port);
288   if (*local_sock < 0)
289     return FTPSYSERR;
290
291   /* Construct the argument of PORT (of the form a,b,c,d,e,f). */
292   ip_address_to_port_repr (&addr, port, bytes, sizeof (bytes));
293
294   /* Send PORT request.  */
295   request = ftp_request ("PORT", bytes);
296   nwritten = fd_write (csock, request, strlen (request), -1);
297   if (nwritten < 0)
298     {
299       xfree (request);
300       fd_close (*local_sock);
301       return WRITEFAILED;
302     }
303   xfree (request);
304
305   /* Get appropriate response.  */
306   err = ftp_response (csock, &respline);
307   if (err != FTPOK)
308     {
309       fd_close (*local_sock);
310       return err;
311     }
312   if (*respline != '2')
313     {
314       xfree (respline);
315       fd_close (*local_sock);
316       return FTPPORTERR;
317     }
318   xfree (respline);
319   return FTPOK;
320 }
321
322 #ifdef ENABLE_IPV6
323 static void
324 ip_address_to_lprt_repr (const ip_address *addr, int port, char *buf, 
325                          size_t buflen)
326 {
327   unsigned char *ptr = IP_INADDR_DATA (addr);
328
329   /* buf must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
330   assert (buflen >= 21 * 4);
331
332   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
333   switch (addr->family) 
334     {
335     case AF_INET: 
336       snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d", 4, 4, 
337                 ptr[0], ptr[1], ptr[2], ptr[3], 2,
338                 (port & 0xff00) >> 8, port & 0xff);
339       break;
340     case AF_INET6: 
341       snprintf (buf, buflen,
342                 "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
343                 6, 16,
344                 ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7], 
345                 ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15],
346                 2, (port & 0xff00) >> 8, port & 0xff);
347       break;
348     default:
349       abort ();
350     }
351 }
352
353 /* Bind a port and send the appropriate PORT command to the FTP
354    server.  Use acceptport after RETR, to get the socket of data
355    connection.  */
356 uerr_t
357 ftp_lprt (int csock, int *local_sock)
358 {
359   uerr_t err;
360   char *request, *respline;
361   ip_address addr;
362   int nwritten;
363   int port;
364   /* Must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
365   char bytes[21 * 4 + 1];
366
367   /* Get the address of this side of the connection. */
368   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
369     return FTPSYSERR;
370
371   assert (addr.family == AF_INET || addr.family == AF_INET6);
372
373   /* Setting port to 0 lets the system choose a free port.  */
374   port = 0;
375
376   /* Bind the port.  */
377   *local_sock = bind_local (&addr, &port);
378   if (*local_sock < 0)
379     return FTPSYSERR;
380
381   /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */
382   ip_address_to_lprt_repr (&addr, port, bytes, sizeof (bytes));
383
384   /* Send PORT request.  */
385   request = ftp_request ("LPRT", bytes);
386   nwritten = fd_write (csock, request, strlen (request), -1);
387   if (nwritten < 0)
388     {
389       xfree (request);
390       fd_close (*local_sock);
391       return WRITEFAILED;
392     }
393   xfree (request);
394   /* Get appropriate response.  */
395   err = ftp_response (csock, &respline);
396   if (err != FTPOK)
397     {
398       fd_close (*local_sock);
399       return err;
400     }
401   if (*respline != '2')
402     {
403       xfree (respline);
404       fd_close (*local_sock);
405       return FTPPORTERR;
406     }
407   xfree (respline);
408   return FTPOK;
409 }
410
411 static void
412 ip_address_to_eprt_repr (const ip_address *addr, int port, char *buf, 
413                          size_t buflen)
414 {
415   int afnum;
416
417   /* buf must contain the argument of EPRT (of the form |af|addr|port|). 
418    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
419    * 1 char for af (1-2) and 5 chars for port (0-65535) */
420   assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5); 
421
422   /* Construct the argument of EPRT (of the form |af|addr|port|). */
423   afnum = (addr->family == AF_INET ? 1 : 2);
424   snprintf (buf, buflen, "|%d|%s|%d|", afnum, print_address (addr), port);
425   buf[buflen - 1] = '\0';
426 }
427
428 /* Bind a port and send the appropriate PORT command to the FTP
429    server.  Use acceptport after RETR, to get the socket of data
430    connection.  */
431 uerr_t
432 ftp_eprt (int csock, int *local_sock)
433 {
434   uerr_t err;
435   char *request, *respline;
436   ip_address addr;
437   int nwritten;
438   int port;
439   /* Must contain the argument of EPRT (of the form |af|addr|port|). 
440    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
441    * 1 char for af (1-2) and 5 chars for port (0-65535) */
442   char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1];
443
444   /* Get the address of this side of the connection. */
445   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
446     return FTPSYSERR;
447
448   /* Setting port to 0 lets the system choose a free port.  */
449   port = 0;
450
451   /* Bind the port.  */
452   *local_sock = bind_local (&addr, &port);
453   if (*local_sock < 0)
454     return FTPSYSERR;
455
456   /* Construct the argument of EPRT (of the form |af|addr|port|). */
457   ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes));
458
459   /* Send PORT request.  */
460   request = ftp_request ("EPRT", bytes);
461   nwritten = fd_write (csock, request, strlen (request), -1);
462   if (nwritten < 0)
463     {
464       xfree (request);
465       fd_close (*local_sock);
466       return WRITEFAILED;
467     }
468   xfree (request);
469   /* Get appropriate response.  */
470   err = ftp_response (csock, &respline);
471   if (err != FTPOK)
472     {
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);
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     return err;
517   if (*respline != '2')
518     {
519       xfree (respline);
520       return FTPNOPASV;
521     }
522   /* Parse the request.  */
523   s = respline;
524   for (s += 4; *s && !ISDIGIT (*s); s++)
525     ;
526   if (!*s)
527     return FTPINVPASV;
528   for (i = 0; i < 6; i++)
529     {
530       tmp[i] = 0;
531       for (; ISDIGIT (*s); s++)
532         tmp[i] = (*s - '0') + 10 * tmp[i];
533       if (*s == ',')
534         s++;
535       else if (i < 5)
536         {
537           /* When on the last number, anything can be a terminator.  */
538           xfree (respline);
539           return FTPINVPASV;
540         }
541     }
542   xfree (respline);
543
544   addr->family = AF_INET;
545   memcpy (IP_INADDR_DATA (addr), tmp, 4);
546   *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
547
548   return FTPOK;
549 }
550
551 #ifdef ENABLE_IPV6
552 /* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
553    transfer.  Reads the response from server and parses it.  Reads the
554    host and port addresses and returns them.  */
555 uerr_t
556 ftp_lpsv (int csock, ip_address *addr, int *port)
557 {
558   char *request, *respline, *s;
559   int nwritten, i, af, addrlen, portlen;
560   uerr_t err;
561   unsigned char tmp[16];
562   unsigned char tmpprt[2];
563
564   assert (addr != NULL);
565   assert (port != NULL);
566
567   xzero (*addr);
568
569   /* Form the request.  */
570   request = ftp_request ("LPSV", NULL);
571
572   /* And send it.  */
573   nwritten = fd_write (csock, request, strlen (request), -1);
574   if (nwritten < 0)
575     {
576       xfree (request);
577       return WRITEFAILED;
578     }
579   xfree (request);
580
581   /* Get the server response.  */
582   err = ftp_response (csock, &respline);
583   if (err != FTPOK)
584     return err;
585   if (*respline != '2')
586     {
587       xfree (respline);
588       return FTPNOPASV;
589     }  
590
591   /* Parse the response.  */
592   s = respline;
593   for (s += 4; *s && !ISDIGIT (*s); s++)
594     ;
595   if (!*s)
596     return FTPINVPASV;
597
598   /* First, get the address family */
599   af = 0;
600   for (; ISDIGIT (*s); s++)
601     af = (*s - '0') + 10 * af;
602
603   if (af != 4 && af != 6)
604     {
605       xfree (respline);
606       return FTPINVPASV;
607     }
608
609   if (!*s || *s++ != ',')
610     {
611       xfree (respline);
612       return FTPINVPASV;
613     }
614
615   /* Then, get the address length */
616   addrlen = 0;
617   for (; ISDIGIT (*s); s++)
618     addrlen = (*s - '0') + 10 * addrlen;
619
620   if (!*s || *s++ != ',')
621     {
622       xfree (respline);
623       return FTPINVPASV;
624     }
625
626   if (addrlen > 16)
627     {
628       xfree (respline);
629       return FTPINVPASV;
630     }
631
632   if ((af == 4 && addrlen != 4)
633       || (af == 6 && addrlen != 16))
634     {
635       xfree (respline);
636       return FTPINVPASV;
637     }
638
639   /* Now, we get the actual address */
640   for (i = 0; i < addrlen; i++)
641     {
642       tmp[i] = 0;
643       for (; ISDIGIT (*s); s++)
644         tmp[i] = (*s - '0') + 10 * tmp[i];
645       if (*s == ',')
646         s++;
647       else
648         {
649           xfree (respline);
650           return FTPINVPASV;
651         }
652     }
653
654   /* Now, get the port length */
655   portlen = 0;
656   for (; ISDIGIT (*s); s++)
657     portlen = (*s - '0') + 10 * portlen;
658
659   if (!*s || *s++ != ',')
660     {
661       xfree (respline);
662       return FTPINVPASV;
663     }
664
665   if (portlen > 2)
666     {
667       xfree (respline);
668       return FTPINVPASV;
669     }
670
671   /* Finally, we get the port number */
672   tmpprt[0] = 0;
673   for (; ISDIGIT (*s); s++)
674     tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
675
676   if (!*s || *s++ != ',')
677     {
678       xfree (respline);
679       return FTPINVPASV;
680     }
681
682   tmpprt[1] = 0;
683   for (; ISDIGIT (*s); s++)
684     tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
685
686   assert (s != NULL);
687
688   if (af == 4)
689     {
690       addr->family = AF_INET;
691       memcpy (IP_INADDR_DATA (addr), tmp, 4);
692       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
693       DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
694       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
695       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
696       DEBUGP (("*port is: %d\n", *port));
697     }
698   else
699     {
700       assert (af == 6);
701       addr->family = AF_INET6;
702       memcpy (IP_INADDR_DATA (addr), tmp, 16);
703       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
704       DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
705       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
706       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
707       DEBUGP (("*port is: %d\n", *port));
708     }
709
710   xfree (respline);
711   return FTPOK;
712 }
713
714 /* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
715    transfer.  Reads the response from server and parses it.  Reads the
716    host and port addresses and returns them.  */
717 uerr_t
718 ftp_epsv (int csock, ip_address *ip, int *port)
719 {
720   char *request, *respline, *start, delim, *s;
721   int nwritten, i;
722   uerr_t err;
723   int tport;
724
725   assert (ip != NULL);
726   assert (port != NULL);
727
728   /* IP already contains the IP address of the control connection's
729      peer, so we don't need to call socket_ip_address here.  */
730
731   /* Form the request.  */
732   /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
733   request = ftp_request ("EPSV", (ip->family == AF_INET ? "1" : "2"));
734
735   /* And send it.  */
736   nwritten = fd_write (csock, request, strlen (request), -1);
737   if (nwritten < 0)
738     {
739       xfree (request);
740       return WRITEFAILED;
741     }
742   xfree (request);
743
744   /* Get the server response.  */
745   err = ftp_response (csock, &respline);
746   if (err != FTPOK)
747     return err;
748   if (*respline != '2')
749     {
750       xfree (respline);
751       return FTPNOPASV;
752     }  
753
754   assert (respline != NULL);
755
756   DEBUGP(("respline is %s\n", respline));
757
758   /* Parse the response.  */
759   s = respline;
760
761   /* Skip the useless stuff and get what's inside the parentheses */
762   start = strchr (respline, '(');
763   if (start == NULL)
764     {
765       xfree (respline);
766       return FTPINVPASV;
767     }  
768
769   /* Skip the first two void fields */
770   s = start + 1;
771   delim = *s++;
772   if (delim < 33 || delim > 126)
773     {
774       xfree (respline);
775       return FTPINVPASV;
776     }  
777
778   for (i = 0; i < 2; i++)
779     {
780       if (*s++ != delim) 
781         {
782           xfree (respline);
783         return FTPINVPASV;
784         }  
785     }
786
787   /* Finally, get the port number */
788   tport = 0; 
789   for (i = 1; ISDIGIT (*s); s++) 
790     {
791       if (i > 5)
792         {
793           xfree (respline);
794           return FTPINVPASV;
795         }  
796       tport = (*s - '0') + 10 * tport;
797     }
798
799   /* Make sure that the response terminates correcty */
800   if (*s++ != delim)
801     {
802       xfree (respline);
803       return FTPINVPASV;
804     }  
805
806   if (*s++ != ')')
807     {
808       xfree (respline);
809       return FTPINVPASV;
810     }  
811
812   *port = tport;
813
814   xfree (respline);
815   return FTPOK;
816 }
817 #endif
818
819 /* Sends the TYPE request to the server.  */
820 uerr_t
821 ftp_type (int csock, int type)
822 {
823   char *request, *respline;
824   int nwritten;
825   uerr_t err;
826   char stype[2];
827
828   /* Construct argument.  */
829   stype[0] = type;
830   stype[1] = 0;
831   /* Send TYPE request.  */
832   request = ftp_request ("TYPE", stype);
833   nwritten = fd_write (csock, request, strlen (request), -1);
834   if (nwritten < 0)
835     {
836       xfree (request);
837       return WRITEFAILED;
838     }
839   xfree (request);
840   /* Get appropriate response.  */
841   err = ftp_response (csock, &respline);
842   if (err != FTPOK)
843     return err;
844   if (*respline != '2')
845     {
846       xfree (respline);
847       return FTPUNKNOWNTYPE;
848     }
849   xfree (respline);
850   /* All OK.  */
851   return FTPOK;
852 }
853
854 /* Changes the working directory by issuing a CWD command to the
855    server.  */
856 uerr_t
857 ftp_cwd (int csock, const char *dir)
858 {
859   char *request, *respline;
860   int nwritten;
861   uerr_t err;
862
863   /* Send CWD request.  */
864   request = ftp_request ("CWD", dir);
865   nwritten = fd_write (csock, request, strlen (request), -1);
866   if (nwritten < 0)
867     {
868       xfree (request);
869       return WRITEFAILED;
870     }
871   xfree (request);
872   /* Get appropriate response.  */
873   err = ftp_response (csock, &respline);
874   if (err != FTPOK)
875     return err;
876   if (*respline == '5')
877     {
878       xfree (respline);
879       return FTPNSFOD;
880     }
881   if (*respline != '2')
882     {
883       xfree (respline);
884       return FTPRERR;
885     }
886   xfree (respline);
887   /* All OK.  */
888   return FTPOK;
889 }
890
891 /* Sends REST command to the FTP server.  */
892 uerr_t
893 ftp_rest (int csock, wgint offset)
894 {
895   char *request, *respline;
896   int nwritten;
897   uerr_t err;
898
899   request = ftp_request ("REST", number_to_static_string (offset));
900   nwritten = fd_write (csock, request, strlen (request), -1);
901   if (nwritten < 0)
902     {
903       xfree (request);
904       return WRITEFAILED;
905     }
906   xfree (request);
907   /* Get appropriate response.  */
908   err = ftp_response (csock, &respline);
909   if (err != FTPOK)
910     return err;
911   if (*respline != '3')
912     {
913       xfree (respline);
914       return FTPRESTFAIL;
915     }
916   xfree (respline);
917   /* All OK.  */
918   return FTPOK;
919 }
920
921 /* Sends RETR command to the FTP server.  */
922 uerr_t
923 ftp_retr (int csock, const char *file)
924 {
925   char *request, *respline;
926   int nwritten;
927   uerr_t err;
928
929   /* Send RETR request.  */
930   request = ftp_request ("RETR", file);
931   nwritten = fd_write (csock, request, strlen (request), -1);
932   if (nwritten < 0)
933     {
934       xfree (request);
935       return WRITEFAILED;
936     }
937   xfree (request);
938   /* Get appropriate response.  */
939   err = ftp_response (csock, &respline);
940   if (err != FTPOK)
941     return err;
942   if (*respline == '5')
943     {
944       xfree (respline);
945       return FTPNSFOD;
946     }
947   if (*respline != '1')
948     {
949       xfree (respline);
950       return FTPRERR;
951     }
952   xfree (respline);
953   /* All OK.  */
954   return FTPOK;
955 }
956
957 /* Sends the LIST command to the server.  If FILE is NULL, send just
958    `LIST' (no space).  */
959 uerr_t
960 ftp_list (int csock, const char *file)
961 {
962   char *request, *respline;
963   int nwritten;
964   uerr_t err;
965
966   /* Send LIST request.  */
967   request = ftp_request ("LIST", file);
968   nwritten = fd_write (csock, request, strlen (request), -1);
969   if (nwritten < 0)
970     {
971       xfree (request);
972       return WRITEFAILED;
973     }
974   xfree (request);
975   /* Get appropriate respone.  */
976   err = ftp_response (csock, &respline);
977   if (err != FTPOK)
978     return err;
979   if (*respline == '5')
980     {
981       xfree (respline);
982       return FTPNSFOD;
983     }
984   if (*respline != '1')
985     {
986       xfree (respline);
987       return FTPRERR;
988     }
989   xfree (respline);
990   /* All OK.  */
991   return FTPOK;
992 }
993
994 /* Sends the SYST command to the server. */
995 uerr_t
996 ftp_syst (int csock, enum stype *server_type)
997 {
998   char *request, *respline;
999   int nwritten;
1000   uerr_t err;
1001
1002   /* Send SYST request.  */
1003   request = ftp_request ("SYST", NULL);
1004   nwritten = fd_write (csock, request, strlen (request), -1);
1005   if (nwritten < 0)
1006     {
1007       xfree (request);
1008       return WRITEFAILED;
1009     }
1010   xfree (request);
1011
1012   /* Get appropriate response.  */
1013   err = ftp_response (csock, &respline);
1014   if (err != FTPOK)
1015     return err;
1016   if (*respline == '5')
1017     {
1018       xfree (respline);
1019       return FTPSRVERR;
1020     }
1021
1022   /* Skip the number (215, but 200 (!!!) in case of VMS) */
1023   strtok (respline, " ");
1024
1025   /* Which system type has been reported (we are interested just in the
1026      first word of the server response)?  */
1027   request = strtok (NULL, " ");
1028
1029   if (!strcasecmp (request, "VMS"))
1030     *server_type = ST_VMS;
1031   else if (!strcasecmp (request, "UNIX"))
1032     *server_type = ST_UNIX;
1033   else if (!strcasecmp (request, "WINDOWS_NT")
1034            || !strcasecmp (request, "WINDOWS2000"))
1035     *server_type = ST_WINNT;
1036   else if (!strcasecmp (request, "MACOS"))
1037     *server_type = ST_MACOS;
1038   else if (!strcasecmp (request, "OS/400"))
1039     *server_type = ST_OS400;
1040   else
1041     *server_type = ST_OTHER;
1042
1043   xfree (respline);
1044   /* All OK.  */
1045   return FTPOK;
1046 }
1047
1048 /* Sends the PWD command to the server. */
1049 uerr_t
1050 ftp_pwd (int csock, char **pwd)
1051 {
1052   char *request, *respline;
1053   int nwritten;
1054   uerr_t err;
1055
1056   /* Send PWD request.  */
1057   request = ftp_request ("PWD", NULL);
1058   nwritten = fd_write (csock, request, strlen (request), -1);
1059   if (nwritten < 0)
1060     {
1061       xfree (request);
1062       return WRITEFAILED;
1063     }
1064   xfree (request);
1065   /* Get appropriate response.  */
1066   err = ftp_response (csock, &respline);
1067   if (err != FTPOK)
1068     return err;
1069   if (*respline == '5')
1070     {
1071     err:
1072       xfree (respline);
1073       return FTPSRVERR;
1074     }
1075
1076   /* Skip the number (257), leading citation mark, trailing citation mark
1077      and everything following it. */
1078   strtok (respline, "\"");
1079   request = strtok (NULL, "\"");
1080   if (!request)
1081     /* Treat the malformed response as an error, which the caller has
1082        to handle gracefully anyway.  */
1083     goto err;
1084
1085   /* Has the `pwd' been already allocated?  Free! */
1086   xfree_null (*pwd);
1087
1088   *pwd = xstrdup (request);
1089
1090   xfree (respline);
1091   /* All OK.  */
1092   return FTPOK;
1093 }
1094
1095 /* Sends the SIZE command to the server, and returns the value in 'size'.
1096  * If an error occurs, size is set to zero. */
1097 uerr_t
1098 ftp_size (int csock, const char *file, wgint *size)
1099 {
1100   char *request, *respline;
1101   int nwritten;
1102   uerr_t err;
1103
1104   /* Send PWD request.  */
1105   request = ftp_request ("SIZE", file);
1106   nwritten = fd_write (csock, request, strlen (request), -1);
1107   if (nwritten < 0)
1108     {
1109       xfree (request);
1110       *size = 0;
1111       return WRITEFAILED;
1112     }
1113   xfree (request);
1114   /* Get appropriate response.  */
1115   err = ftp_response (csock, &respline);
1116   if (err != FTPOK)
1117     {
1118       *size = 0;
1119       return err;
1120     }
1121   if (*respline == '5')
1122     {
1123       /* 
1124        * Probably means SIZE isn't supported on this server.
1125        * Error is nonfatal since SIZE isn't in RFC 959 
1126        */
1127       xfree (respline);
1128       *size = 0;
1129       return FTPOK;
1130     }
1131
1132   errno = 0;
1133   *size = str_to_wgint (respline + 4, NULL, 10);
1134   if (errno)
1135     {
1136       /* 
1137        * Couldn't parse the response for some reason.  On the (few)
1138        * tests I've done, the response is 213 <SIZE> with nothing else -
1139        * maybe something a bit more resilient is necessary.  It's not a
1140        * fatal error, however.
1141        */
1142       xfree (respline);
1143       *size = 0;
1144       return FTPOK;
1145     }
1146
1147   xfree (respline);
1148   /* All OK.  */
1149   return FTPOK;
1150 }
1151
1152 /* If URL's params are of the form "type=X", return character X.
1153    Otherwise, return 'I' (the default type).  */
1154 char
1155 ftp_process_type (const char *params)
1156 {
1157   if (params
1158       && 0 == strncasecmp (params, "type=", 5)
1159       && params[5] != '\0')
1160     return TOUPPER (params[5]);
1161   else
1162     return 'I';
1163 }