2 * ----------------------------------------------------------------------------
3 * "THE BEER-WARE LICENSE" (Revision 42):
4 * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you
5 * can do whatever you want with this stuff. If we meet some day, and you think
6 * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
7 * ----------------------------------------------------------------------------
14 * Turned inside out. Now returns xfers as new file ids, not as a special
18 #include <sys/cdefs.h>
19 __FBSDID("$FreeBSD$");
21 #include <sys/types.h>
22 #include <sys/socket.h>
24 #include <netinet/in.h>
26 #include <arpa/inet.h>
47 /* How to see by a given code whether or not the connection has timed out */
48 #define FTP_TIMEOUT(code) (FtpTimedOut || code == FTP_TIMED_OUT)
50 /* Internal routines - deal only with internal FTP_t type */
51 static FTP_t ftp_new(void);
52 static void check_passive(FILE *fp);
53 static int ftp_read_method(void *n, char *buf, int nbytes);
54 static int ftp_write_method(void *n, const char *buf, int nbytes);
55 static int ftp_close_method(void *n);
56 static int writes(int fd, char *s);
57 static __inline char *get_a_line(FTP_t ftp);
58 static int get_a_number(FTP_t ftp, char **q);
59 static int botch(char *func, char *botch_state);
60 static int cmd(FTP_t ftp, const char *fmt, ...);
61 static int ftp_login_session(FTP_t ftp, char *host, int af, char *user, char *passwd, int port, int verbose);
62 static int ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto);
63 static int ftp_close(FTP_t ftp);
64 static int get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret);
65 static void ftp_timeout(int sig);
66 static void ftp_set_timeout(void);
67 static void ftp_clear_timeout(void);
68 static void ai_unmapped(struct addrinfo *);
71 /* Global status variable - ick */
74 /* FTP happy status codes */
75 #define FTP_GENERALLY_HAPPY 200
76 #define FTP_ASCII_HAPPY FTP_GENERALLY_HAPPY
77 #define FTP_BINARY_HAPPY FTP_GENERALLY_HAPPY
78 #define FTP_PORT_HAPPY FTP_GENERALLY_HAPPY
79 #define FTP_HAPPY_COMMENT 220
80 #define FTP_QUIT_HAPPY 221
81 #define FTP_TRANSFER_HAPPY 226
82 #define FTP_PASSIVE_HAPPY 227
83 #define FTP_LPASSIVE_HAPPY 228
84 #define FTP_EPASSIVE_HAPPY 229
85 #define FTP_CHDIR_HAPPY 250
87 /* FTP unhappy status codes */
88 #define FTP_TIMED_OUT 421
92 * gross! evil! bad! We really need an access primitive for cookie in stdio itself.
93 * it's too convenient a hook to bury and it's already exported through funopen as it is, so...
96 #define fcookie(fp) ((fp)->_cookie)
98 /* Placeholder in case we want to do any pre-init stuff at some point */
102 return SUCCESS; /* XXX dummy function for now XXX */
105 /* Check a return code with some lenience for back-dated garbage that might be in the buffer */
107 check_code(FTP_t ftp, int var, int preferred)
111 if (var == preferred)
113 else if (var == FTP_TRANSFER_HAPPY) /* last operation succeeded */
114 var = get_a_number(ftp, NULL);
115 else if (var == FTP_HAPPY_COMMENT) /* chit-chat */
116 var = get_a_number(ftp, NULL);
117 else if (var == FTP_GENERALLY_HAPPY) /* general success code */
118 var = get_a_number(ftp, NULL);
129 FTP_t ftp = fcookie(fp);
134 i = cmd(ftp, "TYPE A");
135 if (i < 0 || check_code(ftp, i, FTP_ASCII_HAPPY))
137 ftp->is_binary = FALSE;
144 FTP_t ftp = fcookie(fp);
149 i = cmd(ftp, "TYPE I");
150 if (i < 0 || check_code(ftp, i, FTP_BINARY_HAPPY))
152 ftp->is_binary = TRUE;
156 ftpVerbose(FILE *fp, int status)
158 FTP_t ftp = fcookie(fp);
159 ftp->is_verbose = status;
163 ftpChdir(FILE *fp, char *dir)
166 FTP_t ftp = fcookie(fp);
168 i = cmd(ftp, "CWD %s", dir);
169 if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY))
177 FTP_t ftp = fcookie(fp);
182 ftpErrString(int error)
187 return("connection in wrong state");
189 /* XXX soon UNIX errnos will catch up with FTP protocol errnos */
190 return strerror(error);
191 for (k = 0; k < ftpErrListLength; k++)
192 if (ftpErrList[k].num == error)
193 return(ftpErrList[k].string);
194 return("Unknown error");
198 ftpGetSize(FILE *fp, char *name)
201 char p[BUFSIZ], *cp, *ep;
202 FTP_t ftp = fcookie(fp);
206 sprintf(p, "SIZE %s\r\n", name);
208 fprintf(stderr, "Sending %s", p);
209 if (writes(ftp->fd_ctrl, p))
211 i = get_a_number(ftp, &cp);
212 if (check_code(ftp, i, 213))
215 errno = 0; /* to check for ERANGE */
216 size = (off_t)strtoq(cp, &ep, 10);
217 if (*ep != '\0' || errno == ERANGE)
223 ftpGetModtime(FILE *fp, char *name)
227 time_t t0 = time (0);
228 FTP_t ftp = fcookie(fp);
232 sprintf(p, "MDTM %s\r\n", name);
234 fprintf(stderr, "Sending %s", p);
235 if (writes(ftp->fd_ctrl, p))
237 i = get_a_number(ftp, &cp);
238 if (check_code(ftp, i, 213))
240 while (*cp && !isdigit(*cp))
244 t0 = localtime (&t0)->tm_gmtoff;
245 sscanf(cp, "%04d%02d%02d%02d%02d%02d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec);
255 ftpGet(FILE *fp, char *file, off_t *seekto)
258 FTP_t ftp = fcookie(fp);
261 if (ftpBinary(fp) != SUCCESS)
264 if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS)
269 /* Returns a standard FILE pointer type representing an open control connection */
271 ftpLogin(char *host, char *user, char *passwd, int port, int verbose, int *retcode)
274 return ftpLoginAf(host, AF_UNSPEC, user, passwd, port, verbose, retcode);
276 return ftpLoginAf(host, AF_INET, user, passwd, port, verbose, retcode);
281 ftpLoginAf(char *host, int af, char *user, char *passwd, int port, int verbose, int *retcode)
288 if (networkInit() != SUCCESS)
293 if (n && ftp_login_session(n, host, af, user, passwd, port, verbose) == SUCCESS) {
294 fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method); /* BSD 4.4 function! */
295 fp->_file = n->fd_ctrl;
299 *retcode = (FtpTimedOut ? FTP_TIMED_OUT : -1);
300 /* Poor attempt at mapping real errnos to FTP error codes */
301 else switch(n->error) {
303 *retcode = FTP_TIMED_OUT; /* Actually no such host, but we have no way of saying that. :-( */
307 *retcode = FTP_TIMED_OUT;
319 ftpPut(FILE *fp, char *file)
322 FTP_t ftp = fcookie(fp);
325 if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS)
331 ftpPassive(FILE *fp, int st)
333 FTP_t ftp = fcookie(fp);
335 ftp->is_passive = !!st; /* normalize "st" to zero or one */
340 ftpGetURL(char *url, char *user, char *passwd, int *retcode)
343 return ftpGetURLAf(url, AF_UNSPEC, user, passwd, retcode);
345 return ftpGetURLAf(url, AF_INET, user, passwd, retcode);
350 ftpGetURLAf(char *url, int af, char *user, char *passwd, int *retcode)
352 char host[255], name[255];
355 static FILE *fp = NULL;
356 static char *prev_host;
360 if (get_url_info(url, host, &port, name) == SUCCESS) {
361 if (fp && prev_host) {
362 if (!strcmp(prev_host, host)) {
363 /* Try to use cached connection */
364 fp2 = ftpGet(fp, name, NULL);
366 /* Connection timed out or was no longer valid */
375 /* It's a different host now, flush old */
381 fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
383 fp2 = ftpGet(fp, name, NULL);
385 /* Connection timed out or was no longer valid */
387 *retcode = ftpErrno(fp);
392 prev_host = strdup(host);
400 ftpPutURL(char *url, char *user, char *passwd, int *retcode)
403 return ftpPutURLAf(url, AF_UNSPEC, user, passwd, retcode);
405 return ftpPutURLAf(url, AF_INET, user, passwd, retcode);
411 ftpPutURLAf(char *url, int af, char *user, char *passwd, int *retcode)
413 char host[255], name[255];
415 static FILE *fp = NULL;
420 if (fp) { /* Close previous managed connection */
424 if (get_url_info(url, host, &port, name) == SUCCESS) {
425 fp = ftpLoginAf(host, af, user, passwd, port, 0, retcode);
427 fp2 = ftpPut(fp, name);
430 *retcode = ftpErrno(fp);
440 /* Internal workhorse function for dissecting URLs. Takes a URL as the first argument and returns the
441 result of such disection in the host, user, passwd, port and name variables. */
443 get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret)
445 char *name, *host, *cp, url[BUFSIZ];
449 /* XXX add http:// here or somewhere reasonable at some point XXX */
450 if (strncmp("ftp://", url_in, 6) != 0)
452 /* We like to stomp a lot on the URL string in dissecting it, so copy it first */
453 strncpy(url, url_in, BUFSIZ);
455 if ((cp = index(host, ':')) != NULL) {
457 port = strtol(cp, 0, 0);
460 port = 0; /* use default */
464 if ((name = index(cp ? cp : host, '/')) != NULL)
467 strcpy(host_ret, host);
468 if (name && name_ret)
469 strcpy(name_ret, name);
478 ftp = (FTP_t)malloc(sizeof *ftp);
481 memset(ftp, 0, sizeof *ftp);
483 ftp->con_state = init;
484 ftp->is_binary = FALSE;
485 ftp->is_passive = FALSE;
486 ftp->is_verbose = FALSE;
492 ftp_read_method(void *vp, char *buf, int nbytes)
498 i = (fd >= 0) ? read(fd, buf, nbytes) : EOF;
503 ftp_write_method(void *vp, const char *buf, int nbytes)
509 i = (fd >= 0) ? write(fd, buf, nbytes) : EOF;
514 ftp_close_method(void *n)
518 i = ftp_close((FTP_t)n);
524 * This function checks whether the FTP_PASSIVE_MODE environment
525 * variable is set, and, if so, enforces the desired mode.
528 check_passive(FILE *fp)
530 const char *cp = getenv("FTP_PASSIVE_MODE");
533 ftpPassive(fp, strncasecmp(cp, "no", 2));
540 /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */
544 ftp_set_timeout(void)
546 struct sigaction new;
551 sigemptyset(&new.sa_mask);
553 new.sa_handler = ftp_timeout;
554 sigaction(SIGALRM, &new, NULL);
555 cp = getenv("FTP_TIMEOUT");
556 if (!cp || !(ival = atoi(cp)))
562 ftp_clear_timeout(void)
564 struct sigaction new;
567 sigemptyset(&new.sa_mask);
569 new.sa_handler = SIG_DFL;
570 sigaction(SIGALRM, &new, NULL);
574 writes(int fd, char *s)
576 int n, i = strlen(s);
581 if (FtpTimedOut || i != n)
586 static __inline char *
587 get_a_line(FTP_t ftp)
589 static char buf[BUFSIZ];
592 /* Debug("ftp_pkg: trying to read a line from %d", ftp->fd_ctrl); */
593 for(i = 0; i < BUFSIZ;) {
595 j = read(ftp->fd_ctrl, buf + i, 1);
597 if (FtpTimedOut || j != 1)
599 if (buf[i] == '\r' || buf[i] == '\n') {
603 if (ftp->is_verbose == TRUE)
604 fprintf(stderr, "%s\n",buf+4);
609 /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */
614 get_a_number(FTP_t ftp, char **q)
624 return FTP_TIMED_OUT;
627 if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2])))
629 if (i == -1 && p[3] == '-') {
633 if (p[3] != ' ' && p[3] != '\t')
638 /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
642 /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */
654 if (ftp->con_state == isopen) {
655 ftp->con_state = quit;
656 /* If last operation timed out, don't try to quit - just close */
657 if (ftp->error != FTP_TIMED_OUT)
658 i = cmd(ftp, "QUIT");
661 if (!check_code(ftp, i, FTP_QUIT_HAPPY))
666 else if (ftp->con_state == quit)
672 botch(char *func, char *botch_state)
674 /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */
679 cmd(FTP_t ftp, const char *fmt, ...)
686 (void)vsnprintf(p, sizeof p, fmt, ap);
689 if (ftp->con_state == init)
690 return botch("cmd", "open");
694 fprintf(stderr, "Sending: %s", p);
695 if (writes(ftp->fd_ctrl, p)) {
697 return FTP_TIMED_OUT;
700 while ((i = get_a_number(ftp, NULL)) == FTP_HAPPY_COMMENT);
705 ftp_login_session(FTP_t ftp, char *host, int af,
706 char *user, char *passwd, int port, int verbose)
709 struct addrinfo hints, *res, *res0;
714 if (networkInit() != SUCCESS)
717 if (ftp->con_state != init) {
732 snprintf(pbuf, sizeof(pbuf), "%d", port);
733 memset(&hints, 0, sizeof(hints));
734 hints.ai_family = af;
735 hints.ai_socktype = SOCK_STREAM;
736 hints.ai_protocol = 0;
737 err = getaddrinfo(host, pbuf, &hints, &res0);
744 for (res = res0; res; res = res->ai_next) {
746 ftp->addrtype = res->ai_family;
748 if ((s = socket(res->ai_family, res->ai_socktype,
749 res->ai_protocol)) < 0)
752 if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
767 ftp->con_state = isopen;
768 ftp->is_verbose = verbose;
770 i = cmd(ftp, "USER %s", user);
771 if (i >= 300 && i < 400)
772 i = cmd(ftp, "PASS %s", passwd);
773 if (i >= 299 || i < 0) {
783 ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto)
788 unsigned char addr[64];
790 struct sockaddr_in sin4;
791 struct sockaddr_in6 sin6;
799 if (ftp->con_state != isopen)
800 return botch("ftp_file_op", "open");
802 if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) {
807 if (ftp->is_passive) {
808 if (ftp->addrtype == AF_INET) {
810 fprintf(stderr, "Sending PASV\n");
811 if (writes(ftp->fd_ctrl, "PASV\r\n")) {
814 ftp->error = FTP_TIMED_OUT;
815 return FTP_TIMED_OUT;
817 i = get_a_number(ftp, &q);
818 if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) {
825 fprintf(stderr, "Sending EPSV\n");
826 if (writes(ftp->fd_ctrl, "EPSV\r\n")) {
829 ftp->error = FTP_TIMED_OUT;
830 return FTP_TIMED_OUT;
832 i = get_a_number(ftp, &q);
833 if (check_code(ftp, i, FTP_EPASSIVE_HAPPY)) {
835 fprintf(stderr, "Sending LPSV\n");
836 if (writes(ftp->fd_ctrl, "LPSV\r\n")) {
839 ftp->error = FTP_TIMED_OUT;
840 return FTP_TIMED_OUT;
842 i = get_a_number(ftp, &q);
843 if (check_code(ftp, i, FTP_LPASSIVE_HAPPY)) {
851 if (strcmp(cmdstr, "PASV") == 0 || strcmp(cmdstr, "LPSV") == 0) {
852 while (*q && !isdigit(*q))
859 l = (ftp->addrtype == AF_INET ? 6 : 21);
860 for (i = 0; i < l; i++) {
862 addr[i] = strtol(q, &q, 10);
865 sin.sin4.sin_family = ftp->addrtype;
866 if (ftp->addrtype == AF_INET6) {
867 sin.sin6.sin6_len = sizeof(struct sockaddr_in6);
868 bcopy(addr + 2, (char *)&sin.sin6.sin6_addr, 16);
869 bcopy(addr + 19, (char *)&sin.sin6.sin6_port, 2);
871 sin.sin4.sin_len = sizeof(struct sockaddr_in);
872 bcopy(addr, (char *)&sin.sin4.sin_addr, 4);
873 bcopy(addr + 4, (char *)&sin.sin4.sin_port, 2);
875 } else if (strcmp(cmdstr, "EPSV") == 0) {
877 while (*q && *q != '(') /* ) */
884 if (sscanf(q, "%c%c%c%d%c", &addr[0], &addr[1], &addr[2],
885 &port, &addr[3]) != 5
886 || addr[0] != addr[1] || addr[0] != addr[2] || addr[0] != addr[3]) {
890 sinlen = sizeof(sin);
891 if (getpeername(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen) < 0) {
895 switch (sin.sin4.sin_family) {
897 sin.sin4.sin_port = htons(port);
900 sin.sin6.sin6_port = htons(port);
908 if (connect(s, (struct sockaddr *)&sin, sin.sin4.sin_len) < 0) {
913 if (seekto && *seekto) {
914 i = cmd(ftp, "REST %d", *seekto);
915 if (i < 0 || FTP_TIMEOUT(i)) {
922 i = cmd(ftp, "%s %s", operation, file);
923 if (i < 0 || i > 299) {
928 *fp = fdopen(s, mode);
933 #ifdef IPV6_PORTRANGE
934 if (ftp->addrtype == AF_INET6) {
935 portrange = IPV6_PORTRANGE_HIGH;
936 if (setsockopt(s, IPPROTO_IPV6, IPV6_PORTRANGE, (char *)
937 &portrange, sizeof(portrange)) < 0) {
944 if (ftp->addrtype == AF_INET) {
945 portrange = IP_PORTRANGE_HIGH;
946 if (setsockopt(s, IPPROTO_IP, IP_PORTRANGE, (char *)
947 &portrange, sizeof(portrange)) < 0) {
955 getsockname(ftp->fd_ctrl, (struct sockaddr *)&sin, &sinlen);
956 sin.sin4.sin_port = 0;
957 i = ((struct sockaddr *)&sin)->sa_len;
958 if (bind(s, (struct sockaddr *)&sin, i) < 0) {
963 getsockname(s, (struct sockaddr *)&sin, &sinlen);
964 if (listen(s, 1) < 0) {
968 if (sin.sin4.sin_family == AF_INET) {
970 a = ntohl(sin.sin4.sin_addr.s_addr);
971 i = cmd(ftp, "PORT %d,%d,%d,%d,%d,%d",
976 (ntohs(sin.sin4.sin_port) >> 8) & 0xff,
977 ntohs(sin.sin4.sin_port) & 0xff);
978 if (check_code(ftp, i, FTP_PORT_HAPPY)) {
983 #define UC(b) (((int)b)&0xff)
985 char hname[INET6_ADDRSTRLEN];
987 sin.sin6.sin6_scope_id = 0;
988 if (getnameinfo((struct sockaddr *)&sin, sin.sin6.sin6_len,
989 hname, sizeof(hname),
990 NULL, 0, NI_NUMERICHOST) != 0) {
993 i = cmd(ftp, "EPRT |%d|%s|%d|", 2, hname,
994 htons(sin.sin6.sin6_port));
995 if (check_code(ftp, i, FTP_PORT_HAPPY)) {
997 a = (char *)&sin.sin6.sin6_addr;
999 "LPRT %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
1001 UC(a[0]),UC(a[1]),UC(a[2]),UC(a[3]),
1002 UC(a[4]),UC(a[5]),UC(a[6]),UC(a[7]),
1003 UC(a[8]),UC(a[9]),UC(a[10]),UC(a[11]),
1004 UC(a[12]),UC(a[13]),UC(a[14]),UC(a[15]),
1006 (ntohs(sin.sin4.sin_port) >> 8) & 0xff,
1007 ntohs(sin.sin4.sin_port) & 0xff);
1008 if (check_code(ftp, i, FTP_PORT_HAPPY)) {
1014 if (seekto && *seekto) {
1015 i = cmd(ftp, "REST %d", *seekto);
1016 if (i < 0 || FTP_TIMEOUT(i)) {
1024 i = cmd(ftp, "%s %s", operation, file);
1025 if (i < 0 || i > 299) {
1030 fd = accept(s, 0, 0);
1037 *fp = fdopen(fd, mode);
1046 ai_unmapped(struct addrinfo *ai)
1048 struct sockaddr_in6 *sin6;
1049 struct sockaddr_in sin;
1051 if (ai->ai_family != AF_INET6)
1053 if (ai->ai_addrlen != sizeof(struct sockaddr_in6) ||
1054 sizeof(sin) > ai->ai_addrlen)
1056 sin6 = (struct sockaddr_in6 *)ai->ai_addr;
1057 if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
1060 memset(&sin, 0, sizeof(sin));
1061 sin.sin_family = AF_INET;
1062 sin.sin_len = sizeof(struct sockaddr_in);
1063 memcpy(&sin.sin_addr, &sin6->sin6_addr.s6_addr[12],
1064 sizeof(sin.sin_addr));
1065 sin.sin_port = sin6->sin6_port;
1067 ai->ai_family = AF_INET;
1068 memcpy(ai->ai_addr, &sin, sin.sin_len);
1069 ai->ai_addrlen = sin.sin_len;