summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiles Bader <miles@gnu.org>1997-05-07 16:22:54 +0000
committerMiles Bader <miles@gnu.org>1997-05-07 16:22:54 +0000
commitd3f310caacfa6a0f943ba67442b7ce69b4d6d716 (patch)
tree6f8f69ddffa5b1ed2d57eb03cd91302fcb180488
parent058a49ce4cc8840bb4af386f5754c3eb466c4422 (diff)
Initial checkin.
-rw-r--r--libftpconn/ChangeLog0
-rw-r--r--libftpconn/Makefile31
-rw-r--r--libftpconn/ftpconn.c1068
-rw-r--r--libftpconn/ftpconn.h291
-rw-r--r--libftpconn/unix.c599
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;
+}