22406d98dc
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
806 lines
17 KiB
C
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;
|
|
}
|