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