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