freebsd-skq/lib/libftpio/ftpio.c
ache 22406d98dc For functions ftpGetURL, ftpPutURL, ftpLogin it was impossible to know
FTP error return code because
1) They return NULL, it means that ftpErrno can't be used because
it takes file pointer
2) They don't have FILE-type argument as f.e. ftpGet/ftpPut to use
it for ftpErrno instead.

For that functions I add yet one int* type argument to store
FTP error return code. It is impossible to add some global variable
for that reason, because user can have multiply FTP connections
opened at the same time.

So, interface changed, major number bumped.
Userland changes will follows.

Minor bugfixes, the code:
Forget to close file in few places, when failure occurse
Forget to NULL cached host name, multiply free is possible
1996-11-14 06:59:41 +00:00

806 lines
17 KiB
C

/*
* ----------------------------------------------------------------------------
* "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: ftpio.c,v 1.17 1996/11/14 05:22:12 ache Exp $
*
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <ftpio.h>
#include <netdb.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.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 void check_passive(FILE *fp);
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, int verbose);
static int ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto);
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_ASCII_HAPPY 200
#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;
}
}
}
int
ftpAscii(FILE *fp)
{
FTP_t ftp = fcookie(fp);
int i;
if (!ftp->is_binary)
return SUCCESS;
i = cmd(ftp, "TYPE A");
if (i < 0 || check_code(ftp, i, FTP_ASCII_HAPPY))
return i;
ftp->is_binary = FALSE;
return SUCCESS;
}
int
ftpBinary(FILE *fp)
{
FTP_t ftp = fcookie(fp);
int i;
if (ftp->is_binary)
return SUCCESS;
i = cmd(ftp, "TYPE I");
if (i < 0 || check_code(ftp, i, FTP_BINARY_HAPPY))
return i;
ftp->is_binary = TRUE;
return SUCCESS;
}
void
ftpVerbose(FILE *fp, int status)
{
FTP_t ftp = fcookie(fp);
ftp->is_verbose = status;
}
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 i;
return SUCCESS;
}
int
ftpErrno(FILE *fp)
{
FTP_t ftp = fcookie(fp);
return ftp->errno;
}
const char *
ftpErrString(int errno)
{
int k;
for (k = 0; k < ftpErrListLength; k++)
if (ftpErrList[k].num == errno)
return(ftpErrList[k].string);
return("Unknown error");
}
off_t
ftpGetSize(FILE *fp, char *name)
{
int i;
char p[BUFSIZ], *cp, *ep;
FTP_t ftp = fcookie(fp);
off_t size;
check_passive(fp);
sprintf(p, "SIZE %s\r\n", name);
if (ftp->is_verbose)
fprintf(stderr, "Sending %s", p);
i = writes(ftp->fd_ctrl, p);
if (i)
return (off_t)-1;
i = get_a_number(ftp, &cp);
if (check_code(ftp, i, 213))
return (off_t)-1;
errno = 0; /* to check for ERANGE */
size = (off_t)strtoq(cp, &ep, 10);
if (*ep != '\0' || errno == ERANGE)
return (off_t)-1;
return size;
}
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;
check_passive(fp);
sprintf(p, "MDTM %s\r\n", name);
if (ftp->is_verbose)
fprintf(stderr, "Sending %s", p);
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, off_t *seekto)
{
FILE *fp2;
FTP_t ftp = fcookie(fp);
check_passive(fp);
if (ftpBinary(fp) != SUCCESS)
return NULL;
if (ftp_file_op(ftp, "RETR", file, &fp2, "r", seekto) == SUCCESS)
return fp2;
return NULL;
}
/* Returns a standard FILE pointer type representing an open control connection */
FILE *
ftpLogin(char *host, char *user, char *passwd, int port, int verbose, int *retcode)
{
FTP_t n;
FILE *fp;
if (retcode)
*retcode = 0;
if (networkInit() != SUCCESS)
return NULL;
n = ftp_new();
fp = NULL;
if (n && ftp_login_session(n, host, user, passwd, port, verbose) == SUCCESS) {
fp = funopen(n, ftp_read_method, ftp_write_method, NULL, ftp_close_method); /* BSD 4.4 function! */
fp->_file = n->fd_ctrl;
}
if (n && retcode)
*retcode = n->errno;
return fp;
}
FILE *
ftpPut(FILE *fp, char *file)
{
FILE *fp2;
FTP_t ftp = fcookie(fp);
check_passive(fp);
if (ftp_file_op(ftp, "STOR", file, &fp2, "w", NULL) == SUCCESS)
return fp2;
return NULL;
}
/* Unlike binary mode, passive mode is a toggle! :-( */
int
ftpPassive(FILE *fp, int st)
{
FTP_t ftp = fcookie(fp);
int i;
if (ftp->is_passive == st)
return SUCCESS;
i = cmd(ftp, "PASV");
if (i < 0)
return i;
ftp->is_passive = !ftp->is_passive;
return SUCCESS;
}
FILE *
ftpGetURL(char *url, char *user, char *passwd, int *retcode)
{
char host[255], name[255];
int port;
FILE *fp2;
static FILE *fp = NULL;
static char *prev_host;
if (retcode)
*retcode = 0;
if (get_url_info(url, host, &port, name) == SUCCESS) {
if (fp && prev_host) {
if (!strcmp(prev_host, host)) {
/* Try to use cached connection */
fp2 = ftpGet(fp, name, NULL);
if (!fp2) {
/* Connection timed out or was no longer valid */
fclose(fp);
free(prev_host);
prev_host = NULL;
}
else
return fp2;
}
else {
/* It's a different host now, flush old */
fclose(fp);
free(prev_host);
prev_host = NULL;
}
}
fp = ftpLogin(host, user, passwd, port, 0, retcode);
if (fp) {
fp2 = ftpGet(fp, name, NULL);
if (!fp2) {
/* Connection timed out or was no longer valid */
if (retcode)
*retcode = ftpErrno(fp);
fclose(fp);
fp = NULL;
}
else
prev_host = strdup(host);
return fp2;
}
}
return NULL;
}
FILE *
ftpPutURL(char *url, char *user, char *passwd, int *retcode)
{
char host[255], name[255];
int port;
static FILE *fp = NULL;
FILE *fp2;
if (retcode)
*retcode = 0;
if (fp) { /* Close previous managed connection */
fclose(fp);
fp = NULL;
}
if (get_url_info(url, host, &port, name) == SUCCESS) {
fp = ftpLogin(host, user, passwd, port, 0, retcode);
if (fp) {
fp2 = ftpPut(fp, name);
if (!fp2) {
if (retcode)
*retcode = ftpErrno(fp);
fclose(fp);
fp = NULL;
}
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->is_binary = FALSE;
ftp->is_passive = FALSE;
ftp->is_verbose = FALSE;
ftp->errno = 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)
{
int i;
i = ftp_close((FTP_t)n);
free(n);
return i;
}
static void
check_passive(FILE *fp)
{
if (getenv("FTP_PASSIVE_MODE"))
ftpPassive(fp, TRUE);
}
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';
if (ftp->is_verbose == TRUE)
fprintf(stderr, "%s\n",buf+4);
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) {
ftp_close(ftp);
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) {
ftp->con_state = quit;
/* Debug("ftp_pkg: in ftp_close(), sending QUIT"); */
i = cmd(ftp, "QUIT");
close(ftp->fd_ctrl);
ftp->fd_ctrl = -1;
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 == init)
return botch("cmd", "open");
strcat(p, "\r\n");
if (ftp->is_verbose)
fprintf(stderr, "Sending: %s", p);
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, int verbose)
{
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);
return FAILURE;
}
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;
ftp->is_verbose = verbose;
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);
if (i > 0)
ftp->errno = i;
return FAILURE;
}
return SUCCESS;
}
static int
ftp_file_op(FTP_t ftp, char *operation, char *file, FILE **fp, char *mode, off_t *seekto)
{
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 ((s = socket(ftp->addrtype, SOCK_STREAM, 0)) < 0)
return FAILURE;
if (ftp->is_passive) {
if (ftp->is_verbose)
fprintf(stderr, "Sending PASV\n");
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)) {
ftp_close(ftp);
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 (seekto && *seekto) {
i = cmd(ftp, "REST %d", *seekto);
if (i < 0 || FTP_TIMEOUT(i)) {
close(s);
ftp->errno = i;
*seekto = (off_t)0;
return i;
}
}
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 (seekto && *seekto) {
i = cmd(ftp, "REST %d", *seekto);
if (i < 0 || FTP_TIMEOUT(i)) {
close(s);
ftp->errno = i;
return i;
}
else if (i != 350)
*seekto = (off_t)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;
}