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