diff options
author | Miles Bader <miles@gnu.org> | 1997-05-07 16:22:54 +0000 |
---|---|---|
committer | Miles Bader <miles@gnu.org> | 1997-05-07 16:22:54 +0000 |
commit | d3f310caacfa6a0f943ba67442b7ce69b4d6d716 (patch) | |
tree | 6f8f69ddffa5b1ed2d57eb03cd91302fcb180488 | |
parent | 058a49ce4cc8840bb4af386f5754c3eb466c4422 (diff) |
Initial checkin.
-rw-r--r-- | libftpconn/ChangeLog | 0 | ||||
-rw-r--r-- | libftpconn/Makefile | 31 | ||||
-rw-r--r-- | libftpconn/ftpconn.c | 1068 | ||||
-rw-r--r-- | libftpconn/ftpconn.h | 291 | ||||
-rw-r--r-- | libftpconn/unix.c | 599 |
5 files changed, 1989 insertions, 0 deletions
diff --git a/libftpconn/ChangeLog b/libftpconn/ChangeLog new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/libftpconn/ChangeLog diff --git a/libftpconn/Makefile b/libftpconn/Makefile new file mode 100644 index 00000000..9952de54 --- /dev/null +++ b/libftpconn/Makefile @@ -0,0 +1,31 @@ +# Makefile for libftpconn +# +# Copyright (C) 1997 Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +dir := libftpconn +makemode := library + +libname = libftpconn +installhdrs = ftpconn.h + +SRCS = ftpconn.c unix.c +LCLHDRS = ftpconn.h + +OBJS = $(SRCS:.c=.o) + +include ../Makeconf + diff --git a/libftpconn/ftpconn.c b/libftpconn/ftpconn.c new file mode 100644 index 00000000..fc851163 --- /dev/null +++ b/libftpconn/ftpconn.c @@ -0,0 +1,1068 @@ +/* Manage an ftp connection + + Copyright (C) 1997 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <netdb.h> +#include <netinet/in.h> +#include <pwd.h> + +#include <arpa/telnet.h> + +#include <ftpconn.h> + +/* Ftp reply codes. */ +#define REPLY_DELAY 120 /* Service ready in nnn minutes */ + +#define REPLY_OK 200 /* Command OK */ +#define REPLY_SYSTYPE 215 /* NAME version */ +#define REPLY_HELLO 220 /* Service ready for new user */ +#define REPLY_TRANS_OK 226 /* Closing data connection; requested file + action successful */ +#define REPLY_PASV_OK 227 /* Entering passive mode */ +#define REPLY_LOGIN_OK 230 /* User logged in, proceed */ +#define REPLY_FCMD_OK 250 /* Requested file action okay, completed */ +#define REPLY_DIR_NAME 257 /* "DIR" msg */ + +#define REPLY_NEED_PASS 331 /* User name okay, need password */ +#define REPLY_NEED_ACCT 332 /* Need account for login */ + +#define REPLY_CLOSED 421 /* Service not available, closing control connection */ +#define REPLY_ABORTED 426 /* Connection closed; transfer aborted */ + +#define REPLY_BAD_CMD 500 /* Syntax error; command unrecognized */ +#define REPLY_BAD_ARG 501 /* Synax error in parameters or arguments */ +#define REPLY_UNIMP_CMD 502 /* Command not implemented */ +#define REPLY_UNIMP_ARG 504 /* Command not implemented for that parameter */ + +#define REPLY_NO_LOGIN 530 /* Not logged in */ +#define REPLY_NO_ACCT 532 /* Need account for storing files */ +#define REPLY_NO_SPACE 552 /* Requested file action aborted + Exceeded storage allocation */ + +#define REPLY_IS_PRELIM(rep) ((rep) >= 100 && (rep) < 200) +#define REPLY_IS_SUCCESS(rep) ((rep) >= 200 && (rep) < 300) +#define REPLY_IS_INCOMPLETE(rep) ((rep) >= 300 && (rep) < 400) +#define REPLY_IS_TRANSIENT(rep) ((rep) >= 400 && (rep) < 500) +#define REPLY_IS_FAILURE(rep) ((rep) >= 500 && (rep) < 600) + +static error_t +unexpected_reply (struct ftp_conn *conn, int reply, const char *reply_txt, + const error_t *poss_errs) +{ + if (reply == REPLY_CLOSED) + return EPIPE; + else if (reply == REPLY_UNIMP_CMD || reply == REPLY_UNIMP_ARG) + return EOPNOTSUPP; + else if (reply == REPLY_BAD_ARG) + return EINVAL; + else if (REPLY_IS_FAILURE (reply) && reply_txt + && conn->syshooks.interp_err && poss_errs) + return (*conn->syshooks.interp_err) (conn, reply_txt, poss_errs); + else if (REPLY_IS_TRANSIENT (reply)) + return EAGAIN; + else + return EGRATUITOUS; +} + +/* Add STR (of size LEN) to CONN's reply_txt buffer, at offset *OFFS, + updating *OFFS. */ +static error_t +ftp_conn_add_reply_txt (struct ftp_conn *conn, size_t *offs, + const char *str, size_t len) +{ + if (*offs + len + 1 > conn->reply_txt_sz) + { + size_t new_sz = *offs + len + 50; + char *new = realloc (conn->reply_txt, new_sz); + if (! new) + return ENOMEM; + conn->reply_txt = new; + conn->reply_txt_sz = new_sz; + } + + bcopy (str, conn->reply_txt + *offs, len); + conn->reply_txt[*offs + len] = '\0'; /* Make sure nul terminated. */ + + *offs += len; + + return 0; +} + +/* Return a new line read from CONN's control connection in LINE & LINE_LEN; + LINE points into storage allocated in CONN, and is only valid until the + next call to this function, or return an error code. (we used to just use + the stdio getline function, and keep a stdio stream for the control + connection, but interleaved I/O didn't work correctly.) */ +static error_t +ftp_conn_getline (struct ftp_conn *conn, const char **line, size_t *line_len) +{ + char *l = conn->line; + size_t offs = conn->line_offs, len = conn->line_len, sz = conn->line_sz; + + for (;;) + { + int rd; + + if (offs < len) + /* See if there's a newline in the active part of the line buffer. */ + { + char *nl = memchr (l + offs, '\n', len - offs); + if (nl) + /* There is! Consume and return the whole line we found. */ + { + *line = l + offs; + + offs = nl + 1 - l; /* Consume the line */ + + /* Null terminate the result by overwriting the newline; if + there's a CR preceeding it, get rid of that too. */ + if (nl > *line && nl[-1] == '\r') + nl--; + *nl = '\0'; + + *line_len = nl - *line; + + if (offs == len) + conn->line_offs = conn->line_len = 0; + else + conn->line_offs = offs; + + return 0; + } + } + + /* No newline yet, so read some more! */ + + if (offs > (len << 2) && offs < len) + /* Relocate the current contents of the buffer to the beginning. */ + { + len -= offs; + bcopy (l + offs, l, len - offs); + offs = conn->line_offs = 0; + conn->line_len = len; + } + if (len == sz) + /* Grow the line buffer; there's no space left. */ + { + sz = sz + len ?: 50; + l = realloc (l, sz); + if (! l) + return ENOMEM; + conn->line = l; + conn->line_sz = sz; + } + + /* Actually read something. */ + rd = read (conn->control, l + len, sz - len); + if (rd < 0) + return errno; + else if (rd == 0) + { + *line = l + offs; + *line_len = 0; + return 0; + } + + len += rd; + conn->line_len = len; + } +} + +/* Get the next reply from CONN's ftp server, returning the reply code in + REPLY, if REPLY is non-zero, and the text of the reply (not including the + reply code) in REPLY_TXT (if it isn't zero), or return an error code. If + the reply is multiple lines, all of them are included in REPLY_TXT, + separated by newlines. */ +error_t +ftp_conn_get_reply (struct ftp_conn *conn, int *reply, const char **reply_txt) +{ + size_t reply_txt_offs = 0; /* End of a multi-line reply in accum buf. */ + int multi = 0; /* If a multi-line reply, the reply code. */ + + if (!reply && !reply_txt) + return 0; /* nop */ + + do + { + const char *l; + size_t len; + error_t err = ftp_conn_getline (conn, &l, &len); + + if (err) + return err; + if (!multi && len == 0) + return EPIPE; + +#define ACCUM(txt, len) \ + do { \ + if (reply_txt) /* Only accumulate if wanted. */ \ + { \ + error_t err = ftp_conn_add_reply_txt (conn, &reply_txt_offs, txt, len); \ + if (err) \ + return err; \ + } \ + } while (0) + + if (conn->hooks && conn->hooks->cntl_debug) + (*conn->hooks->cntl_debug) (conn, FTP_CONN_CNTL_DEBUG_REPLY, l); + + if (isdigit (l[0]) && isdigit (l[1]) && isdigit (l[2])) + /* A reply code. */ + { + int code = (l[0] - '0')*100 + (l[1] - '0')*10 + (l[2] - '0'); + + if (multi && code != multi) + /* Two codes in a multi-line reply don't match. */ + return EGRATUITOUS; + + if (l[3] == '-') + /* The non-terminal line of a multi-line reply. RFC959 actually + claims there shouldn't be more than one multi-line code (other + lines in between the two shouldn't have a numeric code at + all), but real ftp servers don't obey this rule. */ + multi = code; + else if (l[3] != ' ') + /* Some syntax error. */ + return EGRATUITOUS; + else + /* The end of the reply (and perhaps the only line). */ + { + multi = 0; + if (reply) + *reply = code; + } + + ACCUM (l + 4, len - 4); + } + else if (multi) + /* The lines between the first and last in a multi-line reply may be + anything as long as they don't start with a digit. */ + ACCUM (l, len); + else + return EGRATUITOUS; + } + while (multi); + + if (reply_txt) + *reply_txt = conn->reply_txt; + + return 0; +} + +/* Version of write that writes all LEN bytes of BUF if possible to FD. */ +static error_t +_write (int fd, const void *buf, size_t len) +{ + while (len > 0) + { + ssize_t wr = write (fd, buf, len); + if (wr < 0) + return errno; + else if (wr == 0) + return EPIPE; + buf += wr; + len -= wr; + } + return 0; +} + +static error_t +_skip_write (int fd, const void *buf, size_t len, size_t *skip) +{ + size_t sk = *skip; + error_t err = 0; + + if (len > sk) + { + err = _write (fd, buf + sk, len - sk); + *skip = 0; + } + else + *skip = sk - len; + + return err; +} + +/* Ridiculous function to deal with the never-to-occur case of the ftp + command being too long for the buffer in ftp_conn_cmd; just writes the + portion of the command that wasn't written there. */ +static error_t +_long_cmd (int fd, const char *cmd, const char *arg, size_t skip) +{ + error_t err = _skip_write (fd, cmd, strlen (cmd), &skip); + if (!err && arg) + { + err = _skip_write (fd, " ", 1, &skip); + if (! err) + err = _skip_write (fd, arg, strlen (arg), &skip); + } + if (! err) + err = _skip_write (fd, "\r\n", 2, &skip); + return err; +} + +/* Send the ftp command CMD, with optional argument ARG (if non-zero) to + CONN's ftp server. If either of REPLY or REPLY_TXT is non-zero, then a + reply is waited for and returned as with ftp_conn_get_reply, otherwise + the next reply from the server is left unconsumed. */ +error_t +ftp_conn_cmd (struct ftp_conn *conn, const char *cmd, const char *arg, + int *reply, const char **reply_txt) +{ + error_t err = 0; + + if (conn->control < 0) + err = EPIPE; + else + /* (This used to try to call dprintf to output to conn->control, but that + function doesn't appear to work.) */ + { + char buf[200]; + size_t out = + snprintf (buf, sizeof buf, arg ? "%s %s\r\n" : "%s\r\n", cmd, arg); + err = _write (conn->control, buf, out); + + if (!err && conn->hooks && conn->hooks->cntl_debug) + { + buf[out - 2] = '\0'; /* Stomp the CR & NL. */ + (* conn->hooks->cntl_debug) (conn, FTP_CONN_CNTL_DEBUG_CMD, buf); + } + + if (!err && out == sizeof buf) + err = _long_cmd (conn->control, cmd, arg, sizeof buf); + } + + if (!err && (reply || reply_txt)) + err = ftp_conn_get_reply (conn, reply, reply_txt); + + return err; +} + +/* Send an ftp command to CONN's server, and optionally await a reply as with + ftp_conn_cmd, but also open a new connection if it appears that the old + one has died (as when the ftp server times it out). */ +error_t +ftp_conn_cmd_reopen (struct ftp_conn *conn, const char *cmd, const char *arg, + int *reply, const char **reply_txt) +{ + int _reply; + error_t err; + + err = ftp_conn_cmd (conn, cmd, arg, &_reply, reply_txt); + if (err == EPIPE || (!err && _reply == REPLY_CLOSED)) + /* Retry once after reopening the connection. */ + { + err = ftp_conn_open (conn); + if (! err) + err = ftp_conn_cmd (conn, cmd, arg, reply, reply_txt); + } + else if (reply) + *reply = _reply; + + return err; +} + +/* Send an ftp ABOR command to CONN's server, aborting any transfer in + progress. */ +void +ftp_conn_abort (struct ftp_conn *conn) +{ + if (conn->control >= 0) + { + static const char ip[] = { IAC, IP, IAC }; + static const char abor[] = { DM, 'a', 'b', 'o', 'r', '\r', '\n' }; + + if (conn->hooks && conn->hooks->cntl_debug) + (* conn->hooks->cntl_debug) (conn, FTP_CONN_CNTL_DEBUG_CMD, "abor"); + + if (send (conn->control, ip, sizeof ip, MSG_OOB) == sizeof ip + && write (conn->control, abor, sizeof abor) == sizeof abor) + { + int reply; + error_t err; + do + err = ftp_conn_get_reply (conn, &reply, 0); + while (reply == REPLY_ABORTED); + if (reply != REPLY_TRANS_OK) + ftp_conn_close (conn); + } + else + ftp_conn_close (conn); + } +} + +error_t +ftp_conn_login (struct ftp_conn *conn) +{ + int reply; + error_t err = 0; + const struct ftp_conn_params *p = conn->params; + + err = ftp_conn_cmd (conn, "user", p->user ?: "anonymous", &reply, 0); + + if (!err && reply == REPLY_NEED_ACCT) + { + char *acct = p->acct; + if (!acct && conn->hooks && conn->hooks->get_login_param) + err = (* conn->hooks->get_login_param) (conn, + FTP_CONN_GET_LOGIN_PARAM_ACCT, + &acct); + if (! err) + err = acct ? ftp_conn_cmd (conn, "acct", acct, &reply, 0) : EACCES; + if (acct && !p->acct) + free (acct); + } + + if (!err && reply == REPLY_NEED_PASS) + { + char *pass = p->pass; + if (!pass && conn->hooks && conn->hooks->get_login_param) + err = (* conn->hooks->get_login_param) (conn, + FTP_CONN_GET_LOGIN_PARAM_PASS, + &pass); + if (! err) + if (pass) + err = ftp_conn_cmd (conn, "pass", pass, &reply, 0); + else + { + pass = getenv ("USER"); + if (pass) + pass = getenv ("LOGNAME"); + if (pass) + { + struct passwd *pe = getpwuid (getuid ()); + pass = pe ? pe->pw_name : "?"; + } + + /* Append a '@' */ + pass = strdup (pass); + if (pass) + pass = realloc (pass, strlen (pass) + 1); + if (pass) + { + strcat (pass, "@"); + err = ftp_conn_cmd (conn, "pass", pass, &reply, 0); + } + else + err = ENOMEM; + } + if (pass && !p->pass) + free (pass); + } + + if (!err && reply != REPLY_LOGIN_OK) + if (REPLY_IS_FAILURE (reply)) + err = EACCES; + else + err = unexpected_reply (conn, reply, 0, 0); + + return err; +} + +static error_t +ftp_conn_hello (struct ftp_conn *conn) +{ + int reply; + error_t err; + + do + err = ftp_conn_get_reply (conn, &reply, 0); + while (!err && reply == REPLY_DELAY); + + if (err) + return err; + + if (reply == REPLY_CLOSED) + return ECONNREFUSED; + if (reply != REPLY_HELLO) + return EGRATUITOUS; + + return 0; +} + +/* Sets CONN's syshooks to a copy of SYSHOOKS. */ +void +ftp_conn_set_syshooks (struct ftp_conn *conn, struct ftp_conn_syshooks *syshooks) +{ + conn->syshooks = *syshooks; +} + +void +ftp_conn_choose_syshooks (struct ftp_conn *conn, const char *syst) +{ + if (!syst || (strncasecmp (syst, "UNIX", 4) == 0 && !isalnum (syst[4]))) + ftp_conn_set_syshooks (conn, &ftp_conn_unix_syshooks); +} + +/* Sets CONN's syshooks by querying the remote system to see what type it is. */ +static error_t +ftp_conn_sysify (struct ftp_conn *conn) +{ + int reply; + const char *txt; + error_t err = ftp_conn_cmd (conn, "syst", 0, &reply, &txt); + + if (! err) + if (reply == REPLY_SYSTYPE || reply == REPLY_BAD_CMD) + { + if (reply == REPLY_BAD_CMD) + txt = 0; + if (conn->hooks && conn->hooks->choose_syshooks) + (*conn->hooks->choose_syshooks) (conn, txt); + else + ftp_conn_choose_syshooks (conn, txt); + } + else + err = unexpected_reply (conn, reply, txt, 0); + + return err; +} + +error_t +ftp_conn_open (struct ftp_conn *conn) +{ + static int ftp_port = 0; + int csock; + error_t err; + struct sockaddr_in ftp_addr; + + if (conn->params->addr_type != AF_INET) + return EAFNOSUPPORT; + + if (! ftp_port) + { + struct servent *se = getservbyname ("ftp", "tcp"); + if (! se) + return EGRATUITOUS; + ftp_port = se->s_port; + } + + if (conn->control >= 0) + { + close (conn->control); + conn->control = -1; + } + bzero (&conn->syshooks, sizeof conn->syshooks); + + csock = socket (PF_INET, SOCK_STREAM, 0); + if (csock < 0) + return errno; + + ftp_addr.sin_len = conn->params->addr_len; + ftp_addr.sin_family = conn->params->addr_type; + ftp_addr.sin_addr = *(struct in_addr *)conn->params->addr; + ftp_addr.sin_port = ftp_port; + + if (connect (csock, &ftp_addr, sizeof ftp_addr) < 0) + { + err = errno; + close (csock); + return err; + } + + conn->control = csock; + + err = ftp_conn_hello (conn); + + if (!err && conn->hooks && conn->hooks->opened) + (* conn->hooks->opened) (conn); + + if (! err) + /* Make any machine-dependent customizations. */ + err = ftp_conn_sysify (conn); + + if (! err) + /* login */ + err = ftp_conn_login (conn); + + if (!err && conn->type) + /* Set the connection type. */ + { + int reply; + err = ftp_conn_cmd (conn, "type", conn->type, &reply, 0); + if (!err && reply != REPLY_OK) + err = unexpected_reply (conn, reply, 0, 0); + } + + if (err) + ftp_conn_close (conn); + + return err; +} + +void +ftp_conn_close (struct ftp_conn *conn) +{ + if (conn->control >= 0) + close (conn->control); + conn->control = -1; + if (conn->hooks && conn->hooks->closed) + (* conn->hooks->closed) (conn); +} + +error_t +ftp_conn_create (const struct ftp_conn_params *params, + const struct ftp_conn_hooks *hooks, + struct ftp_conn **conn) +{ + error_t err; + struct ftp_conn *new = malloc (sizeof (struct ftp_conn)); + + if (! new) + return ENOMEM; + + new->control = -1; + new->line = 0; + new->line_sz = 0; + new->line_offs = 0; + new->line_len = 0; + new->reply_txt = 0; + new->reply_txt_sz = 0; + new->params = params; + new->hooks = hooks; + new->cwd = 0; + new->type = 0; + bzero (&new->syshooks, sizeof new->syshooks); + + if (new->hooks && new->hooks->init) + err = (*new->hooks->init) (new); + else + err = 0; + + if (! err) + err = ftp_conn_open (new); + + if (err) + ftp_conn_free (new); + else + *conn = new; + + return err; + +} + +void +ftp_conn_free (struct ftp_conn *conn) +{ + ftp_conn_close (conn); + if (conn->hooks && conn->hooks->fini) + (* conn->hooks->fini) (conn); + if (conn->line) + free (conn->line); + if (conn->reply_txt) + free (conn->reply_txt); + free (conn); +} + +static error_t +ftp_conn_get_pasv_addr (struct ftp_conn *conn, struct sockaddr **addr) +{ + int reply; + const char *txt; + error_t err = ftp_conn_cmd_reopen (conn, "pasv", 0, &reply, &txt); + + if (! err) + if (reply == REPLY_PASV_OK) + err = (*(conn->syshooks.pasv_addr ?: ftp_conn_unix_pasv_addr)) (conn, txt, addr); + else + err = unexpected_reply (conn, reply, txt, 0); + + return err; +} + +static error_t +ftp_conn_send_actv_addr (struct ftp_conn *conn, struct sockaddr *addr) +{ + error_t err; + + if (addr == 0) + err = EINVAL; + else if (addr->sa_family != AF_INET) + err = EAFNOSUPPORT; + else + { + char buf[50]; + int reply; + unsigned char *a = + (unsigned char *)&((struct sockaddr_in *)addr)->sin_addr.s_addr; + unsigned char *p = + (unsigned char *)&((struct sockaddr_in *)addr)->sin_port; + + snprintf (buf, sizeof buf, "%d,%d,%d,%d,%d,%d", + a[0], a[1], a[2], a[3], p[0], p[1]); + err = ftp_conn_cmd_reopen (conn, "port", buf, &reply, 0); + + if (! err) + if (reply == REPLY_OK) + err = 0; + else + err = unexpected_reply (conn, reply, 0, 0); + } + + return err; +} + +/* Open a data connection, returning the file descriptor in DATA. */ +static error_t +ftp_conn_open_data (struct ftp_conn *conn, int *data) +{ + struct sockaddr *addr; + error_t err = ftp_conn_get_pasv_addr (conn, &addr); + + if (! err) + { + int dsock = socket (PF_INET, SOCK_STREAM, 0); + + if (dsock < 0) + err = errno; + else if (connect (dsock, addr, addr->sa_len) < 0) + { + err = errno; + close (dsock); + } + else + *data = dsock; + + free (addr); + } + + return err; +} + +/* Start a transfer command CMD/ARG, returning a file descriptor in DATA. + POSS_ERRS is a list of errnos to try matching against any resulting error + text. */ +error_t +ftp_conn_start_transfer (struct ftp_conn *conn, + const char *cmd, const char *arg, + const error_t *poss_errs, + int *data) +{ + error_t err = ftp_conn_open_data (conn, data); + + if (! err) + { + int reply; + const char *txt; + + err = ftp_conn_cmd (conn, cmd, arg, &reply, &txt); + if (!err && !REPLY_IS_PRELIM (reply)) + err = unexpected_reply (conn, reply, txt, poss_errs); + + if (err) + close (*data); + } + + return err; +} + +/* Wait for the reply signalling the end of a data transfer. */ +error_t +ftp_conn_finish_transfer (struct ftp_conn *conn) +{ + int reply; + error_t err = ftp_conn_get_reply (conn, &reply, 0); + if (!err && reply != REPLY_TRANS_OK && reply != REPLY_FCMD_OK) + err = unexpected_reply (conn, reply, 0, 0); + return err; +} + +static const error_t poss_file_errs[] = { + EIO, ENOENT, EPERM, EACCES, ENOTDIR, ENAMETOOLONG, ELOOP, EISDIR, EROFS, + EMFILE, ENFILE, ENXIO, EOPNOTSUPP, ENOSPC, EDQUOT, ETXTBSY, EEXIST, + 0 +}; + +/* Start retreiving file NAME over CONN, returning a file descriptor in DATA + over which the data can be read. */ +error_t +ftp_conn_start_retrieve (struct ftp_conn *conn, const char *name, int *data) +{ + if (! name) + return EINVAL; + return ftp_conn_start_transfer (conn, "retr", name, poss_file_errs, data); +} + +/* Start retreiving a list of files in NAME over CONN, returning a file + descriptor in DATA over which the data can be read. */ +error_t +ftp_conn_start_list (struct ftp_conn *conn, const char *name, int *data) +{ + return ftp_conn_start_transfer (conn, "nlst", name, poss_file_errs, data); +} + +/* Start retreiving a directory listing of NAME over CONN, returning a file + descriptor in DATA over which the data can be read. */ +error_t +ftp_conn_start_dir (struct ftp_conn *conn, const char *name, int *data) +{ + return ftp_conn_start_transfer (conn, "list", name, poss_file_errs, data); +} + +/* Start storing into file NAME over CONN, returning a file descriptor in DATA + into which the data can be written. */ +error_t +ftp_conn_start_store (struct ftp_conn *conn, const char *name, int *data) +{ + if (! name) + return EINVAL; + return ftp_conn_start_transfer (conn, "stor", name, poss_file_errs, data); +} + +/* Transfer the output of SRC_CMD/SRC_NAME on SRC_CONN to DST_NAME on + DST_CONN, moving the data directly between servers. */ +error_t +ftp_conn_rmt_transfer (struct ftp_conn *src_conn, + const char *src_cmd, const char *src_name, + const int *src_poss_errs, + struct ftp_conn *dst_conn, const char *dst_name) +{ + struct sockaddr *src_addr; + error_t err = ftp_conn_get_pasv_addr (src_conn, &src_addr); + + if (! err) + { + err = ftp_conn_send_actv_addr (dst_conn, src_addr); + + if (! err) + { + int reply; + const char *txt; + err = ftp_conn_cmd (src_conn, src_cmd, src_name, 0, 0); + + if (! err) + { + err = ftp_conn_cmd (dst_conn, "stor", dst_name, &reply, &txt); + + if (! err) + if (REPLY_IS_PRELIM (reply)) + { + err = ftp_conn_get_reply (src_conn, &reply, &txt); + if (!err && !REPLY_IS_PRELIM (reply)) + err = unexpected_reply (src_conn, reply, txt, src_poss_errs); + + if (err) + ftp_conn_abort (dst_conn); + else + err = ftp_conn_finish_transfer (dst_conn); + } + else + err = unexpected_reply (dst_conn, reply, txt, poss_file_errs); + + if (err) + ftp_conn_abort (src_conn); + else + err = ftp_conn_finish_transfer (src_conn); + } + } + + free (src_addr); + } + + return err; +} + +/* Copy the SRC_NAME on SRC_CONN to DST_NAME on DST_CONN, moving the data + directly between servers. */ +error_t +ftp_conn_rmt_copy (struct ftp_conn *src_conn, const char *src_name, + struct ftp_conn *dst_conn, const char *dst_name) +{ + return ftp_conn_rmt_transfer (src_conn, "retr", src_name, poss_file_errs, + dst_conn, dst_name); +} + +static error_t +_cache_cwd (struct ftp_conn *conn, int reopen) +{ + int reply; + const char *txt; + error_t err = + (reopen ? ftp_conn_cmd_reopen : ftp_conn_cmd) (conn, "pwd", 0, &reply, &txt); + + if (! err) + if (reply == REPLY_DIR_NAME) + { + char *cwd = malloc (strlen (txt)); + if (! cwd) + err = ENOMEM; + else if (sscanf (txt, "\"%[^\"]\"", cwd) != 1) + err = EGRATUITOUS; + else + { + if (conn->cwd) + free (conn->cwd); + conn->cwd = cwd; + } + } + else + err = unexpected_reply (conn, reply, txt, 0); + + return err; +} + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t +ftp_conn_get_cwd (struct ftp_conn *conn, char **cwd) +{ + error_t err = 0; + if (! conn->cwd) + err = _cache_cwd (conn, 1); + if (! err) + { + *cwd = strdup (conn->cwd); + if (! *cwd) + err = ENOMEM; + } + return err; +} + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t +ftp_conn_cwd (struct ftp_conn *conn, const char *cwd) +{ + error_t err = 0; + if (conn->cwd && strcmp (conn->cwd, cwd) == 0) + err = 0; + else + { + int reply; + const char *txt; + err = ftp_conn_cmd_reopen (conn, "cwd", cwd, &reply, &txt); + if (! err) + if (reply == REPLY_FCMD_OK) + err = _cache_cwd (conn, 0); + else + err = unexpected_reply (conn, reply, txt, poss_file_errs); + } + return err; +} + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t +ftp_conn_cdup (struct ftp_conn *conn) +{ + int reply; + const char *txt; + error_t err = ftp_conn_cmd_reopen (conn, "cdup", 0, &reply, &txt); + if (! err) + if (reply == REPLY_OK) + err = _cache_cwd (conn, 0); + else + err = unexpected_reply (conn, reply, txt, poss_file_errs); + return err; +} + +/* Set the ftp connection type of CONN to TYPE, or return an error. */ +error_t +ftp_conn_set_type (struct ftp_conn *conn, const char *type) +{ + error_t err = 0; + + if (! type) + return EINVAL; + + if (!conn->type || strcmp (type, conn->type) != 0) + { + type = strdup (type); + if (! type) + err = ENOMEM; + else + { + int reply; + error_t err = ftp_conn_cmd_reopen (conn, "type", type, &reply, 0); + if (! err) + if (reply == REPLY_OK) + { + if (conn->type) + free ((char *)conn->type); + conn->type = type; + } + else + err = unexpected_reply (conn, reply, 0, 0); + } + } + + return err; +} + +/* Start an operation to get a list of file-stat structures for NAME (this + is often similar to ftp_conn_start_dir, but with OS-specific flags), and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_stats. FORCE_DIR controls what happens if + NAME refers to a directory: if FORCE_DIR is false, STATS will contain + entries for all files *in* NAME, and if FORCE_DIR is true, it will + contain just a single entry for NAME itself (or an error will be + returned when this isn't possible). */ +error_t +ftp_conn_start_get_stats (struct ftp_conn *conn, + const char *name, int force_dir, + int *fd, void **state) +{ + if (conn->syshooks.start_get_stats) + return + (*conn->syshooks.start_get_stats) (conn, name, force_dir, fd, state); + else + return EOPNOTSUPP; +} + +/* Read stats information from FD, calling ADD_STAT for each new stat (HOOK + is passed to ADD_STAT). FD and STATE should be returned from + start_get_stats. If this function returns EAGAIN, then it should be + called again to finish the job (possibly after calling select on FD); if + it returns 0, then it is finishe,d and FD and STATE are deallocated. */ +error_t +ftp_conn_cont_get_stats (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_stat_fun_t add_stat, void *hook) +{ + if (conn->syshooks.cont_get_stats) + return (*conn->syshooks.cont_get_stats) (conn, fd, state, add_stat, hook); + else + return EOPNOTSUPP; +} + +/* Get a list of file-stat structures for NAME, calling ADD_STAT for each one + (HOOK is passed to ADD_STAT). If NAME refers to an ordinary file, a + single entry for it is returned for it; if NAME refers to a directory, + then if FORCE_DIR is false, STATS will contain entries for all files *in* + NAME, and if FORCE_DIR is true, it will contain just a single entry for + NAME itself (or an error will be returned when this isn't possible). This + function may block. */ +error_t +ftp_conn_get_stats (struct ftp_conn *conn, + const char *name, int force_dir, + ftp_conn_add_stat_fun_t add_stat, void *hook) +{ + int fd; + void *state; + error_t err = ftp_conn_start_get_stats (conn, name, force_dir, &fd, &state); + + if (err) + return err; + + do + err = ftp_conn_cont_get_stats (conn, fd, state, add_stat, hook); + while (err == EAGAIN); + + return err; +} + diff --git a/libftpconn/ftpconn.h b/libftpconn/ftpconn.h new file mode 100644 index 00000000..221a10dd --- /dev/null +++ b/libftpconn/ftpconn.h @@ -0,0 +1,291 @@ +/* Manage an ftp connection + + Copyright (C) 1997 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#ifndef __FTPCONN_H__ +#define __FTPCONN_H__ + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +struct ftp_conn; +struct ftp_conn_params; +struct ftp_conn_stat; + +/* The type of the function called by ...get_stats to add each new stat. + NAME is the file in question, STAT is stat info about it, and if NAME is a + symlink, SYMLINK_TARGET is what it is linked to, or 0 if it's not a symlink. + NAME is malloced and should be freed by the callee if it's not saved. + HOOK is as passed into ...get_stats. */ +typedef error_t (*ftp_conn_add_stat_fun_t) (char *name, struct stat *stat, + char *symlink_target, + void *hook); + +/* Hooks that customize behavior for particular types of remote system. */ +struct ftp_conn_syshooks +{ + /* Should return in ADDR a malloced struct sockaddr containing the address + of the host referenced by the PASV reply contained in TXT. */ + error_t (*pasv_addr) (struct ftp_conn *conn, const char *txt, + struct sockaddr **addr); + + /* Look at the error string in TXT, and try to guess an error code to + return. If POSS_ERRS is non-zero, it contains a list of errors + that are likely to occur with the previous command, terminated with 0. + If no match is found and POSS_ERRS is non-zero, the first error in + POSS_ERRS should be returned by default. */ + error_t (*interp_err) (struct ftp_conn *conn, const char *txt, + const error_t *poss_errs); + + /* Start an operation to get a list of file-stat structures for NAME (this + is often similar to ftp_conn_start_dir, but with OS-specific flags), and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_stats. FORCE_DIR controls what happens + if NAME refers to a directory: if FORCE_DIR is false, STATS will contain + entries for all files *in* NAME, and if FORCE_DIR is true, it will + contain just a single entry for NAME itself (or an error will be + returned when this isn't possible). */ + error_t (*start_get_stats) (struct ftp_conn *conn, const char *name, + int force_dir, + int *fd, void **state); + + /* Read stats information from FD, calling ADD_STAT for each new stat (HOOK + is passed to ADD_STAT). FD and STATE should be returned from + start_get_stats. If this function returns EAGAIN, then it should be + called again to finish the job (possibly after calling select on FD); if + it returns 0, then it is finishe,d and FD and STATE are deallocated. */ + error_t (*cont_get_stats) (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_stat_fun_t add_stat, void *hook); +}; + +/* Type parameter for the cntl_debug hook. */ +#define FTP_CONN_CNTL_DEBUG_CMD 1 +#define FTP_CONN_CNTL_DEBUG_REPLY 2 + +/* Type parameter for the get_login_param hook. */ +#define FTP_CONN_GET_LOGIN_PARAM_USER 1 +#define FTP_CONN_GET_LOGIN_PARAM_PASS 2 +#define FTP_CONN_GET_LOGIN_PARAM_ACCT 3 + +/* General connection customization. */ +struct ftp_conn_hooks +{ + /* If non-zero, should look at the SYST reply in SYST, and fill in CONN's + syshooks (with ftp_conn_set_hooks) appropriately; SYST may be zero if + the remote system doesn't support that command. If zero, then the + default ftp_conn_choose_syshooks is used. */ + void (*choose_syshooks) (struct ftp_conn *conn, const char *syst); + + /* If non-zero, called during io on the ftp control connection -- TYPE is + FTP_CONN_CNTL_DEBUG_CMD for commands, and FTP_CONN_CNTL_DEBUG_REPLY for + replies; TXT is the actual text. */ + void (*cntl_debug) (struct ftp_conn *conn, int type, const char *txt); + + /* Called after CONN's connection the server has been opened (or reopened). */ + void (*opened) (struct ftp_conn *conn); + + /* If the remote system requires some login parameter that isn't available, + this hook is called to try and get it, returning a value in TXT. The + return value should be in a malloced block of memory. The returned + value will only be used once; if it's desired that it should `stick', + the user may modify the value stored in CONN's params field, but that is + an issue outside of the scope of this interface -- params are only read, + never written. */ + error_t (*get_login_param) (struct ftp_conn *conn, int type, char **txt); + + /* Called after CONN's connection the server has closed for some reason. */ + void (*closed) (struct ftp_conn *conn); + + /* Called when CONN is initially created before any other hook calls. An + error return causes the creation to fail with that error code. */ + error_t (*init) (struct ftp_conn *conn); + + /* Called when CONN is about to be destroyed. No hook calls are ever made + after this one. */ + void (*fini) (struct ftp_conn *conn); +}; + +/* A single ftp connection. */ +struct ftp_conn +{ + const struct ftp_conn_params *params; /* machine, user, &c */ + const struct ftp_conn_hooks *hooks; /* Customization hooks. */ + + struct ftp_conn_syshooks syshooks; /* host-dependent hook functions */ + + int control; /* fd for ftp control connection */ + + char *line; /* buffer for reading control replies */ + size_t line_sz; /* allocated size of LINE */ + size_t line_offs; /* Start of unread input in LINE. */ + size_t line_len; /* End of the contents in LINE. */ + + char *reply_txt; /* A buffer for the text of entire replies */ + size_t reply_txt_sz; /* size of it */ + + char *cwd; /* Last know CWD, or 0 if unknown. */ + const char *type; /* Connection type, or 0 if default. */ + + void *hook; /* Random user data. */ +}; + +/* Parameters for an ftp connection; doesn't include any actual connection + state. */ +struct ftp_conn_params +{ + void *addr; /* Address. */ + size_t addr_len; /* Length in bytes of ADDR. */ + int addr_type; /* Type of ADDR (AF_*). */ + + char *user, *pass, *acct; /* Parameters for logging into ftp. */ +}; + +/* Unix hooks */ +extern error_t ftp_conn_unix_pasv_addr (struct ftp_conn *conn, const char *txt, + struct sockaddr **addr); +extern error_t ftp_conn_unix_interp_err (struct ftp_conn *conn, const char *txt, + error_t *poss_errs); +extern error_t ftp_conn_unix_start_get_stats (struct ftp_conn *conn, + const char *name, + int force_dir, int *fd, + void **state); +extern error_t ftp_conn_unix_cont_get_stats (struct ftp_conn *conn, + int fd, void *state, + ftp_conn_add_stat_fun_t add_stat, + void *hook); + +extern struct ftp_conn_syshooks ftp_conn_unix_syshooks; + +error_t +ftp_conn_get_reply (struct ftp_conn *conn, int *reply, const char **reply_txt); + +error_t +ftp_conn_cmd (struct ftp_conn *conn, const char *cmd, const char *arg, + int *reply, const char **reply_txt); + +error_t +ftp_conn_cmd_reopen (struct ftp_conn *conn, const char *cmd, const char *arg, + int *reply, const char **reply_txt); + +void ftp_conn_abort (struct ftp_conn *conn); + +/* Sets CONN's syshooks to a copy of SYSHOOKS. */ +void ftp_conn_set_syshooks (struct ftp_conn *conn, + struct ftp_conn_syshooks *syshooks); + +error_t ftp_conn_open (struct ftp_conn *conn); + +void ftp_conn_close (struct ftp_conn *conn); + +error_t ftp_conn_create (const struct ftp_conn_params *params, + const struct ftp_conn_hooks *hooks, + struct ftp_conn **conn); + +void ftp_conn_free (struct ftp_conn *conn); + +/* Start a transfer command CMD (and optional args ...), returning a file + descriptor in DATA. POSS_ERRS is a list of errnos to try matching + against any resulting error text. */ +error_t +ftp_conn_start_transfer (struct ftp_conn *conn, + const char *cmd, const char *arg, + error_t *poss_errs, + int *data); + +/* Wait for the reply signalling the end of a data transfer. */ +error_t ftp_conn_finish_transfer (struct ftp_conn *conn); + +/* Start retreiving file NAME over CONN, returning a file descriptor in DATA + over which the data can be read. */ +error_t ftp_conn_start_retrieve (struct ftp_conn *conn, const char *name, int *data); + +/* Start retreiving a list of files in NAME over CONN, returning a file + descriptor in DATA over which the data can be read. */ +error_t ftp_conn_start_list (struct ftp_conn *conn, const char *name, int *data); + +/* Start retreiving a directory listing of NAME over CONN, returning a file + descriptor in DATA over which the data can be read. */ +error_t ftp_conn_start_dir (struct ftp_conn *conn, const char *name, int *data); + +/* Start storing into file NAME over CONN, returning a file descriptor in DATA + into which the data can be written. */ +error_t ftp_conn_start_store (struct ftp_conn *conn, const char *name, int *data); + +/* Transfer the output of SRC_CMD/SRC_NAME on SRC_CONN to DST_NAME on + DST_CONN, moving the data directly between servers. */ +error_t +ftp_conn_rmt_transfer (struct ftp_conn *src_conn, + const char *src_cmd, const char *src_name, + const int *src_poss_errs, + struct ftp_conn *dst_conn, const char *dst_name); + +/* Copy the SRC_NAME on SRC_CONN to DST_NAME on DST_CONN, moving the data + directly between servers. */ +error_t +ftp_conn_rmt_copy (struct ftp_conn *src_conn, const char *src_name, + struct ftp_conn *dst_conn, const char *dst_name); + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t ftp_conn_get_cwd (struct ftp_conn *conn, char **cwd); + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t ftp_conn_cwd (struct ftp_conn *conn, const char *cwd); + +/* Return a malloced string containing CONN's working directory in CWD. */ +error_t ftp_conn_cdup (struct ftp_conn *conn); + +/* Set the ftp connection type of CONN to TYPE, or return an error. */ +error_t ftp_conn_set_type (struct ftp_conn *conn, const char *type); + +/* Start an operation to get a list of file-stat structures for NAME (this + is often similar to ftp_conn_start_dir, but with OS-specific flags), and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_stats. FORCE_DIR controls what happens if + NAME refers to a directory: if FORCE_DIR is false, STATS will contain + entries for all files *in* NAME, and if FORCE_DIR is true, it will + contain just a single entry for NAME itself (or an error will be + returned when this isn't possible). */ +error_t ftp_conn_start_get_stats (struct ftp_conn *conn, + const char *name, int force_dir, + int *fd, void **state); + +/* Read stats information from FD, calling ADD_STAT for each new stat (HOOK + is passed to ADD_STAT). FD and STATE should be returned from + start_get_stats. If this function returns EAGAIN, then it should be + called again to finish the job (possibly after calling select on FD); if + it returns 0, then it is finishe,d and FD and STATE are deallocated. */ +error_t ftp_conn_cont_get_stats (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_stat_fun_t add_stat, void *hook); + +/* Get a list of file-stat structures for NAME, calling ADD_STAT for each one + (HOOK is passed to ADD_STAT). If NAME refers to an ordinary file, a + single entry for it is returned for it; if NAME refers to a directory, + then if FORCE_DIR is false, STATS will contain entries for all files *in* + NAME, and if FORCE_DIR is true, it will contain just a single entry for + NAME itself (or an error will be returned when this isn't possible). This + function may block. */ +error_t ftp_conn_get_stats (struct ftp_conn *conn, + const char *name, int force_dir, + ftp_conn_add_stat_fun_t add_stat, void *hook); + +#endif /* __FTPCONN_H__ */ diff --git a/libftpconn/unix.c b/libftpconn/unix.c new file mode 100644 index 00000000..52cfc105 --- /dev/null +++ b/libftpconn/unix.c @@ -0,0 +1,599 @@ +/* Unix-specific ftpconn hooks + + Copyright (C) 1997 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <unistd.h> +#include <string.h> +#include <ctype.h> +#include <time.h> +#include <errno.h> +#include <pwd.h> +#include <grp.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <hurd/hurd_types.h> + +#include <ftpconn.h> + +/* Uid/gid to use when we don't know about a particular user name. */ +#define DEFAULT_UID 65535 +#define DEFAULT_GID 65535 + +struct ftp_conn_syshooks ftp_conn_unix_syshooks = { + ftp_conn_unix_pasv_addr, ftp_conn_unix_interp_err, + ftp_conn_unix_start_get_stats, ftp_conn_unix_cont_get_stats +}; + +/* Try to get an internet address out of the reply to a PASV command. + Unfortunately, the format of the reply such isn't standardized. */ +error_t +ftp_conn_unix_pasv_addr (struct ftp_conn *conn, const char *txt, + struct sockaddr **addr) +{ + unsigned a0, a1, a2, a3; /* Parts of the inet address */ + unsigned p0, p1; /* Parts of the prot (msb, lsb) */ + + if (sscanf (txt, "%*[^0-9]%d,%d,%d,%d,%d,%d", &a0,&a1,&a2,&a3, &p0,&p1) != 6) + return EGRATUITOUS; + else + { + unsigned char *a, *p; + + *addr = malloc (sizeof (struct sockaddr_in)); + if (! *addr) + return ENOMEM; + + (*addr)->sa_len = sizeof (struct sockaddr_in); + (*addr)->sa_family = AF_INET; + + a = (unsigned char *)&((struct sockaddr_in *)*addr)->sin_addr.s_addr; + a[0] = a0 & 0xff; + a[1] = a1 & 0xff; + a[2] = a2 & 0xff; + a[3] = a3 & 0xff; + + p = (unsigned char *)&((struct sockaddr_in *)*addr)->sin_port; + p[0] = p0 & 0xff; + p[1] = p1 & 0xff; + + return 0; + } +} + +/* Compare strings P & Q in a most forgiving manner, ignoring case and + everything but alphanumeric characters. */ +static int +strlaxcmp (const char *p, const char *q) +{ + for (;;) + { + int ch1, ch2; + + while (*p && !isalnum (*p)) + p++; + while (*q && !isalnum (*q)) + q++; + + if (!*p || !*q) + break; + + ch1 = tolower (*p); + ch2 = tolower (*q); + if (ch1 != ch2) + break; + + p++; + q++; + } + + return *p - *q; +} + +/* Try to convert an error message in TXT into an error code. POSS_ERRS + contains a list of likely errors to try; if no other clue is found, the + first thing in poss_errs is returned. */ +error_t ftp_conn_unix_interp_err (struct ftp_conn *conn, const char *txt, + const error_t *poss_errs) +{ + const char *p; + const error_t *e; + + if (!poss_errs || !poss_errs[0]) + return EIO; + + /* ignore everything before the last colon. */ + p = strrchr (txt, ':'); + if (p) + p++; + else + p = txt; + + /* Now, for each possible error, do a string compare ignoring case and + anything non-alphanumberic. */ + for (e = poss_errs; *e; e++) + if (strlaxcmp (p, strerror (*e)) == 0) + return *e; + + return poss_errs[0]; +} + +struct get_stats_state +{ + char *name; /* Last read (maybe partial) name. */ + int name_partial; /* True if NAME isn't complete. */ + struct stat stat; /* Last read stat info. */ + size_t buf_len; /* Length of contents in BUF. */ + int start; /* True if at beginning of output. */ + char buf[7000]; +}; + +/* Start an operation to get a list of file-stat structures for NAME (this + is often similar to ftp_conn_start_dir, but with OS-specific flags), and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_stats. FORCE_DIR controls what happens if + NAME refers to a directory: if FORCE_DIR is false, STATS will contain + entries for all files *in* NAME, and if FORCE_DIR is true, it will + contain just a single entry for NAME itself (or an error will be + returned when this isn't possible). */ +error_t +ftp_conn_unix_start_get_stats (struct ftp_conn *conn, + const char *name, int force_dir, + int *fd, void **state) +{ + error_t err; + struct get_stats_state *s = malloc (sizeof (struct get_stats_state)); + + if (! s) + return ENOMEM; + + if (force_dir) + { + char *dir_op; + + if (asprintf (&dir_op, "-d %s", name) <= 0) + return errno; + + err = ftp_conn_start_dir (conn, dir_op, fd); + + free (dir_op); + } + else + err = ftp_conn_start_dir (conn, name, fd); + + if (err) + free (s); + else + { + s->name = 0; + s->name_partial = 0; + s->buf_len = 0; + s->start = 1; + *state = s; + } + + return err; +} + +static char *months[] = +{ + "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", + "nov", "dec", 0 +}; + +/* Translate the information in the ls output in *LINE as best we can into + STAT, and update *LINE to point to the filename at the end of the line. + If *LINE should be ignored, EAGAIN is returned. */ +static error_t +parse_dir_entry (char **line, struct stat *stat) +{ + char **m; + struct tm tm; + char *p = *line, *e; + + /* +drwxrwxrwt 3 root wheel 1024 May 1 16:58 /tmp +drwxrwxrwt 5 root daemon 4096 May 1 17:15 /tmp +drwxrwxrwt 4 root 0 1024 May 1 14:34 /tmp +drwxrwxrwt 6 root wheel 284 May 1 12:46 /tmp +drwxrwxrwt 4 sys sys 482 May 1 17:11 /tmp +drwxrwxrwt 7 34 archive 512 May 1 14:28 /tmp + */ + + if (strncasecmp (p, "total ", 6) == 0) + return EAGAIN; + + bzero (stat, sizeof *stat); + + stat->st_fstype = FSTYPE_FTP; + + /* File format (S_IFMT) bits. */ + switch (*p++) + { + case '-': stat->st_mode |= S_IFREG; break; + case 'd': stat->st_mode |= S_IFDIR; break; + case 'c': stat->st_mode |= S_IFCHR; break; + case 'b': stat->st_mode |= S_IFBLK; break; + case 'l': stat->st_mode |= S_IFLNK; break; + case 's': stat->st_mode |= S_IFSOCK; break; + case 'p': stat->st_mode |= S_IFIFO; break; + default: return EGRATUITOUS; + } + + /* User perm bits. */ + switch (*p++) + { + case '-': break; + case 'r': stat->st_mode |= S_IRUSR; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'w': stat->st_mode |= S_IWUSR; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'x': stat->st_mode |= S_IXUSR; break; + case 's': stat->st_mode |= S_IXUSR | S_ISUID; break; + case 'S': stat->st_mode |= S_ISUID; break; + default: return EGRATUITOUS; + } + + /* Group perm bits. */ + switch (*p++) + { + case '-': break; + case 'r': stat->st_mode |= S_IRGRP; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'w': stat->st_mode |= S_IWGRP; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'x': stat->st_mode |= S_IXGRP; break; + case 's': stat->st_mode |= S_IXGRP | S_ISGID; break; + case 'S': stat->st_mode |= S_ISGID; break; + default: return EGRATUITOUS; + } + + /* `Other' perm bits. */ + switch (*p++) + { + case '-': break; + case 'r': stat->st_mode |= S_IROTH; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'w': stat->st_mode |= S_IWOTH; break; + default: return EGRATUITOUS; + } + switch (*p++) + { + case '-': break; + case 'x': stat->st_mode |= S_IXOTH; break; + case 't': stat->st_mode |= S_IXOTH | S_ISVTX; break; + case 'T': stat->st_mode |= S_ISVTX; break; + default: return EGRATUITOUS; + } + +#define SKIP_WS() \ + while (isspace (*p)) p++; +#define PARSE_INT() ({ \ + unsigned u = strtoul (p, &e, 10); \ + if (e == p || isalnum (*e)) \ + return EGRATUITOUS; \ + p = e; \ + u; \ + }) + + /* Link count. */ + SKIP_WS (); + stat->st_nlink = PARSE_INT (); + + /* File owner. */ + SKIP_WS (); + if (isdigit (*p)) + stat->st_uid = PARSE_INT (); + else + { + struct passwd *pw; + + e = p + strcspn (p, " \t\n"); + *e++ = '\0'; + + pw = getpwnam (p); + + if (pw) + stat->st_uid = pw->pw_uid; + else + stat->st_uid = DEFAULT_UID; + + p = e; + } + stat->st_author = stat->st_uid; + + /* File group. */ + SKIP_WS (); + if (isdigit (*p)) + stat->st_gid = PARSE_INT (); + else + { + struct group *gr; + + e = p + strcspn (p, " \t\n"); + *e++ = '\0'; + + gr = getgrnam (p); + + if (gr) + stat->st_gid = gr->gr_gid; + else + stat->st_gid = DEFAULT_GID; + + p = e; + } + + /* File size / device numbers. */ + SKIP_WS (); + if (S_ISCHR (stat->st_mode) || S_ISBLK (stat->st_mode)) + /* Block and character devices show the block params instead of the file + size. */ + { + stat->st_dev = PARSE_INT (); + if (*p != ',') + return EGRATUITOUS; + stat->st_dev = (stat->st_dev << 8) | PARSE_INT (); + stat->st_size = 0; + } + else + /* File size. */ + stat->st_size = PARSE_INT (); + + stat->st_blocks = stat->st_size >> 9; + + /* Date. Ick. */ + /* Formats: MONTH DAY HH:MM and MONTH DAY YEAR */ + + bzero (&tm, sizeof tm); + + SKIP_WS (); + e = p + strcspn (p, " \t\n"); + for (m = months; *m; m++) + if (strncasecmp (*m, p, e - p) == 0) + { + tm.tm_mon = m - months; + break; + } + if (! *m) + return EGRATUITOUS; + p = e; + + SKIP_WS (); + tm.tm_mday = PARSE_INT (); + + SKIP_WS (); + if (p[1] == ':' || p[2] == ':') + { + struct tm *now_tm; + struct timeval now_tv; + + tm.tm_hour = PARSE_INT (); + p++; + tm.tm_min = PARSE_INT (); + + if (gettimeofday (&now_tv, 0) != 0) + return errno; + + now_tm = localtime (&now_tv.tv_sec); + if (now_tm->tm_mon < tm.tm_mon) + tm.tm_year = now_tm->tm_year - 1; + else + tm.tm_year = now_tm->tm_year; + } + else + tm.tm_year = PARSE_INT () - 1900; + + stat->st_mtime = mktime (&tm); + if (stat->st_mtime == (time_t)-1) + return EGRATUITOUS; + + /* atime and ctime are the same as mtime. */ + stat->st_atime = stat->st_ctime = stat->st_mtime; + + /* Update *LINE to point to the filename. */ + SKIP_WS (); + *line = p; + + return 0; +} + +/* Read stats information from FD, calling ADD_STAT for each new stat (HOOK + is passed to ADD_STAT). FD and STATE should be returned from + start_get_stats. If this function returns EAGAIN, then it should be + called again to finish the job (possibly after calling select on FD); if + it returns 0, then it is finishe,d and FD and STATE are deallocated. */ +error_t +ftp_conn_unix_cont_get_stats (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_stat_fun_t add_stat, void *hook) +{ + char *p, *nl; + ssize_t rd; + size_t name_len; + error_t err = 0; + struct get_stats_state *s = state; + + /* We always consume full lines, so we know that we have to read more when + we first get called. */ + rd = read (fd, s->buf + s->buf_len, sizeof (s->buf) - s->buf_len); + if (rd < 0) + { + err = errno; + goto finished; + } + + if (rd == 0) + /* EOF */ + if (s->buf_len == 0) + /* We're done! Clean up and return the result in STATS. */ + { + if (s->start) + /* No output at all. From many ftp servers, this means that the + specified file wasn't found. */ + err = ENOENT; + goto finished; + } + else + /* Partial line at end of file? */ + nl = s->buf + s->buf_len; + else + /* Look for a new line in what we read (we know that there weren't any in + the buffer before that). */ + { + nl = memchr (s->buf + s->buf_len, '\n', rd); + s->buf_len += rd; + } + + s->start = 0; /* We've read past the start. */ + + if (!nl && s->buf_len < sizeof (s->buf)) + /* We didn't find any newlines (which implies we didn't hit EOF), and we + still have room to grow the buffer, so just wait until next time to do + anything. */ + return EAGAIN; + + /* Where we start parsing. */ + p = s->buf; + + do + { + if (! s->name_partial) + /* We aren't continuing to read an overflowed name from the previous + call, so we know that we are at the start of a line, and can parse + the info here as a directory entry. */ + { + /* Parse the directory entry info, updating P to point to the + beginning of the name. */ + err = parse_dir_entry (&p, &s->stat); + if (err == EAGAIN) + /* This line isn't a real entry and should be ignored. */ + goto skip_line; + if (err) + goto finished; + } + + /* Now fill in S->last_stat's name field, possibly extending it from a + previous buffer. */ + name_len = (nl ? nl - p : s->buf + s->buf_len - p); + if (name_len > 0 && p[name_len - 1] == '\r') + name_len--; + if (name_len > 0) + if (s->name) + /* Extending s->name. */ + { + size_t old_len = strlen (s->name); + char *new_name = realloc (s->name, old_len + name_len + 1); + if (! new_name) + goto enomem; + s->name = new_name; + strncpy (new_name + old_len, p, name_len); + new_name[old_len + name_len] = '\0'; + } + else + /* A new name. */ + { + s->name = malloc (name_len + 1); + if (! s->name) + goto enomem; + strncpy (s->name, p, name_len); + s->name[name_len] = '\0'; + } + + if (nl) + { + char *symlink_target = 0; + + if (S_ISLNK (s->stat.st_mode)) + /* A symlink, see if we can find the link target. */ + { + char *lsep = strstr (s->name, " -> "); + if (lsep) + { + *lsep = '\0'; + lsep += 4; + symlink_target = strdup (lsep); + s->name = realloc (s->name, (lsep - 3) - s->name); + } + } + + /* Call the callback function to process the current entry; it is + responsible for freeing S->name and SYMLINK_TARGET. */ + err = (*add_stat) (s->name, &s->stat, symlink_target, hook); + if (err) + goto finished; + + s->name = 0; + s->name_partial = 0; + + skip_line: + p = nl + 1; + nl = memchr (p, '\n', s->buf + s->buf_len - p); + } + else + /* We found no newline, so the name extends past what we read; we'll + try to read more next time. */ + { + s->name_partial = 1; + /* Skip over the partial name for the next iteration. */ + p += name_len; + } + } + while (nl); + + /* Move any remaining characters in the buffer to the beginning for the + next call. */ + s->buf_len -= (p - s->buf); + if (s->buf_len > 0) + memmove (s->buf, p, s->buf_len); + + /* Try again later. */ + return EAGAIN; + +enomem: + /* Some memory allocation failed. */ + err = ENOMEM; + +finished: + /* We're finished (with an error if ERR != 0), deallocate everything & + return. */ + if (s->name) + free (s->name); + free (s); + close (fd); + + return err; +} |