]> sjero.net Git - wget/blob - src/ftp-basic.c
6dff629aa4f20f69dfa606c1f6b8536b809b04df
[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 #include <config.h>
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <errno.h>
25
26 #ifdef HAVE_STRING_H
27 # include <string.h>
28 #else
29 # include <strings.h>
30 #endif
31 #ifdef HAVE_UNISTD_H
32 # include <unistd.h>
33 #endif
34 #include <sys/types.h>
35
36 /* For inet_ntop. */
37 #ifndef WINDOWS
38 #include <sys/socket.h>
39 #include <netinet/in.h>
40 #include <arpa/inet.h>
41 #endif
42
43 #ifdef WINDOWS
44 # include <winsock.h>
45 #endif
46
47 #include "wget.h"
48 #include "utils.h"
49 #include "rbuf.h"
50 #include "connect.h"
51 #include "host.h"
52 #include "ftp.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 (struct rbuf *rbuf, char **line)
63 {
64   int i;
65   int bufsize = 40;
66
67   *line = (char *)xmalloc (bufsize);
68   do
69     {
70       for (i = 0; 1; i++)
71         {
72           int res;
73           if (i > bufsize - 1)
74             *line = (char *)xrealloc (*line, (bufsize <<= 1));
75           res = RBUF_READCHAR (rbuf, *line + i);
76           /* RES is number of bytes read.  */
77           if (res == 1)
78             {
79               if ((*line)[i] == '\n')
80                 {
81                   (*line)[i] = '\0';
82                   /* Get rid of \r.  */
83                   if (i > 0 && (*line)[i - 1] == '\r')
84                     (*line)[i - 1] = '\0';
85                   break;
86                 }
87             }
88           else
89             return FTPRERR;
90         }
91       if (opt.server_response)
92         logprintf (LOG_ALWAYS, "%s\n", *line);
93       else
94         DEBUGP (("%s\n", *line));
95     }
96   while (!(i >= 3 && ISDIGIT (**line) && ISDIGIT ((*line)[1]) &&
97            ISDIGIT ((*line)[2]) && (*line)[3] == ' '));
98   strncpy (ftp_last_respline, *line, sizeof (ftp_last_respline));
99   ftp_last_respline[sizeof (ftp_last_respline) - 1] = '\0';
100   return FTPOK;
101 }
102
103 /* Returns the malloc-ed FTP request, ending with <CR><LF>, printing
104    it if printing is required.  If VALUE is NULL, just use
105    command<CR><LF>.  */
106 static char *
107 ftp_request (const char *command, const char *value)
108 {
109   char *res = (char *)xmalloc (strlen (command)
110                                + (value ? (1 + strlen (value)) : 0)
111                                + 2 + 1);
112   sprintf (res, "%s%s%s\r\n", command, value ? " " : "", value ? value : "");
113   if (opt.server_response)
114     {
115       /* Hack: don't print out password.  */
116       if (strncmp (res, "PASS", 4) != 0)
117         logprintf (LOG_ALWAYS, "--> %s\n", res);
118       else
119         logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n");
120     }
121   else
122     DEBUGP (("\n--> %s\n", res));
123   return res;
124 }
125
126 #ifdef USE_OPIE
127 const char *calculate_skey_response PARAMS ((int, const char *, const char *));
128 #endif
129
130 /* Sends the USER and PASS commands to the server, to control
131    connection socket csock.  */
132 uerr_t
133 ftp_login (struct rbuf *rbuf, const char *acc, const char *pass)
134 {
135   uerr_t err;
136   char *request, *respline;
137   int nwritten;
138
139   /* Get greeting.  */
140   err = ftp_response (rbuf, &respline);
141   if (err != FTPOK)
142     {
143       xfree (respline);
144       return err;
145     }
146   if (*respline != '2')
147     {
148       xfree (respline);
149       return FTPSRVERR;
150     }
151   xfree (respline);
152   /* Send USER username.  */
153   request = ftp_request ("USER", acc);
154   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
155   if (nwritten < 0)
156     {
157       xfree (request);
158       return WRITEFAILED;
159     }
160   xfree (request);
161   /* Get appropriate response.  */
162   err = ftp_response (rbuf, &respline);
163   if (err != FTPOK)
164     {
165       xfree (respline);
166       return err;
167     }
168   /* An unprobable possibility of logging without a password.  */
169   if (*respline == '2')
170     {
171       xfree (respline);
172       return FTPOK;
173     }
174   /* Else, only response 3 is appropriate.  */
175   if (*respline != '3')
176     {
177       xfree (respline);
178       return FTPLOGREFUSED;
179     }
180 #ifdef USE_OPIE
181   {
182     static const char *skey_head[] = {
183       "331 s/key ",
184       "331 opiekey "
185     };
186     int i;
187
188     for (i = 0; i < ARRAY_SIZE (skey_head); i++)
189       {
190         if (strncasecmp (skey_head[i], respline, strlen (skey_head[i])) == 0)
191           break;
192       }
193     if (i < ARRAY_SIZE (skey_head))
194       {
195         const char *cp;
196         int skey_sequence = 0;
197
198         for (cp = respline + strlen (skey_head[i]);
199              '0' <= *cp && *cp <= '9';
200              cp++)
201           {
202             skey_sequence = skey_sequence * 10 + *cp - '0';
203           }
204         if (*cp == ' ')
205           cp++;
206         else
207           {
208           bad:
209             xfree (respline);
210             return FTPLOGREFUSED;
211           }
212         if ((cp = calculate_skey_response (skey_sequence, cp, pass)) == 0)
213           goto bad;
214         pass = cp;
215       }
216   }
217 #endif /* USE_OPIE */
218   xfree (respline);
219   /* Send PASS password.  */
220   request = ftp_request ("PASS", pass);
221   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
222   if (nwritten < 0)
223     {
224       xfree (request);
225       return WRITEFAILED;
226     }
227   xfree (request);
228   /* Get appropriate response.  */
229   err = ftp_response (rbuf, &respline);
230   if (err != FTPOK)
231     {
232       xfree (respline);
233       return err;
234     }
235   if (*respline != '2')
236     {
237       xfree (respline);
238       return FTPLOGINC;
239     }
240   xfree (respline);
241   /* All OK.  */
242   return FTPOK;
243 }
244
245 #ifdef INET6
246 uerr_t
247 ftp_eprt (struct rbuf *rbuf)
248 {
249   uerr_t err;
250
251   char *request, *respline;
252   ip_address in_addr;
253   unsigned short port;
254
255   char ipv6 [8 * (4 * 3 + 3) + 8];
256   char *bytes;
257
258   /* Setting port to 0 lets the system choose a free port.  */
259   port = 0;
260   err = bindport (&port, ip_default_family);
261   if (err != BINDOK)    /* Bind the port.  */
262     return err;
263
264   /* Get the address of this side of the connection.  */
265   if (!conaddr (RBUF_FD (rbuf), &in_addr))
266     /* Huh?  This is not BINDERR! */
267     return BINDERR;
268   inet_ntop (AF_INET6, &in_addr, ipv6, sizeof (ipv6));
269
270   /* Construct the argument of EPRT (of the form |2|IPv6.ascii|PORT.ascii|). */
271   bytes = alloca (3 + strlen (ipv6) + 1 + numdigit (port) + 1 + 1);
272   sprintf (bytes, "|2|%s|%u|", ipv6, port);
273   /* Send PORT request.  */
274   request = ftp_request ("EPRT", bytes);
275   if (0 > iwrite (RBUF_FD (rbuf), request, strlen (request)))
276     {
277       closeport (port);
278       xfree (request);
279       return WRITEFAILED;
280     }
281   xfree (request);
282   /* Get appropriate response.  */
283   err = ftp_response (rbuf, &respline);
284   if (err != FTPOK)
285     {
286       closeport (port);
287       xfree (respline);
288       return err;
289     }
290   if (*respline != '2')
291     {
292       closeport (port);
293       xfree (respline);
294       return FTPPORTERR;
295     }
296   xfree (respline);
297   return FTPOK;
298 }
299 #endif
300
301 /* Bind a port and send the appropriate PORT command to the FTP
302    server.  Use acceptport after RETR, to get the socket of data
303    connection.  */
304 uerr_t
305 ftp_port (struct rbuf *rbuf)
306 {
307   uerr_t err;
308   char *request, *respline;
309   char bytes[6 * 4 +1];
310
311   ip_address in_addr;
312   ip4_address in_addr_4;
313   unsigned char *in_addr4_ptr = (unsigned char *)&in_addr_4;
314
315   int nwritten;
316   unsigned short port;
317 #ifdef INET6
318   /*
319     Only try the Extented Version if we actually use IPv6
320   */
321   if (ip_default_family == AF_INET6)
322     {
323       err = ftp_eprt (rbuf);
324       if (err == FTPOK)
325         return err;
326     }
327 #endif
328   /* Setting port to 0 lets the system choose a free port.  */
329   port = 0;
330
331   err = bindport (&port, AF_INET);
332   if (err != BINDOK)
333     return err;
334
335   /* Get the address of this side of the connection and convert it
336      (back) to IPv4.  */
337   if (!conaddr (RBUF_FD (rbuf), &in_addr))
338     /* Huh?  This is not BINDERR! */
339     return BINDERR;
340   if (!map_ip_to_ipv4 (&in_addr, &in_addr_4))
341     return BINDERR;
342
343   /* Construct the argument of PORT (of the form a,b,c,d,e,f).  Port
344      is unsigned short so (unsigned) (port & 0xff000) >> 8 is the same
345      like port >> 8
346    */
347   sprintf (bytes, "%d,%d,%d,%d,%d,%d",
348            in_addr4_ptr[0], in_addr4_ptr[1], in_addr4_ptr[2], in_addr4_ptr[3],
349            port >> 8, port & 0xff);
350   /* Send PORT request.  */
351   request = ftp_request ("PORT", bytes);
352   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
353   if (nwritten < 0)
354     {
355       xfree (request);
356       return WRITEFAILED;
357     }
358   xfree (request);
359   /* Get appropriate response.  */
360   err = ftp_response (rbuf, &respline);
361   if (err != FTPOK)
362     {
363       xfree (respline);
364       return err;
365     }
366   if (*respline != '2')
367     {
368       xfree (respline);
369       return FTPPORTERR;
370     }
371   xfree (respline);
372   return FTPOK;
373 }
374
375 #ifdef INET6
376 uerr_t
377 ftp_epsv (struct rbuf *rbuf, ip_address *addr, unsigned short *port, 
378           char *typ)
379 {
380   int err;
381   char *s, *respline;
382   char *request = ftp_request ("EPSV", typ);
383   if (0 > iwrite (RBUF_FD (rbuf), request, strlen (request)))
384     {
385       xfree (request);
386       return WRITEFAILED;
387     }
388   /* Get the server response.  */
389   err = ftp_response (rbuf, &respline);
390   if (err != FTPOK)
391     {
392       xfree (respline);
393       return err;
394     }
395   if (*respline != '2')
396     {
397       xfree (respline);
398       return FTPNOPASV;
399     }
400   /* Parse the request.  */
401   s = respline;
402   /* respline::=229 Entering Extended Passive Mode (|||6446|) */
403   for (s += 4; *s && !ISDIGIT (*s); s++);
404   if (!*s)
405     return FTPINVPASV;
406   *port=0; 
407   for (; ISDIGIT (*s); s++) 
408     *port = (*s - '0') + 10 * (*port);
409   xfree (respline);
410   /* Now we have the port but we need the IPv6 :-( */
411   {
412     wget_sockaddr remote;
413     int len = sizeof (remote);
414     struct sockaddr_in *ipv4_sock = ( struct sockaddr_in *)&remote;
415     getpeername (RBUF_FD (rbuf), (struct sockaddr*)&remote, &len);
416     switch(remote.sa.sa_family)
417       {
418         case AF_INET6:
419           memcpy (addr, &remote.sin6.sin6_addr, 16);
420           break;
421         case AF_INET:  
422           map_ipv4_to_ip ((ip4_address *)&ipv4_sock->sin_addr, addr);
423           break;
424         default:
425           abort();
426           return FTPINVPASV;
427           /* realy bad */
428       }
429   }
430   return FTPOK;
431 }
432 #endif
433
434
435 /* Similar to ftp_port, but uses `PASV' to initiate the passive FTP
436    transfer.  Reads the response from server and parses it.  Reads the
437    host and port addresses and returns them.  */
438 uerr_t
439 ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port)
440 {
441   char *request, *respline, *s;
442   int nwritten, i;
443   uerr_t err;
444   unsigned char addr4[4];
445
446 #ifdef INET6
447   if (ip_default_family == AF_INET6) 
448     {
449       err = ftp_epsv (rbuf, addr, port, "2");   /* try IPv6 with EPSV */
450       if (FTPOK == err) 
451         return FTPOK;
452       err = ftp_epsv (rbuf, addr, port, "1");   /* try IPv4 with EPSV */
453       if (FTPOK == err) 
454         return FTPOK;
455     }
456 #endif  
457   /* Form the request.  */
458   request = ftp_request ("PASV", NULL);
459   /* And send it.  */
460   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
461   if (nwritten < 0)
462     {
463       xfree (request);
464       return WRITEFAILED;
465     }
466   xfree (request);
467   /* Get the server response.  */
468   err = ftp_response (rbuf, &respline);
469   if (err != FTPOK)
470     {
471       xfree (respline);
472       return err;
473     }
474   if (*respline != '2')
475     {
476       xfree (respline);
477       return FTPNOPASV;
478     }
479   /* Parse the request.  */
480   /* respline::=227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).  */
481   s = respline;
482   for (s += 4; *s && !ISDIGIT (*s); s++);
483   if (!*s)
484     return FTPINVPASV;
485   for (i = 0; i < 4; i++)
486     {
487       addr4[i] = 0;
488       for (; ISDIGIT (*s); s++)
489         addr4[i] = (*s - '0') + 10 * addr4[i];
490       if (*s == ',')
491         s++;
492       else
493         {
494           xfree (respline);
495           return FTPINVPASV;
496         }
497     }
498
499   /* Eventually make an IPv4 in IPv6 adress if needed */
500   map_ipv4_to_ip ((ip4_address *)addr4, addr);
501
502   *port=0;
503   for (; ISDIGIT (*s); s++)
504     *port = (*s - '0') + 10 * (*port);
505   if (*s == ',') 
506     s++;
507   else
508     {
509       xfree (respline);
510       return FTPINVPASV;
511     }
512
513   {
514     unsigned short port2 = 0; 
515     for (; ISDIGIT (*s); s++) 
516       port2 = (*s - '0') + 10 * port2;
517     *port = (*port) * 256 + port2;
518   }
519   xfree (respline);
520   return FTPOK;
521 }
522
523 /* Sends the TYPE request to the server.  */
524 uerr_t
525 ftp_type (struct rbuf *rbuf, int type)
526 {
527   char *request, *respline;
528   int nwritten;
529   uerr_t err;
530   char stype[2];
531
532   /* Construct argument.  */
533   stype[0] = type;
534   stype[1] = 0;
535   /* Send TYPE request.  */
536   request = ftp_request ("TYPE", stype);
537   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
538   if (nwritten < 0)
539     {
540       xfree (request);
541       return WRITEFAILED;
542     }
543   xfree (request);
544   /* Get appropriate response.  */
545   err = ftp_response (rbuf, &respline);
546   if (err != FTPOK)
547     {
548       xfree (respline);
549       return err;
550     }
551   if (*respline != '2')
552     {
553       xfree (respline);
554       return FTPUNKNOWNTYPE;
555     }
556   xfree (respline);
557   /* All OK.  */
558   return FTPOK;
559 }
560
561 /* Changes the working directory by issuing a CWD command to the
562    server.  */
563 uerr_t
564 ftp_cwd (struct rbuf *rbuf, const char *dir)
565 {
566   char *request, *respline;
567   int nwritten;
568   uerr_t err;
569
570   /* Send CWD request.  */
571   request = ftp_request ("CWD", dir);
572   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
573   if (nwritten < 0)
574     {
575       xfree (request);
576       return WRITEFAILED;
577     }
578   xfree (request);
579   /* Get appropriate response.  */
580   err = ftp_response (rbuf, &respline);
581   if (err != FTPOK)
582     {
583       xfree (respline);
584       return err;
585     }
586   if (*respline == '5')
587     {
588       xfree (respline);
589       return FTPNSFOD;
590     }
591   if (*respline != '2')
592     {
593       xfree (respline);
594       return FTPRERR;
595     }
596   xfree (respline);
597   /* All OK.  */
598   return FTPOK;
599 }
600
601 /* Sends REST command to the FTP server.  */
602 uerr_t
603 ftp_rest (struct rbuf *rbuf, long offset)
604 {
605   char *request, *respline;
606   int nwritten;
607   uerr_t err;
608   static char numbuf[24]; /* Buffer for the number */
609
610   number_to_string (numbuf, offset);
611   request = ftp_request ("REST", numbuf);
612   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
613   if (nwritten < 0)
614     {
615       xfree (request);
616       return WRITEFAILED;
617     }
618   xfree (request);
619   /* Get appropriate response.  */
620   err = ftp_response (rbuf, &respline);
621   if (err != FTPOK)
622     {
623       xfree (respline);
624       return err;
625     }
626   if (*respline != '3')
627     {
628       xfree (respline);
629       return FTPRESTFAIL;
630     }
631   xfree (respline);
632   /* All OK.  */
633   return FTPOK;
634 }
635
636 /* Sends RETR command to the FTP server.  */
637 uerr_t
638 ftp_retr (struct rbuf *rbuf, const char *file)
639 {
640   char *request, *respline;
641   int nwritten;
642   uerr_t err;
643
644   /* Send RETR request.  */
645   request = ftp_request ("RETR", file);
646   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
647   if (nwritten < 0)
648     {
649       xfree (request);
650       return WRITEFAILED;
651     }
652   xfree (request);
653   /* Get appropriate response.  */
654   err = ftp_response (rbuf, &respline);
655   if (err != FTPOK)
656     {
657       xfree (respline);
658       return err;
659     }
660   if (*respline == '5')
661     {
662       xfree (respline);
663       return FTPNSFOD;
664     }
665   if (*respline != '1')
666     {
667       xfree (respline);
668       return FTPRERR;
669     }
670   xfree (respline);
671   /* All OK.  */
672   return FTPOK;
673 }
674
675 /* Sends the LIST command to the server.  If FILE is NULL, send just
676    `LIST' (no space).  */
677 uerr_t
678 ftp_list (struct rbuf *rbuf, const char *file)
679 {
680   char *request, *respline;
681   int nwritten;
682   uerr_t err;
683
684   /* Send LIST request.  */
685   request = ftp_request ("LIST", file);
686   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
687   if (nwritten < 0)
688     {
689       xfree (request);
690       return WRITEFAILED;
691     }
692   xfree (request);
693   /* Get appropriate respone.  */
694   err = ftp_response (rbuf, &respline);
695   if (err != FTPOK)
696     {
697       xfree (respline);
698       return err;
699     }
700   if (*respline == '5')
701     {
702       xfree (respline);
703       return FTPNSFOD;
704     }
705   if (*respline != '1')
706     {
707       xfree (respline);
708       return FTPRERR;
709     }
710   xfree (respline);
711   /* All OK.  */
712   return FTPOK;
713 }
714
715 /* Sends the SYST command to the server. */
716 uerr_t
717 ftp_syst (struct rbuf *rbuf, enum stype *server_type)
718 {
719   char *request, *respline;
720   int nwritten;
721   uerr_t err;
722
723   /* Send SYST request.  */
724   request = ftp_request ("SYST", NULL);
725   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
726   if (nwritten < 0)
727     {
728       xfree (request);
729       return WRITEFAILED;
730     }
731   xfree (request);
732
733   /* Get appropriate response.  */
734   err = ftp_response (rbuf, &respline);
735   if (err != FTPOK)
736     {
737       xfree (respline);
738       return err;
739     }
740   if (*respline == '5')
741     {
742       xfree (respline);
743       return FTPSRVERR;
744     }
745
746   /* Skip the number (215, but 200 (!!!) in case of VMS) */
747   strtok (respline, " ");
748   
749   /* Which system type has been reported (we are interested just in the
750      first word of the server response)?  */
751   request = strtok (NULL, " ");
752
753   if (!strcasecmp (request, "VMS"))
754     *server_type = ST_VMS;
755   else if (!strcasecmp (request, "UNIX"))
756     *server_type = ST_UNIX;
757   else if (!strcasecmp (request, "WINDOWS_NT"))
758     *server_type = ST_WINNT;
759   else if (!strcasecmp (request, "MACOS"))
760     *server_type = ST_MACOS;
761   else
762     *server_type = ST_OTHER;
763
764   xfree (respline);
765   /* All OK.  */
766   return FTPOK;
767 }
768
769 /* Sends the PWD command to the server. */
770 uerr_t
771 ftp_pwd (struct rbuf *rbuf, char **pwd)
772 {
773   char *request, *respline;
774   int nwritten;
775   uerr_t err;
776
777   /* Send PWD request.  */
778   request = ftp_request ("PWD", NULL);
779   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
780   if (nwritten < 0)
781     {
782       xfree (request);
783       return WRITEFAILED;
784     }
785   xfree (request);
786   /* Get appropriate response.  */
787   err = ftp_response (rbuf, &respline);
788   if (err != FTPOK)
789     {
790       xfree (respline);
791       return err;
792     }
793   if (*respline == '5')
794     {
795       xfree (respline);
796       return FTPSRVERR;
797     }
798
799   /* Skip the number (257), leading citation mark, trailing citation mark
800      and everything following it. */
801   strtok (respline, "\"");
802   request = strtok (NULL, "\"");
803   
804   /* Has the `pwd' been already allocated?  Free! */
805   FREE_MAYBE (*pwd);
806
807   *pwd = xstrdup (request);
808
809   xfree (respline);
810   /* All OK.  */
811   return FTPOK;
812 }
813
814 /* Sends the SIZE command to the server, and returns the value in 'size'.
815  * If an error occurs, size is set to zero. */
816 uerr_t
817 ftp_size (struct rbuf *rbuf, const char *file, long int *size)
818 {
819   char *request, *respline;
820   int nwritten;
821   uerr_t err;
822
823   /* Send PWD request.  */
824   request = ftp_request ("SIZE", file);
825   nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request));
826   if (nwritten < 0)
827     {
828       xfree (request);
829       *size = 0;
830       return WRITEFAILED;
831     }
832   xfree (request);
833   /* Get appropriate response.  */
834   err = ftp_response (rbuf, &respline);
835   if (err != FTPOK)
836     {
837       xfree (respline);
838       *size = 0;
839       return err;
840     }
841   if (*respline == '5')
842     {
843       /* 
844        * Probably means SIZE isn't supported on this server.
845        * Error is nonfatal since SIZE isn't in RFC 959 
846        */
847       xfree (respline);
848       *size = 0;
849       return FTPOK;
850     }
851
852   errno = 0;
853   *size = strtol (respline + 4, NULL, 0);
854   if (errno) 
855     {
856       /* 
857        * Couldn't parse the response for some reason.  On the (few)
858        * tests I've done, the response is 213 <SIZE> with nothing else -
859        * maybe something a bit more resilient is necessary.  It's not a
860        * fatal error, however.
861        */
862       xfree (respline);
863       *size = 0;
864       return FTPOK;
865     }
866
867   xfree (respline);
868   /* All OK.  */
869   return FTPOK;
870 }
871
872 /* If URL's params are of the form "type=X", return character X.
873    Otherwise, return 'I' (the default type).  */
874 char
875 ftp_process_type (const char *params)
876 {
877   if (params
878       && 0 == strncasecmp (params, "type=", 5)
879       && params[5] != '\0')
880     return TOUPPER (params[5]);
881   else
882     return 'I';
883 }