]> sjero.net Git - wget/blob - src/ftp-basic.c
[svn] Renamed "pretty_print_address" to "print_address".
[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   /* buf must contain the argument of EPRT (of the form |af|addr|port|). 
423    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
424    * 1 char for af (1-2) and 5 chars for port (0-65535) */
425   assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5); 
426
427   /* Construct the argument of EPRT (of the form |af|addr|port|). */
428   afnum = (addr->type == IPV4_ADDRESS ? 1 : 2);
429   snprintf (buf, buflen, "|%d|%s|%d|", afnum, print_address (addr), port);
430   buf[buflen - 1] = '\0';
431 }
432
433 /* Bind a port and send the appropriate PORT command to the FTP
434    server.  Use acceptport after RETR, to get the socket of data
435    connection.  */
436 uerr_t
437 ftp_eprt (int csock, int *local_sock)
438 {
439   uerr_t err;
440   char *request, *respline;
441   ip_address addr;
442   int nwritten;
443   int port;
444   /* Must contain the argument of EPRT (of the form |af|addr|port|). 
445    * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr  
446    * 1 char for af (1-2) and 5 chars for port (0-65535) */
447   char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1];
448
449   /* Get the address of this side of the connection. */
450   if (!socket_ip_address (csock, &addr, ENDPOINT_LOCAL))
451     return FTPSYSERR;
452
453   /* Setting port to 0 lets the system choose a free port.  */
454   port = 0;
455
456   /* Bind the port.  */
457   *local_sock = bind_local (&addr, &port);
458   if (*local_sock < 0)
459     return FTPSYSERR;
460
461   /* Construct the argument of EPRT (of the form |af|addr|port|). */
462   ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes));
463
464   /* Send PORT request.  */
465   request = ftp_request ("EPRT", bytes);
466   nwritten = fd_write (csock, request, strlen (request), -1);
467   if (nwritten < 0)
468     {
469       xfree (request);
470       fd_close (*local_sock);
471       return WRITEFAILED;
472     }
473   xfree (request);
474   /* Get appropriate response.  */
475   err = ftp_response (csock, &respline);
476   if (err != FTPOK)
477     {
478       fd_close (*local_sock);
479       return err;
480     }
481   if (*respline != '2')
482     {
483       xfree (respline);
484       fd_close (*local_sock);
485       return FTPPORTERR;
486     }
487   xfree (respline);
488   return FTPOK;
489 }
490 #endif
491
492 /* Similar to ftp_port, but uses `PASV' to initiate the passive FTP
493    transfer.  Reads the response from server and parses it.  Reads the
494    host and port addresses and returns them.  */
495 uerr_t
496 ftp_pasv (int csock, ip_address *addr, int *port)
497 {
498   char *request, *respline, *s;
499   int nwritten, i;
500   uerr_t err;
501   unsigned char tmp[6];
502
503   assert (addr != NULL);
504   assert (port != NULL);
505
506   xzero (*addr);
507
508   /* Form the request.  */
509   request = ftp_request ("PASV", NULL);
510   /* And send it.  */
511   nwritten = fd_write (csock, request, strlen (request), -1);
512   if (nwritten < 0)
513     {
514       xfree (request);
515       return WRITEFAILED;
516     }
517   xfree (request);
518   /* Get the server response.  */
519   err = ftp_response (csock, &respline);
520   if (err != FTPOK)
521     return err;
522   if (*respline != '2')
523     {
524       xfree (respline);
525       return FTPNOPASV;
526     }
527   /* Parse the request.  */
528   s = respline;
529   for (s += 4; *s && !ISDIGIT (*s); s++);
530   if (!*s)
531     return FTPINVPASV;
532   for (i = 0; i < 6; i++)
533     {
534       tmp[i] = 0;
535       for (; ISDIGIT (*s); s++)
536         tmp[i] = (*s - '0') + 10 * tmp[i];
537       if (*s == ',')
538         s++;
539       else if (i < 5)
540         {
541           /* When on the last number, anything can be a terminator.  */
542           xfree (respline);
543           return FTPINVPASV;
544         }
545     }
546   xfree (respline);
547
548   addr->type = IPV4_ADDRESS;
549   memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
550   *port = ((tmp[4] << 8) & 0xff00) + tmp[5];
551
552   return FTPOK;
553 }
554
555 #ifdef ENABLE_IPV6
556 /* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP
557    transfer.  Reads the response from server and parses it.  Reads the
558    host and port addresses and returns them.  */
559 uerr_t
560 ftp_lpsv (int csock, ip_address *addr, int *port)
561 {
562   char *request, *respline, *s;
563   int nwritten, i, af, addrlen, portlen;
564   uerr_t err;
565   unsigned char tmp[16];
566   unsigned char tmpprt[2];
567
568   assert (addr != NULL);
569   assert (port != NULL);
570
571   xzero (*addr);
572
573   /* Form the request.  */
574   request = ftp_request ("LPSV", NULL);
575
576   /* And send it.  */
577   nwritten = fd_write (csock, request, strlen (request), -1);
578   if (nwritten < 0)
579     {
580       xfree (request);
581       return WRITEFAILED;
582     }
583   xfree (request);
584
585   /* Get the server response.  */
586   err = ftp_response (csock, &respline);
587   if (err != FTPOK)
588     return err;
589   if (*respline != '2')
590     {
591       xfree (respline);
592       return FTPNOPASV;
593     }  
594
595   /* Parse the response.  */
596   s = respline;
597   for (s += 4; *s && !ISDIGIT (*s); s++);
598   if (!*s)
599     return FTPINVPASV;
600
601   /* First, get the address family */
602   af = 0;
603   for (; ISDIGIT (*s); s++)
604     af = (*s - '0') + 10 * af;
605
606   if (af != 4 && af != 6)
607     {
608       xfree (respline);
609       return FTPINVPASV;
610     }
611
612   if (!*s || *s++ != ',')
613     {
614       xfree (respline);
615       return FTPINVPASV;
616     }
617
618   /* Then, get the address length */
619   addrlen = 0;
620   for (; ISDIGIT (*s); s++)
621     addrlen = (*s - '0') + 10 * addrlen;
622
623   if (!*s || *s++ != ',')
624     {
625       xfree (respline);
626       return FTPINVPASV;
627     }
628
629   if (addrlen > 16)
630     {
631       xfree (respline);
632       return FTPINVPASV;
633     }
634
635   if ((af == 4 && addrlen != 4)
636       || (af == 6 && addrlen != 16))
637     {
638       xfree (respline);
639       return FTPINVPASV;
640     }
641
642   /* Now, we get the actual address */
643   for (i = 0; i < addrlen; i++)
644     {
645       tmp[i] = 0;
646       for (; ISDIGIT (*s); s++)
647         tmp[i] = (*s - '0') + 10 * tmp[i];
648       if (*s == ',')
649         s++;
650       else
651         {
652           xfree (respline);
653           return FTPINVPASV;
654         }
655     }
656
657   /* Now, get the port length */
658   portlen = 0;
659   for (; ISDIGIT (*s); s++)
660     portlen = (*s - '0') + 10 * portlen;
661
662   if (!*s || *s++ != ',')
663     {
664       xfree (respline);
665       return FTPINVPASV;
666     }
667
668   if (portlen > 2)
669     {
670       xfree (respline);
671       return FTPINVPASV;
672     }
673
674   /* Finally, we get the port number */
675   tmpprt[0] = 0;
676   for (; ISDIGIT (*s); s++)
677     tmpprt[0] = (*s - '0') + 10 * tmpprt[0];
678
679   if (!*s || *s++ != ',')
680     {
681       xfree (respline);
682       return FTPINVPASV;
683     }
684
685   tmpprt[1] = 0;
686   for (; ISDIGIT (*s); s++)
687     tmpprt[1] = (*s - '0') + 10 * tmpprt[1];
688
689   assert (s != NULL);
690
691   if (af == 4)
692     {
693       addr->type = IPV4_ADDRESS;
694       memcpy (ADDRESS_IPV4_DATA (addr), tmp, 4);
695       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
696       DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
697       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
698       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
699       DEBUGP (("*port is: %d\n", *port));
700     }
701   else
702     {
703       assert (af == 6);
704       addr->type = IPV6_ADDRESS;
705       memcpy (ADDRESS_IPV6_DATA (addr), tmp, 16);
706       *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1];
707       DEBUGP (("lpsv addr is: %s\n", print_address(addr)));
708       DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0]));
709       DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1]));
710       DEBUGP (("*port is: %d\n", *port));
711     }
712
713   xfree (respline);
714   return FTPOK;
715 }
716
717 /* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP
718    transfer.  Reads the response from server and parses it.  Reads the
719    host and port addresses and returns them.  */
720 uerr_t
721 ftp_epsv (int csock, ip_address *ip, int *port)
722 {
723   char *request, *respline, *start, delim, *s;
724   int nwritten, i;
725   uerr_t err;
726   int tport;
727
728   assert (ip != NULL);
729   assert (port != NULL);
730
731   /* IP already contains the IP address of the control connection's
732      peer, so we don't need to call socket_ip_address here.  */
733
734   /* Form the request.  */
735   /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */
736   request = ftp_request ("EPSV", (ip->type == IPV4_ADDRESS ? "1" : "2"));
737
738   /* And send it.  */
739   nwritten = fd_write (csock, request, strlen (request), -1);
740   if (nwritten < 0)
741     {
742       xfree (request);
743       return WRITEFAILED;
744     }
745   xfree (request);
746
747   /* Get the server response.  */
748   err = ftp_response (csock, &respline);
749   if (err != FTPOK)
750     return err;
751   if (*respline != '2')
752     {
753       xfree (respline);
754       return FTPNOPASV;
755     }  
756
757   assert (respline != NULL);
758
759   DEBUGP(("respline is %s\n", respline));
760
761   /* Parse the response.  */
762   s = respline;
763
764   /* Skip the useless stuff and get what's inside the parentheses */
765   start = strchr (respline, '(');
766   if (start == NULL)
767     {
768       xfree (respline);
769       return FTPINVPASV;
770     }  
771
772   /* Skip the first two void fields */
773   s = start + 1;
774   delim = *s++;
775   if (delim < 33 || delim > 126)
776     {
777       xfree (respline);
778       return FTPINVPASV;
779     }  
780
781   for (i = 0; i < 2; i++)
782     {
783       if (*s++ != delim) 
784         {
785           xfree (respline);
786         return FTPINVPASV;
787         }  
788     }
789
790   /* Finally, get the port number */
791   tport = 0; 
792   for (i = 1; ISDIGIT (*s); s++) 
793     {
794       if (i > 5)
795         {
796           xfree (respline);
797           return FTPINVPASV;
798         }  
799       tport = (*s - '0') + 10 * tport;
800     }
801
802   /* Make sure that the response terminates correcty */
803   if (*s++ != delim)
804     {
805       xfree (respline);
806       return FTPINVPASV;
807     }  
808
809   if (*s++ != ')')
810     {
811       xfree (respline);
812       return FTPINVPASV;
813     }  
814
815   *port = tport;
816
817   xfree (respline);
818   return FTPOK;
819 }
820 #endif
821
822 /* Sends the TYPE request to the server.  */
823 uerr_t
824 ftp_type (int csock, int type)
825 {
826   char *request, *respline;
827   int nwritten;
828   uerr_t err;
829   char stype[2];
830
831   /* Construct argument.  */
832   stype[0] = type;
833   stype[1] = 0;
834   /* Send TYPE request.  */
835   request = ftp_request ("TYPE", stype);
836   nwritten = fd_write (csock, request, strlen (request), -1);
837   if (nwritten < 0)
838     {
839       xfree (request);
840       return WRITEFAILED;
841     }
842   xfree (request);
843   /* Get appropriate response.  */
844   err = ftp_response (csock, &respline);
845   if (err != FTPOK)
846     return err;
847   if (*respline != '2')
848     {
849       xfree (respline);
850       return FTPUNKNOWNTYPE;
851     }
852   xfree (respline);
853   /* All OK.  */
854   return FTPOK;
855 }
856
857 /* Changes the working directory by issuing a CWD command to the
858    server.  */
859 uerr_t
860 ftp_cwd (int csock, const char *dir)
861 {
862   char *request, *respline;
863   int nwritten;
864   uerr_t err;
865
866   /* Send CWD request.  */
867   request = ftp_request ("CWD", dir);
868   nwritten = fd_write (csock, request, strlen (request), -1);
869   if (nwritten < 0)
870     {
871       xfree (request);
872       return WRITEFAILED;
873     }
874   xfree (request);
875   /* Get appropriate response.  */
876   err = ftp_response (csock, &respline);
877   if (err != FTPOK)
878     return err;
879   if (*respline == '5')
880     {
881       xfree (respline);
882       return FTPNSFOD;
883     }
884   if (*respline != '2')
885     {
886       xfree (respline);
887       return FTPRERR;
888     }
889   xfree (respline);
890   /* All OK.  */
891   return FTPOK;
892 }
893
894 /* Sends REST command to the FTP server.  */
895 uerr_t
896 ftp_rest (int csock, wgint offset)
897 {
898   char *request, *respline;
899   int nwritten;
900   uerr_t err;
901
902   request = ftp_request ("REST", number_to_static_string (offset));
903   nwritten = fd_write (csock, request, strlen (request), -1);
904   if (nwritten < 0)
905     {
906       xfree (request);
907       return WRITEFAILED;
908     }
909   xfree (request);
910   /* Get appropriate response.  */
911   err = ftp_response (csock, &respline);
912   if (err != FTPOK)
913     return err;
914   if (*respline != '3')
915     {
916       xfree (respline);
917       return FTPRESTFAIL;
918     }
919   xfree (respline);
920   /* All OK.  */
921   return FTPOK;
922 }
923
924 /* Sends RETR command to the FTP server.  */
925 uerr_t
926 ftp_retr (int csock, const char *file)
927 {
928   char *request, *respline;
929   int nwritten;
930   uerr_t err;
931
932   /* Send RETR request.  */
933   request = ftp_request ("RETR", file);
934   nwritten = fd_write (csock, request, strlen (request), -1);
935   if (nwritten < 0)
936     {
937       xfree (request);
938       return WRITEFAILED;
939     }
940   xfree (request);
941   /* Get appropriate response.  */
942   err = ftp_response (csock, &respline);
943   if (err != FTPOK)
944     return err;
945   if (*respline == '5')
946     {
947       xfree (respline);
948       return FTPNSFOD;
949     }
950   if (*respline != '1')
951     {
952       xfree (respline);
953       return FTPRERR;
954     }
955   xfree (respline);
956   /* All OK.  */
957   return FTPOK;
958 }
959
960 /* Sends the LIST command to the server.  If FILE is NULL, send just
961    `LIST' (no space).  */
962 uerr_t
963 ftp_list (int csock, const char *file)
964 {
965   char *request, *respline;
966   int nwritten;
967   uerr_t err;
968
969   /* Send LIST request.  */
970   request = ftp_request ("LIST", file);
971   nwritten = fd_write (csock, request, strlen (request), -1);
972   if (nwritten < 0)
973     {
974       xfree (request);
975       return WRITEFAILED;
976     }
977   xfree (request);
978   /* Get appropriate respone.  */
979   err = ftp_response (csock, &respline);
980   if (err != FTPOK)
981     return err;
982   if (*respline == '5')
983     {
984       xfree (respline);
985       return FTPNSFOD;
986     }
987   if (*respline != '1')
988     {
989       xfree (respline);
990       return FTPRERR;
991     }
992   xfree (respline);
993   /* All OK.  */
994   return FTPOK;
995 }
996
997 /* Sends the SYST command to the server. */
998 uerr_t
999 ftp_syst (int csock, enum stype *server_type)
1000 {
1001   char *request, *respline;
1002   int nwritten;
1003   uerr_t err;
1004
1005   /* Send SYST request.  */
1006   request = ftp_request ("SYST", NULL);
1007   nwritten = fd_write (csock, request, strlen (request), -1);
1008   if (nwritten < 0)
1009     {
1010       xfree (request);
1011       return WRITEFAILED;
1012     }
1013   xfree (request);
1014
1015   /* Get appropriate response.  */
1016   err = ftp_response (csock, &respline);
1017   if (err != FTPOK)
1018     return err;
1019   if (*respline == '5')
1020     {
1021       xfree (respline);
1022       return FTPSRVERR;
1023     }
1024
1025   /* Skip the number (215, but 200 (!!!) in case of VMS) */
1026   strtok (respline, " ");
1027
1028   /* Which system type has been reported (we are interested just in the
1029      first word of the server response)?  */
1030   request = strtok (NULL, " ");
1031
1032   if (!strcasecmp (request, "VMS"))
1033     *server_type = ST_VMS;
1034   else if (!strcasecmp (request, "UNIX"))
1035     *server_type = ST_UNIX;
1036   else if (!strcasecmp (request, "WINDOWS_NT")
1037            || !strcasecmp (request, "WINDOWS2000"))
1038     *server_type = ST_WINNT;
1039   else if (!strcasecmp (request, "MACOS"))
1040     *server_type = ST_MACOS;
1041   else if (!strcasecmp (request, "OS/400"))
1042     *server_type = ST_OS400;
1043   else
1044     *server_type = ST_OTHER;
1045
1046   xfree (respline);
1047   /* All OK.  */
1048   return FTPOK;
1049 }
1050
1051 /* Sends the PWD command to the server. */
1052 uerr_t
1053 ftp_pwd (int csock, char **pwd)
1054 {
1055   char *request, *respline;
1056   int nwritten;
1057   uerr_t err;
1058
1059   /* Send PWD request.  */
1060   request = ftp_request ("PWD", NULL);
1061   nwritten = fd_write (csock, request, strlen (request), -1);
1062   if (nwritten < 0)
1063     {
1064       xfree (request);
1065       return WRITEFAILED;
1066     }
1067   xfree (request);
1068   /* Get appropriate response.  */
1069   err = ftp_response (csock, &respline);
1070   if (err != FTPOK)
1071     return err;
1072   if (*respline == '5')
1073     {
1074     err:
1075       xfree (respline);
1076       return FTPSRVERR;
1077     }
1078
1079   /* Skip the number (257), leading citation mark, trailing citation mark
1080      and everything following it. */
1081   strtok (respline, "\"");
1082   request = strtok (NULL, "\"");
1083   if (!request)
1084     /* Treat the malformed response as an error, which the caller has
1085        to handle gracefully anyway.  */
1086     goto err;
1087
1088   /* Has the `pwd' been already allocated?  Free! */
1089   xfree_null (*pwd);
1090
1091   *pwd = xstrdup (request);
1092
1093   xfree (respline);
1094   /* All OK.  */
1095   return FTPOK;
1096 }
1097
1098 /* Sends the SIZE command to the server, and returns the value in 'size'.
1099  * If an error occurs, size is set to zero. */
1100 uerr_t
1101 ftp_size (int csock, const char *file, wgint *size)
1102 {
1103   char *request, *respline;
1104   int nwritten;
1105   uerr_t err;
1106
1107   /* Send PWD request.  */
1108   request = ftp_request ("SIZE", file);
1109   nwritten = fd_write (csock, request, strlen (request), -1);
1110   if (nwritten < 0)
1111     {
1112       xfree (request);
1113       *size = 0;
1114       return WRITEFAILED;
1115     }
1116   xfree (request);
1117   /* Get appropriate response.  */
1118   err = ftp_response (csock, &respline);
1119   if (err != FTPOK)
1120     {
1121       *size = 0;
1122       return err;
1123     }
1124   if (*respline == '5')
1125     {
1126       /* 
1127        * Probably means SIZE isn't supported on this server.
1128        * Error is nonfatal since SIZE isn't in RFC 959 
1129        */
1130       xfree (respline);
1131       *size = 0;
1132       return FTPOK;
1133     }
1134
1135   errno = 0;
1136   *size = str_to_wgint (respline + 4, NULL, 10);
1137   if (errno)
1138     {
1139       /* 
1140        * Couldn't parse the response for some reason.  On the (few)
1141        * tests I've done, the response is 213 <SIZE> with nothing else -
1142        * maybe something a bit more resilient is necessary.  It's not a
1143        * fatal error, however.
1144        */
1145       xfree (respline);
1146       *size = 0;
1147       return FTPOK;
1148     }
1149
1150   xfree (respline);
1151   /* All OK.  */
1152   return FTPOK;
1153 }
1154
1155 /* If URL's params are of the form "type=X", return character X.
1156    Otherwise, return 'I' (the default type).  */
1157 char
1158 ftp_process_type (const char *params)
1159 {
1160   if (params
1161       && 0 == strncasecmp (params, "type=", 5)
1162       && params[5] != '\0')
1163     return TOUPPER (params[5]);
1164   else
1165     return 'I';
1166 }