diff options
Diffstat (limited to 'libftpconn')
-rw-r--r-- | libftpconn/ChangeLog | 261 | ||||
-rw-r--r-- | libftpconn/Makefile | 34 | ||||
-rw-r--r-- | libftpconn/addr.c | 79 | ||||
-rw-r--r-- | libftpconn/cmd.c | 169 | ||||
-rw-r--r-- | libftpconn/create.c | 87 | ||||
-rw-r--r-- | libftpconn/cwd.c | 113 | ||||
-rw-r--r-- | libftpconn/errs.c | 32 | ||||
-rw-r--r-- | libftpconn/fname.c | 78 | ||||
-rw-r--r-- | libftpconn/ftpconn.h | 385 | ||||
-rw-r--r-- | libftpconn/names.c | 238 | ||||
-rw-r--r-- | libftpconn/open.c | 252 | ||||
-rw-r--r-- | libftpconn/priv.h | 90 | ||||
-rw-r--r-- | libftpconn/reply.c | 241 | ||||
-rw-r--r-- | libftpconn/rmt.c | 94 | ||||
-rw-r--r-- | libftpconn/set-type.c | 60 | ||||
-rw-r--r-- | libftpconn/stats.c | 80 | ||||
-rw-r--r-- | libftpconn/unix.c | 712 | ||||
-rw-r--r-- | libftpconn/xfer.c | 281 | ||||
-rw-r--r-- | libftpconn/xinl.c | 24 |
19 files changed, 3310 insertions, 0 deletions
diff --git a/libftpconn/ChangeLog b/libftpconn/ChangeLog new file mode 100644 index 00000000..4d5351ca --- /dev/null +++ b/libftpconn/ChangeLog @@ -0,0 +1,261 @@ +1999-08-11 Thomas Bushnell, BSG <tb@mit.edu> + + * open.c (ftp_conn_sysify): Handle REPLY_NO_LOGIN from "syst". + (ftp_conn_open): Ignore error from ftp_conn_sysify; it's ok if we + can't successfully deal. If we didn't sysify successfully before + login, then try again afterwards. + +1998-10-20 Roland McGrath <roland@baalperazim.frob.com> + + * unix.c (ftp_conn_unix_cont_get_stats): Add braces to silence gcc + warning. + * open.c (ftp_conn_sysify): Likewise. + (ftp_conn_login): Likewise. + * cwd.c (ftp_conn_cdup): Likewise. + (ftp_conn_cwd): Likewise. + (_cache_cwd): Likewise. + * addr.c (ftp_conn_send_actv_addr): Likewise. + (ftp_conn_get_pasv_addr): Likewise. + +1997-09-04 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpconn.h [!FTP_CONN_EI] (FTP_CONN_EI): New macro. + (ftp_conn_validate_syshooks): Use FTP_CONN_EI instead of + `static inline'. + Protect with __OPTIMIZE__. + + * ftpconn.h (struct ftp_conn): Remove ACTV_DATA_CONN_QUEUE field. + * create.c (ftp_conn_create): Don't initialize + CONN->actv_data_conn_queue. + * open.c (ftp_conn_close): Don't cleanup CONN->actv_data_conn_queue. + * xfer.c (ftp_conn_start_open_actv_data): Always generate a new data + connection (it doesn't work to always listen on a single address). + (ftp_conn_finish_open_actv_data): Close the listen queue after + looking for a connection. + (ftp_conn_abort_open_actv_data): Close the data queue socket. + +1997-09-02 Miles Bader <miles@gnu.ai.mit.edu> + + * xfer.c (ftp_conn_start_open_actv_data): If necessary, reopen the + control connection for getsockname. + +1997-08-27 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpconn.h (struct ftp_conn): Add the ACTV_DATA_ADDR and + ACTV_DATA_CONN_QUEUE fields. + * create.c (ftp_conn_free): Free the ACTV_DATA_ADDR field. + (ftp_conn_create): Initialiaze the ACTV_DATA_ADDR & + ACTV_DATA_CONN_QUEUE fields. + * open.c (ftp_conn_close): Close CONN->actv_data_conn_queue if necessary. + * xfer.c (ftp_conn_start_open_actv_data): Renamed ftp_conn_open_actv_data. + Correctly generate data address. + (ftp_conn_start_open_data): Renamed from ftp_conn_open_data. + (ftp_conn_finish_open_data, ftp_conn_finish_open_actv_data): New + functions. + (ftp_conn_start_transfer): Use ftp_conn_start_open_data and + ftp_conn_finish_open_data instead of ftp_conn_open_data. + +1997-08-26 Miles Bader <miles@gnu.ai.mit.edu> + + * xfer.c (ftp_conn_open_actv_data): New function. + (ftp_conn_open_data): Use an active data connection if passive + doesn't work. + * ftpconn.h (struct ftp_conn): Add USE_PASSIVE field. + * create.c (ftp_conn_create): Initialize USE_PASSIVE field. + + * open.c (ftp_conn_open): Set SIN_LEN field correctly. + +1997-08-19 Miles Bader <miles@gnu.ai.mit.edu> + + * create.c (ftp_conn_create): Initialize the SYSHOOKS_VALID field. + +1997-08-15 Miles Bader <miles@gnu.ai.mit.edu> + + * fname.c (ftp_conn_append_name, ftp_conn_basename): Make sure + CONN's SYSHOOKS field is valid before using it. + * ftpconn.h (struct ftp_conn): Add SYSHOOKS_VALID field. + (ftp_conn_validate_syshooks): New inline function. + * open.c (ftp_conn_sysify): Set SYSHOOKS_VALID. + + * set-type.c (ftp_conn_set_type): If there's no connection, don't + open a new one, just save the type for later. + +1997-08-09 Miles Bader <miles@gnu.ai.mit.edu> + + * names.c (ftp_conn_cont_get_names): Check for interrupts. + * unix.c (ftp_conn_unix_cont_get_stats): Likewise. + * reply.c (ftp_conn_getline): Likewise. + * ftpconn.h (struct ftp_conn_hooks): Add INTERRUPT_CHECK field. + +1997-08-08 Miles Bader <miles@gnu.ai.mit.edu> + + * rmt.c (ftp_conn_rmt_transfer): If we get an error, close SRC_CONN + instead of aborting it, to avoid problems with some ftp servers. + + * create.c (ftp_conn_create): Don't open the connection here. + + * names.c (ftp_conn_cont_get_names): Use BASENAME hook instead of + FIX_NLIST_NAME. + (ftp_conn_start_get_names): Don't set DIR field. + (struct get_names_state): Remove DIR field. + (ftp_conn_cont_get_names): Don't free DIR field. + + * ftpconn.h (struct ftp_conn_syshooks): Add APPEND_NAME and + BASENAME fields; remove FIX_NLIST_NAME field. + (ftp_conn_unix_append_name, ftp_conn_unix_basename): New declarations. + (ftp_conn_unix_fix_nlist_name): Declaration removed. + (ftp_conn_append_name, ftp_conn_basename): New declarations. + * unix.c (ftp_conn_unix_cont_get_stats): Pass only directory- + relative names to the callback function. + (ftp_conn_unix_append_name, ftp_conn_unix_basename): New functions. + (ftp_conn_unix_fix_nlist_name): Function removed. + (struct ftp_conn_unix_syshooks): Initialize APPEND_NAME and + BASENAME fields; remove FIX_NLIST_NAME initialization. + * fname.c: New file. + * Makefile (SRCS): Add fname.c + +1997-08-07 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpconn.h (struct ftp_conn_syshooks): Add FIX_NLIST_NAME field. + (ftp_conn_unix_fix_nlist_name): New declaration. + * names.c (ftp_conn_cont_get_names): Support FIX_NLIST_NAME hook. + Free S->dir if necessary. Close or abort connection when done. + (struct get_names_state): Add DIR field. + (ftp_conn_start_get_names): Fill in S->dir field if necessary. + + * unix.c (ftp_conn_unix_fix_nlist_name): New function. + (ftp_conn_unix_syshooks): Add ftp_conn_unix_fix_nlist_name. + +1997-08-06 Miles Bader <miles@gnu.ai.mit.edu> + + * open.c (ftp_conn_sysify): Handle REPLY_UNIMP_CMD for "syst". + + * unix.c (parse_dir_entry): Re-enable mktime calls. + +1997-08-04 Miles Bader <miles@gnu.ai.mit.edu> + + * unix.c (ftp_conn_unix_cont_get_stats): If returning with an + error, ignore any return from ftp_conn_finish_transfer. + +Wed Jun 4 14:17:30 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * Makefile (ftpcp, ftpdir): Rules removed. + * ftpcp.c, ftpdir.c: Files removed. + +Thu May 29 17:24:40 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * ftpcp.c (doc): Update. + (econnect, eopen_wr, eopen_rd, efinish): New functions. + (struct epoint): Remove FD field. + Rename RMT_FILE & RMT_HOST to FILE & HOST. + (main): Remove array of epoints & loops, & just use RD & WR, + calling the e* functions above. + Support DST being a directory. + +Wed May 14 15:22:17 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * open.c (ftp_conn_login): Correct defaulting of username. + +Tue May 13 16:37:18 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * unix.c (ftp_conn_unix_cont_get_stats): Clean up our ftp transfer + when we're done. + +Mon May 12 10:57:54 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * unix.c (ftp_conn_unix_cont_get_stats): Only abort when + necessary. + (ftp_conn_unix_start_get_stats): Allocate enough memory for REQ. + + * cmd.c (ftp_conn_abort): Use ftp_conn_get_raw_reply instead of + ftp_conn_get_reply. Accept REPLY_ABORT_OK as a final reply. + * reply.c (ftp_conn_get_raw_reply): Renamed from ftp_conn_get_reply. + (ftp_conn_get_reply): New function. + * ftpconn.h (ftp_conn_get_raw_reply): New declaration. + +Fri May 9 17:55:25 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * priv.h (REPLY_ABORT_OK): New macro. + + * ftpdir.c (ftpdir2): Deal with new semantics of CONTENTS argument + to ftp_conn_get_stats. + * unix.c (struct get_stats_state): Add ADDED_SLASH field. + (ftp_conn_unix_start_get_stats): Don't use asprintf. + Set ADDED_SLASH field. + (ftp_conn_unix_cont_get_stats): Remove any initial `./' we added. + Abort CONN if we return an error. + +Thu May 8 14:22:08 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * open.c (ftp_conn_open): Cast 2nd arg in connect call to `struct + sockaddr *'. + * cmd.c: Include <string.h>. + + * stats.c (ftp_conn_start_get_stats, ftp_conn_get_stats): + Rename FORCE_DIR argument to CONTENTS. + * ftpconn.h (ftp_conn_get_stats, ftp_conn_start_get_stats, + ftp_conn_unix_start_get_stats): Rename FORCE_DIR argument to CONTENTS. + (struct ftp_conn_syshooks): Rename FORCE_DIR argument of + START_GET_STATS field type to CONTENTS. + * unix.c (struct get_stats_state): Add CONTENTS field. + (ftp_conn_unix_start_get_stats): Initialize it. + Change FORCE_DIR argument to CONTENTS. + Pass -A flag. + Prepend ./ to NAME if it doesn't contain a slash. + Reject names containg possible meta-characters. + + * unix.c: Only include <hurd/hurd_types.h> if + HAVE_HURD_HURD_TYPES_H is defined. + (parse_dir_entry): Only set stat->st_fstype if FSTYPE_FTP is defined. + Only set stat->st_author if HAVE_STAT_ST_AUTHOR is defined. + * Makefile (CPPFLAGS): Add -DHAVE_HURD_HURD_TYPES_H. + +Wed May 7 12:35:21 1997 Miles Bader <miles@gnu.ai.mit.edu> + + * Makefile (SRCS): ftpconn.c replaced by resulting split files. + * errs.c: New file. + * ftpconn.c (ftp_conn_poss_file_errs): Moved to errs.c. + * open.c: New file. + * ftpconn.c (ftp_conn_login, ftp_conn_hello, ftp_conn_set_syshooks, + ftp_conn_choose_syshooks, ftp_conn_sysify, ftp_conn_open, + ftp_conn_close): Functions moved to open.c. + * ftpconn.c (ftp_conn_poss_file_errs): Renamed from + poss_file_errs; all references changed. + Made not static. + * xfer.c: New file. + * ftpconn.c (ftp_conn_finish_transfer, ftp_conn_start_transfer, + ftp_conn_open_data, ftp_conn_start_retrieve, ftp_conn_start_list, + ftp_conn_start_dir, ftp_conn_start_store): Functions moved to xfer.c. + * addr.c: New file. + * ftpconn.c p(ftp_conn_get_pasv_addr, ftp_conn_send_actv_addr): + Functions moved to addr.c. + * create.c: New file. + * ftpconn.c (ftp_conn_create, ftp_conn_free): Functions moved to create.c. + * priv.h: New file. + * xinl.c: New file. + * ftpconn.c (unexpected_reply): Function moved to priv.h. + (REPLY_*): Macros moved to priv.h. + * rmt.c: New file. + * ftpconn.c (ftp_conn_rmt_copy, ftp_conn_rmt_transfer): Functions + moved to rmt.c. + * cwd.c: New file. + * ftpconn.c (ftp_conn_cdup, ftp_conn_cwd, ftp_conn_get_cwd, + _cache_cwd): Functions moved to cwd.c. + * set-type.c: New file. + * ftpconn.c (ftp_conn_set_type): Function moved to set-type.c. + * stats.c: New file. + * ftpconn.c (ftp_conn_start_get_stats, ftp_conn_cont_get_stats, + ftp_conn_get_stats): Functions moved to stats.c. + + * names.c: New file. + * ftpconn.h (ftp_conn_add_name_fun_t): New type. + (ftp_conn_start_get_names, ftp_conn_cont_get_names, + ftp_conn_get_names): New functions. + + * unix.c (struct get_stats_state): Add NAME_LEN & NAME_ALLOCED fields. + (ftp_conn_unix_cont_get_stats): Retain S's name buffer between + entries, only growing it when needed; leave any copying to the client. + * ftpconn.h (ftp_conn_add_stat_fun_t): Make first 3 params const. + Update comment to say they should be copied if retained. + + * Makefile (installhdrsubdir): New variable. diff --git a/libftpconn/Makefile b/libftpconn/Makefile new file mode 100644 index 00000000..05f3ddfb --- /dev/null +++ b/libftpconn/Makefile @@ -0,0 +1,34 @@ +# 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 +installhdrsubdir = . + +SRCS = addr.c cmd.c create.c cwd.c errs.c names.c open.c reply.c \ + rmt.c set-type.c stats.c unix.c xfer.c xinl.c fname.c +LCLHDRS = ftpconn.h priv.h + +OBJS = $(SRCS:.c=.o) + +CPPFLAGS += -DHAVE_HURD_HURD_TYPES_H -DHAVE_STAT_ST_AUTHOR + +include ../Makeconf diff --git a/libftpconn/addr.c b/libftpconn/addr.c new file mode 100644 index 00000000..3b668f34 --- /dev/null +++ b/libftpconn/addr.c @@ -0,0 +1,79 @@ +/* Send/receive data-connection addresses + + Copyright (C) 1997, 1998 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 <errno.h> +#include <netinet/in.h> + +#include <ftpconn.h> +#include "priv.h" + +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; +} + +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; +} diff --git a/libftpconn/cmd.c b/libftpconn/cmd.c new file mode 100644 index 00000000..803dda7c --- /dev/null +++ b/libftpconn/cmd.c @@ -0,0 +1,169 @@ +/* Send commands to the ftp server + + 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 <errno.h> +#include <string.h> +#include <arpa/telnet.h> + +#include <ftpconn.h> +#include "priv.h" + +/* 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_raw_reply (conn, &reply, 0); + while (reply == REPLY_ABORTED); + if (reply != REPLY_TRANS_OK && reply != REPLY_ABORT_OK) + ftp_conn_close (conn); + } + else + ftp_conn_close (conn); + } +} diff --git a/libftpconn/create.c b/libftpconn/create.c new file mode 100644 index 00000000..20a64561 --- /dev/null +++ b/libftpconn/create.c @@ -0,0 +1,87 @@ +/* Create a new 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 <errno.h> +#include <string.h> + +#include <ftpconn.h> + +/* Create a new ftp connection as specified by PARAMS, and return it in CONN; + HOOKS contains customization hooks used by the connection. Neither PARAMS + nor HOOKS is copied, so a copy of it should be made if necessary before + calling this function; if it should be freed later, a FINI hook may be + used to do so. */ +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->syshooks_valid = 0; + new->use_passive = 1; + new->actv_data_addr = 0; + 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) + ftp_conn_free (new); + else + *conn = new; + + return err; +} + +/* Free the ftp connection CONN, closing it first, and freeing all resources + it uses. */ +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); + if (conn->actv_data_addr) + free (conn->actv_data_addr); + free (conn); +} diff --git a/libftpconn/cwd.c b/libftpconn/cwd.c new file mode 100644 index 00000000..868150f1 --- /dev/null +++ b/libftpconn/cwd.c @@ -0,0 +1,113 @@ +/* Get/set connection current working directory + + Copyright (C) 1997, 1998 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 <errno.h> +#include <string.h> + +#include <ftpconn.h> +#include "priv.h" + +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, ftp_conn_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, ftp_conn_poss_file_errs); + } + return err; +} diff --git a/libftpconn/errs.c b/libftpconn/errs.c new file mode 100644 index 00000000..5a93fb55 --- /dev/null +++ b/libftpconn/errs.c @@ -0,0 +1,32 @@ +/* Error codes we think may result from file operations we do + + 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 <errno.h> +#include <ftpconn.h> +#include "priv.h" + +/* Error codes we think may result from file operations we do. */ +const error_t +ftp_conn_poss_file_errs[] = +{ + EIO, ENOENT, EPERM, EACCES, ENOTDIR, ENAMETOOLONG, ELOOP, EISDIR, EROFS, + EMFILE, ENFILE, ENXIO, EOPNOTSUPP, ENOSPC, EDQUOT, ETXTBSY, EEXIST, + 0 +}; diff --git a/libftpconn/fname.c b/libftpconn/fname.c new file mode 100644 index 00000000..afa0852b --- /dev/null +++ b/libftpconn/fname.c @@ -0,0 +1,78 @@ +/* Filename frobbing + + Copyright (C) 1997 Free Software Foundation, Inc. + Written by Miles Bader <miles@gnu.ai.mit.edu> + This file is part of the GNU Hurd. + + The GNU Hurd 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. + + The GNU Hurd 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +#include <string.h> + +#include "ftpconn.h" + +/* Give a name which refers to a directory file, and a name in that + directory, this should return in COMPOSITE the composite name refering to + that name in that directory, in malloced storage. */ +error_t +ftp_conn_append_name (struct ftp_conn *conn, + const char *dir, const char *name, + char **composite) +{ + error_t err = ftp_conn_validate_syshooks (conn); + if (err) + return err; + else if (conn->syshooks.append_name) + return (*conn->syshooks.append_name) (conn, dir, name, composite); + else + return EOPNOTSUPP; +} + +/* If the name of a file COMPOSITE is a composite name (containing both a + filename and a directory name), this function will return the name + component only in BASE, in malloced storage, otherwise it simply returns a + newly malloced copy of COMPOSITE in BASE. */ +error_t +ftp_conn_basename (struct ftp_conn *conn, const char *composite, char **base) +{ + error_t err = ftp_conn_validate_syshooks (conn); + + if (err) + return err; + + if (conn->syshooks.basename) + { + size_t in_size = strlen (composite) + 1; + char *in = strdup (composite), *out = in; + + if (! in) + return ENOMEM; + + err = (*conn->syshooks.basename) (conn, &out); + if (err || out != in) + { + if (!err && out >= in && out < in + in_size) + /* OUT uses storage from IN, but not at the beginning. */ + out = strdup (out); + free (in); + } + + if (! err) + *base = out; + + return err; + } + else + return EOPNOTSUPP; +} diff --git a/libftpconn/ftpconn.h b/libftpconn/ftpconn.h new file mode 100644 index 00000000..c4172ffe --- /dev/null +++ b/libftpconn/ftpconn.h @@ -0,0 +1,385 @@ +/* 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 <sys/types.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#define __need_error_t +#include <errno.h> + +#ifndef __error_t_defined +typedef int error_t; +#define __error_t_defined +#endif + +#ifndef FTP_CONN_EI +# define FTP_CONN_EI extern inline +#endif + +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 and SYMLINK_TARGET should be copied if they are used + outside of this function. HOOK is as passed into ...get_stats. */ +typedef error_t (*ftp_conn_add_stat_fun_t) (const char *name, + const struct stat *stat, + const 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. If CONTENTS is true, NAME must + refer to a directory, and the contents will be returned, otherwise, the + (single) result will refer to NAME. */ + error_t (*start_get_stats) (struct ftp_conn *conn, const char *name, + int contents, 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); + + /* Give a name which refers to a directory file, and a name in that + directory, this should return in COMPOSITE the composite name refering + to that name in that directory, in malloced storage. */ + error_t (*append_name) (struct ftp_conn *conn, + const char *dir, const char *name, + char **composite); + + /* If the name of a file *NAME is a composite name (containing both a + filename and a directory name), this function should change *NAME to be + the name component only; if the result is shorter than the original + *NAME, the storage pointed to it may be modified, otherwise, *NAME + should be changed to point to malloced storage holding the result, which + will be freed by the caller. */ + error_t (*basename) (struct ftp_conn *conn, char **name); +}; + +/* 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); + + /* This hook should return true if the current thread has been interrupted + in some way, and EINTR (or a short count in some cases) should be + returned from a blocking function. */ + int (*interrupt_check) (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 syshooks_valid : 1; /* True if the system type has been determined. */ + + 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. */ + + int use_passive : 1; /* If true, first try passive data conns. */ + + struct sockaddr *actv_data_addr;/* Address of port for active data conns. */ +}; + +/* 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, + const error_t *poss_errs); +extern error_t ftp_conn_unix_start_get_stats (struct ftp_conn *conn, + const char *name, + int contents, 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); +error_t ftp_conn_unix_append_name (struct ftp_conn *conn, + const char *dir, const char *name, + char **composite); +error_t ftp_conn_unix_basename (struct ftp_conn *conn, char **name); + +extern struct ftp_conn_syshooks ftp_conn_unix_syshooks; + +error_t +ftp_conn_get_raw_reply (struct ftp_conn *conn, + int *reply, const char **reply_txt); +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); + +#ifdef __OPTIMIZE__ +/* Makes sure that CONN's syshooks are set according to the remote system + type. */ +FTP_CONN_EI error_t +ftp_conn_validate_syshooks (struct ftp_conn *conn) +{ + if (conn->syshooks_valid) + return 0; + else + /* Opening the connection should set the syshooks. */ + return ftp_conn_open (conn); +} +#endif /* __OPTIMIZE__ */ + +/* Create a new ftp connection as specified by PARAMS, and return it in CONN; + HOOKS contains customization hooks used by the connection. Neither PARAMS + nor HOOKS is copied, so a copy of it should be made if necessary before + calling this function; if it should be freed later, a FINI hook may be + used to do so. */ +error_t ftp_conn_create (const struct ftp_conn_params *params, + const struct ftp_conn_hooks *hooks, + struct ftp_conn **conn); + +/* Free the ftp connection CONN, closing it first, and freeing all resources + it uses. */ +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, + const 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. If CONTENTS is true, NAME must + refer to a directory, and the contents will be returned, otherwise, the + (single) result will refer to NAME. */ +error_t ftp_conn_start_get_stats (struct ftp_conn *conn, + const char *name, int contents, + 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 CONTENTS is true, NAME must refer to a + directory, and the contents will be returned, otherwise, the (single) + result will refer to NAME. This function may block. */ +error_t ftp_conn_get_stats (struct ftp_conn *conn, + const char *name, int contents, + ftp_conn_add_stat_fun_t add_stat, void *hook); + +/* The type of the function called by ...get_names to add each new name. + NAME is the name in question and HOOK is as passed into ...get_stats. */ +typedef error_t (*ftp_conn_add_name_fun_t) (const char *name, void *hook); + +/* Start an operation to get a list of filenames in the directory NAME, and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_names. */ +error_t ftp_conn_start_get_names (struct ftp_conn *conn, + const char *name, int *fd, void **state); + +/* Read filenames from FD, calling ADD_NAME for each new NAME (HOOK is passed + to ADD_NAME). 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_names (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_name_fun_t add_name, void *hook); + +/* Get a list of names in the directory NAME, calling ADD_NAME for each one + (HOOK is passed to ADD_NAME). This function may block. */ +error_t ftp_conn_get_names (struct ftp_conn *conn, const char *name, + ftp_conn_add_name_fun_t add_name, void *hook); + +/* Give a name which refers to a directory file, and a name in that + directory, this should return in COMPOSITE the composite name refering to + that name in that directory, in malloced storage. */ +error_t ftp_conn_append_name (struct ftp_conn *conn, + const char *dir, const char *name, + char **composite); + +/* If the name of a file COMPOSITE is a composite name (containing both a + filename and a directory name), this function will return the name + component only in BASE, in malloced storage, otherwise it simply returns a + newly malloced copy of COMPOSITE in BASE. */ +error_t ftp_conn_basename (struct ftp_conn *conn, + const char *composite, char **base); + +#endif /* __FTPCONN_H__ */ diff --git a/libftpconn/names.c b/libftpconn/names.c new file mode 100644 index 00000000..4536b723 --- /dev/null +++ b/libftpconn/names.c @@ -0,0 +1,238 @@ +/* Fetch directory file names + + 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 <errno.h> + +#include <ftpconn.h> + +struct get_names_state +{ + char *name; /* Last read (maybe partial) name. */ + size_t name_len; /* Valid length of NAME, *not including* '\0'. */ + size_t name_alloced; /* Allocated size of NAME (>= NAME_LEN). */ + int name_partial; /* True if NAME isn't complete. */ + + size_t buf_len; /* Length of contents in BUF. */ + char buf[7000]; +}; + +/* Start an operation to get a list of filenames in the directory NAME, and + return a file-descriptor for reading on, and a state structure in STATE + suitable for passing to cont_get_names. */ +error_t +ftp_conn_start_get_names (struct ftp_conn *conn, + const char *name, int *fd, void **state) +{ + error_t err; + struct get_names_state *s = malloc (sizeof (struct get_names_state)); + + if (! s) + return ENOMEM; + + err = ftp_conn_start_list (conn, name, fd); + + if (err) + free (s); + else + { + s->name = 0; + s->name_len = s->name_alloced = 0; + s->name_partial = 0; + s->buf_len = 0; + *state = s; + } + + return err; +} + +/* Read filenames from FD, calling ADD_NAME for each new NAME (HOOK is passed + to ADD_NAME). FD and STATE should be returned from start_get_names. 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_names (struct ftp_conn *conn, int fd, void *state, + ftp_conn_add_name_fun_t add_name, void *hook) +{ + char *p, *nl; + ssize_t rd; + size_t name_len; + error_t err = 0; + struct get_names_state *s = state; + int (*icheck) (struct ftp_conn *conn) = conn->hooks->interrupt_check; + + /* 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 (icheck && (*icheck) (conn)) + { + err = EINTR; + goto finished; + } + + if (rd == 0) + /* EOF */ + if (s->buf_len == 0) + /* We're done! Clean up and return the result in NAMES. */ + 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; + } + + 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 + { + /* Fill in S->name, 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) + /* Extending s->name. */ + { + size_t old_len = s->name_len; + size_t total_len = old_len + name_len + 1; + + if (total_len > s->name_alloced) + { + char *new_name = realloc (s->name, total_len); + if (! new_name) + goto enomem; + s->name = new_name; + s->name_alloced = total_len; + } + + strncpy (s->name + old_len, p, name_len); + s->name[old_len + name_len] = '\0'; + s->name_len = total_len - 1; + } + + if (nl) + { + char *name = s->name; + + if (conn->syshooks.basename) + /* Fixup any screwy names returned by the server. */ + { + err = (*conn->syshooks.basename) (conn, &name); + if (err) + goto finished; + } + + /* Call the callback function to process the current entry. */ + err = (*add_name) (name, hook); + + if (name < s->name || name > s->name + s->name_len) + /* User-allocated NAME from the fix_nlist_name hook. */ + free (name); + + if (err) + goto finished; + + s->name_len = 0; + s->name_partial = 0; + + 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); + + if (err && rd > 0) + ftp_conn_abort (conn); + else if (err) + ftp_conn_finish_transfer (conn); + else + err = ftp_conn_finish_transfer (conn); + + return err; +} + +/* Get a list of names in the directory NAME, calling ADD_NAME for each one + (HOOK is passed to ADD_NAME). This function may block. */ +error_t +ftp_conn_get_names (struct ftp_conn *conn, const char *name, + ftp_conn_add_name_fun_t add_name, void *hook) +{ + int fd; + void *state; + error_t err = ftp_conn_start_get_names (conn, name, &fd, &state); + + if (err) + return err; + + do + err = ftp_conn_cont_get_names (conn, fd, state, add_name, hook); + while (err == EAGAIN); + + return err; +} + diff --git a/libftpconn/open.c b/libftpconn/open.c new file mode 100644 index 00000000..f52bf4d0 --- /dev/null +++ b/libftpconn/open.c @@ -0,0 +1,252 @@ +/* Connection initiation + + Copyright (C) 1997, 1998, 1999 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 <errno.h> +#include <string.h> +#include <ctype.h> +#include <pwd.h> +#include <netdb.h> +#include <netinet/in.h> + +#include <ftpconn.h> +#include "priv.h" + +static 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 || reply == REPLY_UNIMP_CMD || REPLY_NO_LOGIN) + { + if (reply == REPLY_BAD_CMD || reply == REPLY_UNIMP_CMD + || reply == REPLY_NO_LOGIN) + txt = 0; + if (conn->hooks && conn->hooks->choose_syshooks) + (*conn->hooks->choose_syshooks) (conn, txt); + else + ftp_conn_choose_syshooks (conn, txt); + conn->syshooks_valid = 1; + } + 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 = sizeof ftp_addr; + 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, (struct sockaddr *)&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. */ + ftp_conn_sysify (conn); + + if (! err) + /* login */ + err = ftp_conn_login (conn); + + if (!err && !conn->syshooks_valid) + /* Try again now. */ + err = ftp_conn_sysify (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); +} diff --git a/libftpconn/priv.h b/libftpconn/priv.h new file mode 100644 index 00000000..fb4dae50 --- /dev/null +++ b/libftpconn/priv.h @@ -0,0 +1,90 @@ +/* libftpconn private definitions + + 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_PRIV_H__ +#define __FTPCONN_PRIV_H__ + +#ifndef FTP_CONN_EI +# define FTP_CONN_EI extern inline +#endif + +/* 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_ABORT_OK 225 /* ABOR command successful */ +#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) + +FTP_CONN_EI 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; +} + +/* Error codes we think may result from file operations we do. */ +extern const error_t ftp_conn_poss_file_errs[]; + +error_t ftp_conn_get_pasv_addr (struct ftp_conn *conn, struct sockaddr **addr); + +error_t ftp_conn_send_actv_addr (struct ftp_conn *conn, struct sockaddr *addr); + +#endif /* __FTPCONN_PRIV_H__ */ diff --git a/libftpconn/reply.c b/libftpconn/reply.c new file mode 100644 index 00000000..4f30f751 --- /dev/null +++ b/libftpconn/reply.c @@ -0,0 +1,241 @@ +/* Parse ftp server replies + + 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 <errno.h> +#include <string.h> +#include <ctype.h> + +#include <ftpconn.h> +#include "priv.h" + +/* Add STR (of size LEN) to CONN's reply_txt buffer, at offset *OFFS, + updating *OFFS. */ +static inline 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 inline 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; + int (*icheck) (struct ftp_conn *conn) = conn->hooks->interrupt_check; + + 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; + + if (icheck && (*icheck) (conn)) + return EINTR; + } +} + +/* 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. */ +inline error_t +ftp_conn_get_raw_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; +} + +/* 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. This differs from ftp_conn_get_raw_reply in that + it eats REPLY_ABORT_OK replies on the assumption that they're junk left + over from the last abort command. */ +error_t +ftp_conn_get_reply (struct ftp_conn *conn, int *reply, const char **reply_txt) +{ + int code; + error_t err; + + do + err = ftp_conn_get_raw_reply (conn, &code, reply_txt); + while (!err && code == REPLY_ABORT_OK); + + if (!err && reply) + *reply = code; + + return err; +} diff --git a/libftpconn/rmt.c b/libftpconn/rmt.c new file mode 100644 index 00000000..7ada5774 --- /dev/null +++ b/libftpconn/rmt.c @@ -0,0 +1,94 @@ +/* Remote (server-to-server) transfer + + Copyright (C) 1997, 1998 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 <errno.h> + +#include <ftpconn.h> +#include "priv.h" + +/* 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, + ftp_conn_poss_file_errs); + } + if (err) + /* Ftp servers seem to hang trying to abort at this point, so + just close the connection entirely. */ + ftp_conn_close (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, ftp_conn_poss_file_errs, + dst_conn, dst_name); +} diff --git a/libftpconn/set-type.c b/libftpconn/set-type.c new file mode 100644 index 00000000..6c500ce6 --- /dev/null +++ b/libftpconn/set-type.c @@ -0,0 +1,60 @@ +/* Set connection type + + 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 <errno.h> +#include <string.h> + +#include <ftpconn.h> +#include "priv.h" + +/* 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 (conn, "type", type, &reply, 0); + + if (!err && reply != REPLY_OK && reply != REPLY_CLOSED) + err = unexpected_reply (conn, reply, 0, 0); + + if (!err || err == EPIPE) + { + if (conn->type) + free ((char *)conn->type); + conn->type = type; + } + } + } + + return err; +} diff --git a/libftpconn/stats.c b/libftpconn/stats.c new file mode 100644 index 00000000..1b37a7f3 --- /dev/null +++ b/libftpconn/stats.c @@ -0,0 +1,80 @@ +/* Fetch file stats + + 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 <errno.h> + +#include <ftpconn.h> + +/* 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. If CONTENTS is true, NAME must + refer to a directory, and the contents will be returned, otherwise, the + (single) result will refer to NAME. */ +error_t +ftp_conn_start_get_stats (struct ftp_conn *conn, + const char *name, int contents, + int *fd, void **state) +{ + if (conn->syshooks.start_get_stats) + return + (*conn->syshooks.start_get_stats) (conn, name, contents, 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 CONTENTS is true, NAME must refer to a + directory, and the contents will be returned, otherwise, the (single) + result will refer to NAME. This function may block. */ +error_t +ftp_conn_get_stats (struct ftp_conn *conn, + const char *name, int contents, + ftp_conn_add_stat_fun_t add_stat, void *hook) +{ + int fd; + void *state; + error_t err = ftp_conn_start_get_stats (conn, name, contents, &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/unix.c b/libftpconn/unix.c new file mode 100644 index 00000000..e3277721 --- /dev/null +++ b/libftpconn/unix.c @@ -0,0 +1,712 @@ +/* Unix-specific ftpconn hooks + + Copyright (C) 1997, 1998 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> +#ifdef HAVE_HURD_HURD_TYPES_H +#include <hurd/hurd_types.h> +#endif + +#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, + ftp_conn_unix_append_name, ftp_conn_unix_basename +}; + +/* 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. */ + size_t name_len; /* Valid length of NAME, *not including* '\0'. */ + size_t name_alloced; /* Allocated size of NAME (>= NAME_LEN). */ + int name_partial; /* True if NAME isn't complete. */ + + int contents; /* Are we looking for directory contents? */ + int added_slash; /* Did we prefix the name with `./'? */ + + struct stat stat; /* Last read stat info. */ + + int start; /* True if at beginning of output. */ + + size_t buf_len; /* Length of contents in BUF. */ + 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. If CONTENTS is true, NAME must + refer to a directory, and the contents will be returned, otherwise, the + (single) result will refer to NAME. */ +error_t +ftp_conn_unix_start_get_stats (struct ftp_conn *conn, + const char *name, int contents, + int *fd, void **state) +{ + error_t err; + size_t req_len; + char *req; + struct get_stats_state *s = malloc (sizeof (struct get_stats_state)); + const char *flags = contents ? "-A" : "-Ad"; + const char *slash = strchr (name, '/'); + + if (! s) + return ENOMEM; + + if (strcspn (name, "*? \t\n{}$`\\\"'") < strlen (name)) + /* NAME contains some metacharacters, which makes the behavior of various + ftp servers unpredictable, so punt. */ + { + free (s); + return EINVAL; + } + + /* We pack the ls options and the name into the list argument, in REQ, + which will do the right thing on most unix ftp servers. */ + + /* Space needed for REQ. */ + req_len = strlen (flags) + 1 + strlen (name) + 1; + + /* If NAME doesn't contain a slash, we prepend `./' to it so that we can + tell from the results whether it's a directory or not. */ + if (! slash) + req_len += 2; + + req = malloc (req_len); + if (! req) + return ENOMEM; + + snprintf (req, req_len, "%s %s%s", flags, slash ? "" : "./", name); + + /* Make the actual request. */ + err = ftp_conn_start_dir (conn, req, fd); + + free (req); + + if (err) + free (s); + else + { + s->contents = contents; + s->added_slash = !slash; + s->name = 0; + s->name_len = s->name_alloced = 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); + +#ifdef FSTYPE_FTP + stat->st_fstype = FSTYPE_FTP; +#endif + + /* 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; + } + +#ifdef HAVE_STAT_ST_AUTHOR + stat->st_author = stat->st_uid; +#endif + + /* 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; + int (*icheck) (struct ftp_conn *conn) = conn->hooks->interrupt_check; + + /* 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 (icheck && (*icheck) (conn)) + { + err = EINTR; + 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) + /* Extending s->name. */ + { + size_t old_len = s->name_len; + size_t total_len = old_len + name_len + 1; + + if (total_len > s->name_alloced) + { + char *new_name = realloc (s->name, total_len); + if (! new_name) + goto enomem; + s->name = new_name; + s->name_alloced = total_len; + } + + strncpy (s->name + old_len, p, name_len); + s->name[old_len + name_len] = '\0'; + s->name_len = total_len - 1; + } + + if (nl) + { + char *name = s->name; + char *symlink_target = 0; + + if (S_ISLNK (s->stat.st_mode)) + /* A symlink, see if we can find the link target. */ + { + symlink_target = strstr (name, " -> "); + if (symlink_target) + { + *symlink_target = '\0'; + symlink_target += 4; + } + } + + if (strchr (name, '/')) + { + if (s->contents) + /* We know that the name originally request had a slash in + it (because we added one if necessary), so if a name in + the listing has one too, it can't be the contents of a + directory; if this is the case and we wanted the + contents, this must not be a directory. */ + { + err = ENOTDIR; + goto finished; + } + else if (s->added_slash) + /* S->name must be the same name we passed; if we added a + `./' prefix, removed it so the client gets back what it + passed. */ + name += 2; + } + + /* Pass only directory-relative names to the callback function. */ + name = basename (name); + + /* Call the callback function to process the current entry; it is + responsible for freeing S->name and SYMLINK_TARGET. */ + err = (*add_stat) (name, &s->stat, symlink_target, hook); + if (err) + goto finished; + + s->name_len = 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); + + if (err && rd > 0) + ftp_conn_abort (conn); + else if (err) + ftp_conn_finish_transfer (conn); + else + err = ftp_conn_finish_transfer (conn); + + return err; +} + +/* Give a name which refers to a directory file, and a name in that + directory, this should return in COMPOSITE the composite name refering to + that name in that directory, in malloced storage. */ +error_t +ftp_conn_unix_append_name (struct ftp_conn *conn, + const char *dir, const char *name, + char **composite) +{ + char *path = malloc (strlen (dir) + 1 + strlen (name) + 1); + + if (! path) + return ENOMEM; + + /* Form the path name. */ + if (name && *name) + if (dir[0] == '/' && dir[1] == '\0') + stpcpy (stpcpy (path, dir), name); + else + stpcpy (stpcpy (stpcpy (path, dir), "/"), name); + else + strcpy (path, dir); + + *composite = path; + + return 0; +} + +/* If the name of a file *NAME is a composite name (containing both a + filename and a directory name), this function should change *NAME to be + the name component only; if the result is shorter than the original + *NAME, the storage pointed to it may be modified, otherwise, *NAME + should be changed to point to malloced storage holding the result, which + will be freed by the caller. */ +error_t +ftp_conn_unix_basename (struct ftp_conn *conn, char **name) +{ + *name = basename (*name); + return 0; +} diff --git a/libftpconn/xfer.c b/libftpconn/xfer.c new file mode 100644 index 00000000..762fd6a7 --- /dev/null +++ b/libftpconn/xfer.c @@ -0,0 +1,281 @@ +/* Start/stop data channel transfer + + 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 <errno.h> +#include <string.h> +#include <netinet/in.h> + +#include <ftpconn.h> +#include "priv.h" + +/* Open an active data connection, returning the file descriptor in DATA. */ +static error_t +ftp_conn_start_open_actv_data (struct ftp_conn *conn, int *data) +{ + error_t err = 0; + /* DCQ is a socket on which to listen for data connections from the server. */ + int dcq; + struct sockaddr *addr = conn->actv_data_addr; + size_t addr_len = sizeof *addr; + + if (! addr) + /* Generate an address for the data connection (which we must know, + so we can tell the server). */ + { + addr = conn->actv_data_addr = malloc (sizeof (struct sockaddr_in)); + if (! addr) + return ENOMEM; + + /* Get the local address chosen by the system. */ + if (conn->control < 0) + err = EBADF; + else if (getsockname (conn->control, addr, &addr_len) < 0) + err = errno; + + if (err == EBADF || err == EPIPE) + /* Control connection has closed; reopen it and try again. */ + { + err = ftp_conn_open (conn); + if (!err && getsockname (conn->control, addr, &addr_len) < 0) + err = errno; + } + + if (err) + { + free (addr); + conn->actv_data_addr = 0; + return err; + } + } + + dcq = socket (AF_INET, SOCK_STREAM, 0); + if (dcq < 0) + return errno; + + /* Let the system choose a port for us. */ + ((struct sockaddr_in *)addr)->sin_port = 0; + + /* Use ADDR as the data socket's local address. */ + if (!err && bind (dcq, addr, addr_len) < 0) + err = errno; + + /* See what port was chosen by the system. */ + if (!err && getsockname (dcq, addr, &addr_len) < 0) + err = errno; + + /* Set the incoming connection queue length. */ + if (!err && listen (dcq, 1) < 0) + err = errno; + + if (err) + close (dcq); + else + err = ftp_conn_send_actv_addr (conn, conn->actv_data_addr); + + if (! err) + *data = dcq; + + return err; +} + +/* Finish opening the active data connection *DATA opened with + ftp_conn_start_open_actv_data, following the sending of the command that + uses the connection to the server. This function closes the file + descriptor in *DATA, and returns a new file descriptor for the actual data + connection. */ +static error_t +ftp_conn_finish_open_actv_data (struct ftp_conn *conn, int *data) +{ + struct sockaddr_in rmt_addr; + size_t rmt_addr_len = sizeof rmt_addr; + int real = accept (*data, &rmt_addr, &rmt_addr_len); + + close (*data); + + if (real < 0) + return errno; + + *data = real; + + return 0; +} + +/* Abort an active data connection open sequence; this function should be + called if ftp_conn_start_open_actv_data succeeds, but an error happens + before ftp_conn_finish_open_actv_data can be called. */ +static void +ftp_conn_abort_open_actv_data (struct ftp_conn *conn, int data) +{ + close (data); +} + +/* Return a data connection, which may not be in a completely open state; + this call should be followed by the command that uses the connection, and + a call to ftp_conn_finish_open_data, if that succeeds. */ +static error_t +ftp_conn_start_open_data (struct ftp_conn *conn, int *data) +{ + error_t err; + + if (conn->use_passive) + /* First try a passive connection. */ + { + struct sockaddr *addr; + + /* Tell the server we wan't to use passive mode, for which it should + give us an address to connect to. */ + 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); + } + } + else + err = EAGAIN; + + if (err) + /* Using a passive connection didn't work, try an active one. */ + { + conn->use_passive = 0; /* Don't try again. */ + err = ftp_conn_start_open_actv_data (conn, data); + } + + return err; +} + +/* Finish opening the data connection *DATA opened with + ftp_conn_start_open_data, following the sending of the command that uses + the connection to the server. This function may change *DATA, in which + case the old file descriptor is closed. */ +static error_t +ftp_conn_finish_open_data (struct ftp_conn *conn, int *data) +{ + if (conn->use_passive) + /* Passive connections should already have been completely opened. */ + return 0; + else + return ftp_conn_finish_open_actv_data (conn, data); +} + +/* Abort a data connection open sequence; this function should be called if + ftp_conn_start_open_data succeeds, but an error happens before + ftp_conn_finish_open_data can be called. */ +static void +ftp_conn_abort_open_data (struct ftp_conn *conn, int data) +{ + if (conn->use_passive) + close (data); + else + return ftp_conn_abort_open_actv_data (conn, data); +} + +/* 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_start_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) + ftp_conn_abort_open_data (conn, *data); + else + err = ftp_conn_finish_open_data (conn, 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; +} + +/* 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, ftp_conn_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, ftp_conn_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, ftp_conn_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, ftp_conn_poss_file_errs, data); +} diff --git a/libftpconn/xinl.c b/libftpconn/xinl.c new file mode 100644 index 00000000..a975b80d --- /dev/null +++ b/libftpconn/xinl.c @@ -0,0 +1,24 @@ +/* Real definitions for extern inline functions in priv.h + + Copyright (C) 1997 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu>. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The GNU C Library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#define FTP_CONN_EI +#include <ftpconn.h> +#include "priv.h" |