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