rpm/ftp.c

572 lines
12 KiB
C

#include "system.h"
#if !defined(HAVE_CONFIG_H)
#define HAVE_MACHINE_TYPES_H 1
#define HAVE_ALLOCA_H 1
#define HAVE_NETINET_IN_SYSTM_H 1
#define HAVE_SYS_SOCKET_H 1
#endif
#ifndef __LCLINT__
#if HAVE_MACHINE_TYPES_H
# include <machine/types.h>
#endif
#endif
#if HAVE_NETINET_IN_SYSTM_H
# include <sys/types.h>
# include <netinet/in_systm.h>
#endif
#if ! HAVE_HERRNO
extern int h_errno;
#endif
#include <stdarg.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include "rpmio.h"
#if !defined(HAVE_INET_ATON)
int inet_aton(const char *cp, struct in_addr *inp);
#endif
#define TIMEOUT_SECS 60
#define BUFFER_SIZE 4096
#ifndef IPPORT_FTP
# define IPPORT_FTP 21
#endif
#if defined(USE_ALT_DNS) && USE_ALT_DNS
#include "dns.h"
#endif
#include "url.h"
#include "ftp.h"
static int ftpDebug = 0;
static int ftpTimeoutSecs = TIMEOUT_SECS;
#ifdef DYING
static int ftpCheckResponse(urlinfo *u, char ** str);
static int ftpCommand(urlinfo *u, char * command, ...);
static int copyData(FD_t sfd, FD_t tfd);
static int getHostAddress(const char * host, struct in_addr * address);
#endif
static int ftpCheckResponse(urlinfo *u, char ** str) {
static char buf[BUFFER_SIZE + 1];
int bufLength = 0;
fd_set emptySet, readSet;
char * chptr, * start;
struct timeval timeout;
int bytesRead, rc = 0;
int doesContinue = 1;
char errorCode[4];
errorCode[0] = '\0';
do {
FD_ZERO(&emptySet);
FD_ZERO(&readSet);
FD_SET(u->ftpControl, &readSet);
timeout.tv_sec = ftpTimeoutSecs;
timeout.tv_usec = 0;
rc = select(u->ftpControl + 1, &readSet, &emptySet, &emptySet, &timeout);
if (rc < 1) {
if (rc==0)
return FTPERR_BAD_SERVER_RESPONSE;
else
rc = FTPERR_UNKNOWN;
} else
rc = 0;
bytesRead = read(u->ftpControl, buf + bufLength, sizeof(buf) - bufLength - 1);
bufLength += bytesRead;
buf[bufLength] = '\0';
/* divide the response into lines, checking each one to see if
we are finished or need to continue */
start = chptr = buf;
do {
while (*chptr != '\n' && *chptr) chptr++;
if (*chptr == '\n') {
*chptr = '\0';
if (*(chptr - 1) == '\r') *(chptr - 1) = '\0';
if (str) *str = start;
if (errorCode[0]) {
if (!strncmp(start, errorCode, 3) && start[3] == ' ')
doesContinue = 0;
} else {
strncpy(errorCode, start, 3);
errorCode[3] = '\0';
if (start[3] != '-') {
doesContinue = 0;
}
}
start = chptr + 1;
chptr++;
} else {
chptr++;
}
} while (*chptr);
if (doesContinue && chptr > start) {
memcpy(buf, start, chptr - start - 1);
bufLength = chptr - start - 1;
} else {
bufLength = 0;
}
} while (doesContinue && !rc);
if (ftpDebug)
fprintf(stderr, "<- %s\n", buf);
if (*errorCode == '4' || *errorCode == '5') {
if (!strncmp(errorCode, "550", 3))
return FTPERR_FILE_NOT_FOUND;
if (!strncmp(errorCode, "552", 3))
return FTPERR_NIC_ABORT_IN_PROGRESS;
return FTPERR_BAD_SERVER_RESPONSE;
}
if (rc) return rc;
return 0;
}
static int ftpCommand(urlinfo *u, char * command, ...) {
va_list ap;
int len;
char * s;
char * buf;
va_start(ap, command);
len = strlen(command) + 2;
s = va_arg(ap, char *);
while (s) {
len += strlen(s) + 1;
s = va_arg(ap, char *);
}
va_end(ap);
buf = alloca(len + 1);
va_start(ap, command);
strcpy(buf, command);
strcat(buf, " ");
s = va_arg(ap, char *);
while (s) {
strcat(buf, s);
strcat(buf, " ");
s = va_arg(ap, char *);
}
va_end(ap);
buf[len - 2] = '\r';
buf[len - 1] = '\n';
buf[len] = '\0';
if (ftpDebug)
fprintf(stderr, "-> %s", buf);
if (write(u->ftpControl, buf, len) != len) {
return FTPERR_SERVER_IO_ERROR;
}
return ftpCheckResponse(u, NULL);
}
#if !defined(USE_ALT_DNS) || !USE_ALT_DNS
static int mygethostbyname(const char * host, struct in_addr * address) {
struct hostent * hostinfo;
hostinfo = gethostbyname(host);
if (!hostinfo) return 1;
memcpy(address, hostinfo->h_addr_list[0], hostinfo->h_length);
return 0;
}
#endif
static int getHostAddress(const char * host, struct in_addr * address) {
if (isdigit(host[0])) {
if (!inet_aton(host, address)) {
return FTPERR_BAD_HOST_ADDR;
}
} else {
if (mygethostbyname(host, address)) {
errno = h_errno;
return FTPERR_BAD_HOSTNAME;
}
}
return 0;
}
int tcpConnect(const char *host, int port)
{
struct sockaddr_in sin;
int sock = -1;
int rc;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = INADDR_ANY;
do {
if ((rc = getHostAddress(host, &sin.sin_addr)) < 0)
break;
if ((sock = socket(sin.sin_family, SOCK_STREAM, IPPROTO_IP)) < 0) {
rc = FTPERR_FAILED_CONNECT;
break;
}
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin))) {
rc = FTPERR_FAILED_CONNECT;
break;
}
} while (0);
if (rc < 0 && sock >= 0) {
close(sock);
return rc;
}
if (ftpDebug)
fprintf(stderr,"++ connect %s:%d on fd %d\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), sock);
return sock;
}
int ftpOpen(urlinfo *u)
{
const char * host;
const char * user;
const char * password;
int port;
int rc;
if (u == NULL || ((host = u->host) == NULL))
return FTPERR_BAD_HOSTNAME;
if ((port = u->port) < 0) port = IPPORT_FTP;
if ((user = u->user) == NULL)
user = "anonymous";
if ((password = u->password) == NULL) {
if (getuid()) {
struct passwd * pw = getpwuid(getuid());
char *myp = alloca(strlen(pw->pw_name) + sizeof("@"));
strcpy(myp, pw->pw_name);
strcat(myp, "@");
password = myp;
} else {
password = "root@";
}
}
if ((u->ftpControl = tcpConnect(host, port)) < 0)
return u->ftpControl;
/* ftpCheckResponse() assumes the socket is nonblocking */
if (fcntl(u->ftpControl, F_SETFL, O_NONBLOCK)) {
close(u->ftpControl);
u->ftpControl = -1;
return FTPERR_FAILED_CONNECT;
}
if ((rc = ftpCheckResponse(u, NULL))) {
return rc;
}
if ((rc = ftpCommand(u, "USER", user, NULL))) {
close(u->ftpControl);
u->ftpControl = -1;
return rc;
}
if ((rc = ftpCommand(u, "PASS", password, NULL))) {
close(u->ftpControl);
u->ftpControl = -1;
return rc;
}
if ((rc = ftpCommand(u, "TYPE", "I", NULL))) {
close(u->ftpControl);
u->ftpControl = -1;
return rc;
}
return u->ftpControl;
}
static int copyData(FD_t sfd, FD_t tfd) {
char buf[BUFFER_SIZE];
fd_set emptySet, readSet;
struct timeval timeout;
int bytesRead;
int bytesCopied = 0;
int rc;
while (1) {
FD_ZERO(&emptySet);
FD_ZERO(&readSet);
FD_SET(fdFileno(sfd), &readSet);
timeout.tv_sec = ftpTimeoutSecs;
timeout.tv_usec = 0;
rc = select(fdFileno(sfd) + 1, &readSet, &emptySet, &emptySet, &timeout);
if (rc == 0) {
rc = FTPERR_SERVER_TIMEOUT;
break;
} else if (rc < 0) {
rc = FTPERR_UNKNOWN;
break;
}
bytesRead = fdRead(sfd, buf, sizeof(buf));
if (bytesRead == 0) {
rc = 0;
break;
}
if (fdWrite(tfd, buf, bytesRead) != bytesRead) {
rc = FTPERR_FILE_IO_ERROR;
break;
}
bytesCopied += bytesRead;
}
if (ftpDebug)
fprintf(stderr, "++ copied %d bytes: %s\n", bytesCopied, ftpStrerror(rc));
fdClose(sfd);
return rc;
}
int ftpAbort(FD_t fd) {
urlinfo *u = (urlinfo *)fd->fd_url;
char buf[BUFFER_SIZE];
int rc;
int tosecs = ftpTimeoutSecs;
if (ftpDebug)
fprintf(stderr, "-> ABOR\n");
sprintf(buf, "%c%c%c", IAC, IP, IAC);
send(u->ftpControl, buf, 3, MSG_OOB);
sprintf(buf, "%cABOR\r\n", DM);
if (write(u->ftpControl, buf, 7) != 7) {
return FTPERR_SERVER_IO_ERROR;
}
if (fdFileno(fd) >= 0) {
while(read(fdFileno(fd), buf, sizeof(buf)) > 0)
;
}
ftpTimeoutSecs = 10;
if ((rc = ftpCheckResponse(u, NULL)) == FTPERR_NIC_ABORT_IN_PROGRESS) {
rc = ftpCheckResponse(u, NULL);
}
rc = ftpCheckResponse(u, NULL);
ftpTimeoutSecs = tosecs;
if (fdFileno(fd) >= 0)
fdClose(fd);
return 0;
}
static int ftpGetFileDone(urlinfo *u) {
if (u->ftpGetFileDoneNeeded && ftpCheckResponse(u, NULL))
return FTPERR_BAD_SERVER_RESPONSE;
u->ftpGetFileDoneNeeded = 0;
return 0;
}
int ftpGetFileDesc(FD_t fd)
{
urlinfo *u;
const char *remotename;
struct sockaddr_in dataAddress;
int i, j;
char * passReply;
char * chptr;
char * retrCommand;
int rc;
u = (urlinfo *)fd->fd_url;
remotename = u->path;
/*
* XXX When ftpGetFileDesc() is called, there may be a lurking
* XXX transfer complete message (if ftpGetFileDone() was not
* XXX called to clear that message). Clear that message now.
*/
if (u->ftpGetFileDoneNeeded)
rc = ftpGetFileDone(u);
if (ftpDebug)
fprintf(stderr, "-> PASV\n");
if (write(u->ftpControl, "PASV\r\n", 6) != 6) {
return FTPERR_SERVER_IO_ERROR;
}
if ((rc = ftpCheckResponse(u, &passReply)))
return FTPERR_PASSIVE_ERROR;
chptr = passReply;
while (*chptr && *chptr != '(') chptr++;
if (*chptr != '(') return FTPERR_PASSIVE_ERROR;
chptr++;
passReply = chptr;
while (*chptr && *chptr != ')') chptr++;
if (*chptr != ')') return FTPERR_PASSIVE_ERROR;
*chptr-- = '\0';
while (*chptr && *chptr != ',') chptr--;
if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
chptr--;
while (*chptr && *chptr != ',') chptr--;
if (*chptr != ',') return FTPERR_PASSIVE_ERROR;
*chptr++ = '\0';
/* now passReply points to the IP portion, and chptr points to the
port number portion */
dataAddress.sin_family = AF_INET;
if (sscanf(chptr, "%d,%d", &i, &j) != 2) {
return FTPERR_PASSIVE_ERROR;
}
dataAddress.sin_port = htons((i << 8) + j);
chptr = passReply;
while (*chptr++) {
if (*chptr == ',') *chptr = '.';
}
if (!inet_aton(passReply, &dataAddress.sin_addr))
return FTPERR_PASSIVE_ERROR;
fd->fd_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (fdFileno(fd) < 0) {
return FTPERR_FAILED_CONNECT;
}
retrCommand = alloca(strlen(remotename) + 20);
sprintf(retrCommand, "RETR %s\r\n", remotename);
i = strlen(retrCommand);
while (connect(fdFileno(fd), (struct sockaddr *) &dataAddress,
sizeof(dataAddress)) < 0) {
if (errno == EINTR)
continue;
fdClose(fd);
return FTPERR_FAILED_DATA_CONNECT;
}
if (ftpDebug)
fprintf(stderr, "-> %s", retrCommand);
if (write(u->ftpControl, retrCommand, i) != i) {
return FTPERR_SERVER_IO_ERROR;
}
if ((rc = ftpCheckResponse(u, NULL))) {
fdClose(fd);
return rc;
}
u ->ftpGetFileDoneNeeded = 1;
return 0;
}
int ftpGetFile(FD_t sfd, FD_t tfd)
{
urlinfo *u;
int rc;
/* XXX sfd will be freed by copyData -- grab sfd->fd_url now */
u = (urlinfo *)sfd->fd_url;
/* XXX normally sfd = ufdOpen(...) and this code does not execute */
if (fdFileno(sfd) < 0 && (rc = ftpGetFileDesc(sfd)) < 0) {
fdClose(sfd);
return rc;
}
rc = copyData(sfd, tfd);
if (rc < 0)
return rc;
return ftpGetFileDone(u);
}
int ftpClose(FD_t fd) {
int fdno = ((urlinfo *)fd->fd_url)->ftpControl;
if (fdno >= 0)
close(fdno);
return 0;
}
const char *ftpStrerror(int errorNumber) {
switch (errorNumber) {
case 0:
return _("Success");
case FTPERR_BAD_SERVER_RESPONSE:
return _("Bad server response");
case FTPERR_SERVER_IO_ERROR:
return _("Server IO error");
case FTPERR_SERVER_TIMEOUT:
return _("Server timeout");
case FTPERR_BAD_HOST_ADDR:
return _("Unable to lookup server host address");
case FTPERR_BAD_HOSTNAME:
return _("Unable to lookup server host name");
case FTPERR_FAILED_CONNECT:
return _("Failed to connect to server");
case FTPERR_FAILED_DATA_CONNECT:
return _("Failed to establish data connection to server");
case FTPERR_FILE_IO_ERROR:
return _("IO error to local file");
case FTPERR_PASSIVE_ERROR:
return _("Error setting remote server to passive mode");
case FTPERR_FILE_NOT_FOUND:
return _("File not found on server");
case FTPERR_NIC_ABORT_IN_PROGRESS:
return _("Abort in progress");
case FTPERR_UNKNOWN:
default:
return _("Unknown or unexpected error");
}
}