diff options
Diffstat (limited to 'lib/libftpio/ftpio.c')
-rw-r--r-- | lib/libftpio/ftpio.c | 686 |
1 files changed, 686 insertions, 0 deletions
diff --git a/lib/libftpio/ftpio.c b/lib/libftpio/ftpio.c new file mode 100644 index 0000000..b5d58b8 --- /dev/null +++ b/lib/libftpio/ftpio.c @@ -0,0 +1,686 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@login.dknet.dk> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * Major Changelog: + * + * Jordan K. Hubbard + * 17 Jan 1996 + * + * Turned inside out. Now returns xfers as new file ids, not as a special + * `state' of FTP_t + * + * $Id: ftp.c,v 1.14 1995/06/11 19:29:55 rgrimes Exp $ + * + */ + +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include <netdb.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include <sys/signal.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <ftpio.h> + +#define SUCCESS 0 +#define FAILURE -1 + +#ifndef TRUE +#define TRUE (1) +#define FALSE (0) +#endif + +/* How to see by a given code whether or not the connection has timed out */ +#define FTP_TIMEOUT(code) (code == 421) + +/* Internal routines - deal only with internal FTP_t type */ +static FTP_t ftp_new(void); +static int ftp_read_method(void *n, char *buf, int nbytes); +static int ftp_write_method(void *n, const char *buf, int nbytes); +static int ftp_close_method(void *n); +static int writes(int fd, char *s); +static __inline char *get_a_line(FTP_t ftp); +static int get_a_number(FTP_t ftp, char **q); +static int botch(char *func, char *botch_state); +static int cmd(FTP_t ftp, const char *fmt, ...); +static int ftp_login_session(FTP_t ftp, char *host, char *user, char *passwd, int port); +static int ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode); +static int ftp_close(FTP_t ftp); +static int get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret); + +/* Global status variable - ick */ +int FtpTimedOut; + +/* FTP status codes */ +#define FTP_BINARY_HAPPY 200 +#define FTP_PORT_HAPPY 200 +#define FTP_QUIT_HAPPY 221 +#define FTP_TRANSFER_HAPPY 226 +#define FTP_PASSIVE_HAPPY 227 +#define FTP_CHDIR_HAPPY 250 + +/* + * XXX + * gross! evil! bad! We really need an access primitive for cookie in stdio itself. + * it's too convenient a hook to bury and it's already exported through funopen as it is, so... + * XXX + */ +#define fcookie(fp) ((fp)->_cookie) + +/* Placeholder in case we want to do any pre-init stuff at some point */ +int +networkInit() +{ + return SUCCESS; /* XXX dummy function for now XXX */ +} + +/* Check a return code with some lenience for back-dated garbage that might be in the buffer */ +static int +check_code(FTP_t ftp, int var, int preferred) +{ + ftp->errno = 0; + while (1) { + if (var == preferred) + return 0; + else if (var == 226) /* last operation succeeded */ + var = get_a_number(ftp, NULL); + else if (var == 220) /* chit-chat */ + var = get_a_number(ftp, NULL); + else if (var == 200) /* success codes */ + var = get_a_number(ftp, NULL); + else { + ftp->errno = var; + return 1; + } + } +} + +/* Returns a standard FILE pointer type representing an open control connection */ +FILE * +ftpLogin(char *host, char *user, char *passwd, int port) +{ + FTP_t n; + FILE *fp; + + if (networkInit() != SUCCESS) + return NULL; + + n = ftp_new(); + fp = NULL; + if (n && ftp_login_session(n, host, user, passwd, port) == SUCCESS) { + fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method); /* BSD 4.4 function! */ + /* Yuck, but can't use fdopen() because that also allocates an fp. Sigh! */ + fp->_file = n->fd_ctrl; + } + if (n && !fp) + free(n); + return fp; +} + +int +ftpChdir(FILE *fp, char *dir) +{ + int i; + FTP_t ftp = fcookie(fp); + + i = cmd(ftp, "CWD %s", dir); + if (i < 0 || check_code(ftp, i, FTP_CHDIR_HAPPY)) + return -1; + return SUCCESS; +} + +int +ftpErrno(FILE *fp) +{ + FTP_t ftp = fcookie(fp); + return ftp->errno; +} + +int +ftpRestart(FILE *fp, int where) +{ + FTP_t ftp = fcookie(fp); + int old = ftp->seek; + + ftp->seek = where; + return old; +} + +size_t +ftpGetSize(FILE *fp, char *name) +{ + int i; + char p[BUFSIZ], *cp; + FTP_t ftp = fcookie(fp); + + sprintf(p, "SIZE %s\r\n", name); + i = writes(ftp->fd_ctrl, p); + if (i) + return (size_t)-1; + i = get_a_number(ftp, &cp); + if (check_code(ftp, i, 213)) + return (size_t)-1; + return (size_t)atoi(cp); +} + +time_t +ftpGetModtime(FILE *fp, char *name) +{ + char p[BUFSIZ], *cp; + struct tm t; + time_t t0 = time (0); + FTP_t ftp = fcookie(fp); + int i; + + sprintf(p, "MDTM %s\r\n", name); + i = writes(ftp->fd_ctrl, p); + if (i) + return (time_t)0; + i = get_a_number(ftp, &cp); + if (check_code(ftp, i, 213)) + return (time_t)0; + while (*cp && !isdigit(*cp)) + cp++; + if (!*cp) + return (time_t)0; + t0 = localtime (&t0)->tm_gmtoff; + 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); + t.tm_mon--; + t.tm_year -= 1900; + t.tm_isdst=-1; + t.tm_gmtoff = 0; + t0 += mktime (&t); + return t0; +} + +FILE * +ftpGet(FILE *fp, char *file) +{ + FILE *fp2; + FTP_t ftp = fcookie(fp); + + if (ftp_file_op(ftp, "RETR", file, &fp2, "r") == SUCCESS) + return fp2; + return NULL; +} + +FILE * +ftpPut(FILE *fp, char *file) +{ + FILE *fp2; + FTP_t ftp = fcookie(fp); + + if (ftp_file_op(ftp, "STOR", file, &fp2, "w") == SUCCESS) + return fp2; + return NULL; +} + +int +ftpBinary(FILE *fp, int st) +{ + FTP_t ftp = fcookie(fp); + + ftp->binary = st; + return SUCCESS; +} + +int +ftpPassive(FILE *fp, int st) +{ + FTP_t ftp = fcookie(fp); + + ftp->passive = st; + return SUCCESS; +} + +FILE * +ftpGetURL(char *url, char *user, char *passwd) +{ + char host[255], name[255]; + int port; + FILE *fp, *fp2; + + if (get_url_info(url, host, &port, name) == SUCCESS) { + fp = ftpLogin(host, user, passwd, port); + if (fp) { + fp2 = ftpGet(fp, name); + fclose(fp); + return fp2; + } + } + return NULL; +} + +FILE * +ftpPutURL(char *url, char *user, char *passwd) +{ + char host[255], name[255]; + int port; + FILE *fp, *fp2; + + if (get_url_info(url, host, &port, name) == SUCCESS) { + fp = ftpLogin(host, user, passwd, port); + if (fp) { + fp2 = ftpPut(fp, name); + fclose(fp); + return fp2; + } + } + return NULL; +} + +/* Internal workhorse function for dissecting URLs. Takes a URL as the first argument and returns the + result of such disection in the host, user, passwd, port and name variables. */ +static int +get_url_info(char *url_in, char *host_ret, int *port_ret, char *name_ret) +{ + char *name, *host, *cp, url[BUFSIZ]; + int port; + + name = host = NULL; + /* XXX add http:// here or somewhere reasonable at some point XXX */ + if (strncmp("ftp://", url_in, 6) != NULL) + return FAILURE; + /* We like to stomp a lot on the URL string in dissecting it, so copy it first */ + strncpy(url, url_in, BUFSIZ); + host = url + 6; + if ((cp = index(host, ':')) != NULL) { + *(cp++) = '\0'; + port = strtol(cp, 0, 0); + } + else + port = 0; /* use default */ + if (port_ret) + *port_ret = port; + + if ((name = index(cp ? cp : host, '/')) != NULL) + *(name++) = '\0'; + if (host_ret) + strcpy(host_ret, host); + if (name && name_ret) + strcpy(name_ret, name); + return SUCCESS; +} + +static FTP_t +ftp_new(void) +{ + FTP_t ftp; + + ftp = (FTP_t)malloc(sizeof *ftp); + if (!ftp) + return NULL; + memset(ftp, 0, sizeof *ftp); + ftp->fd_ctrl = -1; + ftp->con_state = init; + ftp->errno = 0; + ftp->seek = 0; + return ftp; +} + +static int +ftp_read_method(void *vp, char *buf, int nbytes) +{ + int i, fd; + FTP_t n = (FTP_t)vp; + + fd = n->fd_ctrl; + i = (fd >= 0) ? read(fd, buf, nbytes) : EOF; + return i; +} + +static int +ftp_write_method(void *vp, const char *buf, int nbytes) +{ + int i, fd; + FTP_t n = (FTP_t)vp; + + fd = n->fd_ctrl; + i = (fd >= 0) ? write(fd, buf, nbytes) : EOF; + return i; +} + +static int +ftp_close_method(void *n) +{ + return ftp_close((FTP_t)n); +} + +static void +ftp_timeout() +{ + FtpTimedOut = TRUE; + /* Debug("ftp_pkg: ftp_timeout called - operation timed out"); */ +} + +static int +writes(int fd, char *s) +{ + int n, i = strlen(s); + + /* Set the timer */ + FtpTimedOut = FALSE; + signal(SIGALRM, ftp_timeout); + alarm(120); + /* Debug("ftp_pkg: writing \"%s\" to ftp connection %d", s, fd); */ + n = write(fd, s, i); + alarm(0); + if (i != n) + return FAILURE; + return SUCCESS; +} + +static __inline char * +get_a_line(FTP_t ftp) +{ + static char buf[BUFSIZ]; + int i,j; + + /* Set the timer */ + FtpTimedOut = FALSE; + signal(SIGALRM, ftp_timeout); + + /* Debug("ftp_pkg: trying to read a line from %d", ftp->fd_ctrl); */ + for(i = 0; i < BUFSIZ;) { + alarm(120); + j = read(ftp->fd_ctrl, buf + i, 1); + alarm(0); + if (j != 1) + return NULL; + if (buf[i] == '\r' || buf[i] == '\n') { + if (!i) + continue; + buf[i] = '\0'; + return buf; + } + i++; + } + /* Debug("ftp_pkg: read string \"%s\" from %d", buf, ftp->fd_ctrl); */ + return buf; +} + +static int +get_a_number(FTP_t ftp, char **q) +{ + char *p; + int i = -1, j; + + while(1) { + p = get_a_line(ftp); + if (!p) + return FAILURE; + if (!(isdigit(p[0]) && isdigit(p[1]) && isdigit(p[2]))) + continue; + if (i == -1 && p[3] == '-') { + i = strtol(p, 0, 0); + continue; + } + if (p[3] != ' ' && p[3] != '\t') + continue; + j = strtol(p, 0, 0); + if (i == -1) { + if (q) *q = p+4; + /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */ + return j; + } else if (j == i) { + if (q) *q = p+4; + /* Debug("ftp_pkg: read reply %d from server (%s)", j, p); */ + return j; + } + } +} + +static int +ftp_close(FTP_t ftp) +{ + int i; + + if (ftp->con_state == isopen) { + /* Debug("ftp_pkg: in ftp_close(), sending QUIT"); */ + i = cmd(ftp, "QUIT"); + close(ftp->fd_ctrl); + ftp->fd_ctrl = -1; + ftp->con_state = init; + if (check_code(ftp, i, FTP_QUIT_HAPPY)) { + ftp->errno = i; + return FAILURE; + } + /* Debug("ftp_pkg: ftp_close() - proper shutdown"); */ + return SUCCESS; + } + /* Debug("ftp_pkg: ftp_close() - improper shutdown"); */ + return FAILURE; +} + +static int +botch(char *func, char *botch_state) +{ + /* Debug("ftp_pkg: botch: %s(%s)", func, botch_state); */ + return FAILURE; +} + +static int +cmd(FTP_t ftp, const char *fmt, ...) +{ + char p[BUFSIZ]; + int i; + + va_list ap; + va_start(ap, fmt); + (void)vsnprintf(p, sizeof p, fmt, ap); + va_end(ap); + + if (ftp->con_state != isopen) + return botch("cmd", "open"); + + strcat(p, "\r\n"); + i = writes(ftp->fd_ctrl, p); + if (i) + return FAILURE; + while ((i = get_a_number(ftp, NULL)) == 220); + return i; +} + +static int +ftp_login_session(FTP_t ftp, char *host, char *user, char *passwd, int port) +{ + struct hostent *he = NULL; + struct sockaddr_in sin; + int s; + unsigned long temp; + int i; + + if (networkInit() != SUCCESS) + return FAILURE; + + if (ftp->con_state != init) + ftp_close(ftp); + + if (!user) + user = "ftp"; + + if (!passwd) + passwd = "setup@"; + + if (!port) + port = 21; + + temp = inet_addr(host); + if (temp != INADDR_NONE) { + ftp->addrtype = sin.sin_family = AF_INET; + sin.sin_addr.s_addr = temp; + } + else { + he = gethostbyname(host); + if (!he) + return FAILURE; + ftp->addrtype = sin.sin_family = he->h_addrtype; + bcopy(he->h_addr, (char *)&sin.sin_addr, he->h_length); + } + + sin.sin_port = htons(port); + + if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) + return FAILURE; + + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + (void)close(s); + return FAILURE; + } + + ftp->fd_ctrl = s; + ftp->con_state = isopen; + + i = cmd(ftp, "USER %s", user); + if (i >= 300 && i < 400) + i = cmd(ftp, "PASS %s", passwd); + if (i >= 299 || i < 0) { + ftp_close(ftp); + return FAILURE; + } + return SUCCESS; +} + +static int +ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode) +{ + int i,s; + char *q; + unsigned char addr[64]; + struct sockaddr_in sin; + u_long a; + + if (!fp) + return FAILURE; + *fp = NULL; + + if (ftp->con_state != isopen) + return botch("ftp_file_op", "open"); + + if (ftp->binary) { + i = cmd(ftp, "TYPE I"); + if (check_code(ftp, i, FTP_BINARY_HAPPY)) { + ftp_close(ftp); + return i; + } + } + + if ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0) + return FAILURE; + + if (ftp->passive) { + if (writes(ftp->fd_ctrl, "PASV\r\n")) { + ftp_close(ftp); + return FAILURE; + } + i = get_a_number(ftp, &q); + if (check_code(ftp, i, FTP_PASSIVE_HAPPY)) + return i; + while (*q && !isdigit(*q)) + q++; + if (!*q) { + ftp_close(ftp); + return FAILURE; + } + q--; + for (i = 0; i < 6; i++) { + q++; + addr[i] = strtol(q, &q, 10); + } + + sin.sin_family = ftp->addrtype; + bcopy(addr, (char *)&sin.sin_addr, 4); + bcopy(addr + 4, (char *)&sin.sin_port, 2); + if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + (void)close(s); + return FAILURE; + } + if (!strcmp(operation, "RETR") && ftp->seek) { + i = cmd(ftp, "RETR %d", ftp->seek); + if (i < 0 || FTP_TIMEOUT(i)) { + close(s); + ftp->errno = i; + return i; + } + else if (i != 350) + ftp->seek = 0; + } + i = cmd(ftp, "%s %s", operation, file); + if (i < 0 || i > 299) { + close(s); + ftp->errno = i; + return i; + } + *fp = fdopen(s, mode); + } + else { + int fd; + + i = sizeof sin; + getsockname(ftp->fd_ctrl, (struct sockaddr *)&sin, &i); + sin.sin_port = 0; + i = sizeof sin; + if (bind(s, (struct sockaddr *)&sin, i) < 0) { + close (s); + return FAILURE; + } + getsockname(s,(struct sockaddr *)&sin,&i); + if (listen(s, 1) < 0) { + close(s); + return FAILURE; + } + a = ntohl(sin.sin_addr.s_addr); + i = cmd(ftp, "PORT %d,%d,%d,%d,%d,%d", + (a >> 24) & 0xff, + (a >> 16) & 0xff, + (a >> 8) & 0xff, + a & 0xff, + (ntohs(sin.sin_port) >> 8) & 0xff, + ntohs(sin.sin_port) & 0xff); + if (check_code(ftp, i, FTP_PORT_HAPPY)) { + close(s); + return i; + } + if (!strcmp(operation, "RETR") && ftp->seek) { + i = cmd(ftp, "RETR %d", ftp->seek); + if (i < 0 || FTP_TIMEOUT(i)) { + close(s); + ftp->errno = i; + return i; + } + else if (i != 350) + ftp->seek = 0; + } + i = cmd(ftp, "%s %s", operation, file); + if (i < 0 || i > 299) { + close(s); + ftp->errno = i; + return FAILURE; + } + fd = accept(s, 0, 0); + if (fd < 0) { + close(s); + ftp->errno = 401; + return FAILURE; + } + close(s); + *fp = fdopen(fd, mode); + } + if (*fp) + return SUCCESS; + else + return FAILURE; +} |