aboutsummaryrefslogtreecommitdiff
path: root/libftpconn/unix.c
diff options
context:
space:
mode:
authorMiles Bader <miles@gnu.org>1997-05-07 16:22:54 +0000
committerMiles Bader <miles@gnu.org>1997-05-07 16:22:54 +0000
commitd3f310caacfa6a0f943ba67442b7ce69b4d6d716 (patch)
tree6f8f69ddffa5b1ed2d57eb03cd91302fcb180488 /libftpconn/unix.c
parent058a49ce4cc8840bb4af386f5754c3eb466c4422 (diff)
downloadhurd-d3f310caacfa6a0f943ba67442b7ce69b4d6d716.tar.gz
hurd-d3f310caacfa6a0f943ba67442b7ce69b4d6d716.tar.bz2
hurd-d3f310caacfa6a0f943ba67442b7ce69b4d6d716.zip
Initial checkin.
Diffstat (limited to 'libftpconn/unix.c')
-rw-r--r--libftpconn/unix.c599
1 files changed, 599 insertions, 0 deletions
diff --git a/libftpconn/unix.c b/libftpconn/unix.c
new file mode 100644
index 00000000..52cfc105
--- /dev/null
+++ b/libftpconn/unix.c
@@ -0,0 +1,599 @@
+/* Unix-specific ftpconn hooks
+
+ Copyright (C) 1997 Free Software Foundation, Inc.
+
+ Written by Miles Bader <miles@gnu.ai.mit.edu>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
+
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#include <errno.h>
+#include <pwd.h>
+#include <grp.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <hurd/hurd_types.h>
+
+#include <ftpconn.h>
+
+/* Uid/gid to use when we don't know about a particular user name. */
+#define DEFAULT_UID 65535
+#define DEFAULT_GID 65535
+
+struct ftp_conn_syshooks ftp_conn_unix_syshooks = {
+ ftp_conn_unix_pasv_addr, ftp_conn_unix_interp_err,
+ ftp_conn_unix_start_get_stats, ftp_conn_unix_cont_get_stats
+};
+
+/* Try to get an internet address out of the reply to a PASV command.
+ Unfortunately, the format of the reply such isn't standardized. */
+error_t
+ftp_conn_unix_pasv_addr (struct ftp_conn *conn, const char *txt,
+ struct sockaddr **addr)
+{
+ unsigned a0, a1, a2, a3; /* Parts of the inet address */
+ unsigned p0, p1; /* Parts of the prot (msb, lsb) */
+
+ if (sscanf (txt, "%*[^0-9]%d,%d,%d,%d,%d,%d", &a0,&a1,&a2,&a3, &p0,&p1) != 6)
+ return EGRATUITOUS;
+ else
+ {
+ unsigned char *a, *p;
+
+ *addr = malloc (sizeof (struct sockaddr_in));
+ if (! *addr)
+ return ENOMEM;
+
+ (*addr)->sa_len = sizeof (struct sockaddr_in);
+ (*addr)->sa_family = AF_INET;
+
+ a = (unsigned char *)&((struct sockaddr_in *)*addr)->sin_addr.s_addr;
+ a[0] = a0 & 0xff;
+ a[1] = a1 & 0xff;
+ a[2] = a2 & 0xff;
+ a[3] = a3 & 0xff;
+
+ p = (unsigned char *)&((struct sockaddr_in *)*addr)->sin_port;
+ p[0] = p0 & 0xff;
+ p[1] = p1 & 0xff;
+
+ return 0;
+ }
+}
+
+/* Compare strings P & Q in a most forgiving manner, ignoring case and
+ everything but alphanumeric characters. */
+static int
+strlaxcmp (const char *p, const char *q)
+{
+ for (;;)
+ {
+ int ch1, ch2;
+
+ while (*p && !isalnum (*p))
+ p++;
+ while (*q && !isalnum (*q))
+ q++;
+
+ if (!*p || !*q)
+ break;
+
+ ch1 = tolower (*p);
+ ch2 = tolower (*q);
+ if (ch1 != ch2)
+ break;
+
+ p++;
+ q++;
+ }
+
+ return *p - *q;
+}
+
+/* Try to convert an error message in TXT into an error code. POSS_ERRS
+ contains a list of likely errors to try; if no other clue is found, the
+ first thing in poss_errs is returned. */
+error_t ftp_conn_unix_interp_err (struct ftp_conn *conn, const char *txt,
+ const error_t *poss_errs)
+{
+ const char *p;
+ const error_t *e;
+
+ if (!poss_errs || !poss_errs[0])
+ return EIO;
+
+ /* ignore everything before the last colon. */
+ p = strrchr (txt, ':');
+ if (p)
+ p++;
+ else
+ p = txt;
+
+ /* Now, for each possible error, do a string compare ignoring case and
+ anything non-alphanumberic. */
+ for (e = poss_errs; *e; e++)
+ if (strlaxcmp (p, strerror (*e)) == 0)
+ return *e;
+
+ return poss_errs[0];
+}
+
+struct get_stats_state
+{
+ char *name; /* Last read (maybe partial) name. */
+ int name_partial; /* True if NAME isn't complete. */
+ struct stat stat; /* Last read stat info. */
+ size_t buf_len; /* Length of contents in BUF. */
+ int start; /* True if at beginning of output. */
+ char buf[7000];
+};
+
+/* Start an operation to get a list of file-stat structures for NAME (this
+ is often similar to ftp_conn_start_dir, but with OS-specific flags), and
+ return a file-descriptor for reading on, and a state structure in STATE
+ suitable for passing to cont_get_stats. FORCE_DIR controls what happens if
+ NAME refers to a directory: if FORCE_DIR is false, STATS will contain
+ entries for all files *in* NAME, and if FORCE_DIR is true, it will
+ contain just a single entry for NAME itself (or an error will be
+ returned when this isn't possible). */
+error_t
+ftp_conn_unix_start_get_stats (struct ftp_conn *conn,
+ const char *name, int force_dir,
+ int *fd, void **state)
+{
+ error_t err;
+ struct get_stats_state *s = malloc (sizeof (struct get_stats_state));
+
+ if (! s)
+ return ENOMEM;
+
+ if (force_dir)
+ {
+ char *dir_op;
+
+ if (asprintf (&dir_op, "-d %s", name) <= 0)
+ return errno;
+
+ err = ftp_conn_start_dir (conn, dir_op, fd);
+
+ free (dir_op);
+ }
+ else
+ err = ftp_conn_start_dir (conn, name, fd);
+
+ if (err)
+ free (s);
+ else
+ {
+ s->name = 0;
+ s->name_partial = 0;
+ s->buf_len = 0;
+ s->start = 1;
+ *state = s;
+ }
+
+ return err;
+}
+
+static char *months[] =
+{
+ "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct",
+ "nov", "dec", 0
+};
+
+/* Translate the information in the ls output in *LINE as best we can into
+ STAT, and update *LINE to point to the filename at the end of the line.
+ If *LINE should be ignored, EAGAIN is returned. */
+static error_t
+parse_dir_entry (char **line, struct stat *stat)
+{
+ char **m;
+ struct tm tm;
+ char *p = *line, *e;
+
+ /*
+drwxrwxrwt 3 root wheel 1024 May 1 16:58 /tmp
+drwxrwxrwt 5 root daemon 4096 May 1 17:15 /tmp
+drwxrwxrwt 4 root 0 1024 May 1 14:34 /tmp
+drwxrwxrwt 6 root wheel 284 May 1 12:46 /tmp
+drwxrwxrwt 4 sys sys 482 May 1 17:11 /tmp
+drwxrwxrwt 7 34 archive 512 May 1 14:28 /tmp
+ */
+
+ if (strncasecmp (p, "total ", 6) == 0)
+ return EAGAIN;
+
+ bzero (stat, sizeof *stat);
+
+ stat->st_fstype = FSTYPE_FTP;
+
+ /* File format (S_IFMT) bits. */
+ switch (*p++)
+ {
+ case '-': stat->st_mode |= S_IFREG; break;
+ case 'd': stat->st_mode |= S_IFDIR; break;
+ case 'c': stat->st_mode |= S_IFCHR; break;
+ case 'b': stat->st_mode |= S_IFBLK; break;
+ case 'l': stat->st_mode |= S_IFLNK; break;
+ case 's': stat->st_mode |= S_IFSOCK; break;
+ case 'p': stat->st_mode |= S_IFIFO; break;
+ default: return EGRATUITOUS;
+ }
+
+ /* User perm bits. */
+ switch (*p++)
+ {
+ case '-': break;
+ case 'r': stat->st_mode |= S_IRUSR; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'w': stat->st_mode |= S_IWUSR; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'x': stat->st_mode |= S_IXUSR; break;
+ case 's': stat->st_mode |= S_IXUSR | S_ISUID; break;
+ case 'S': stat->st_mode |= S_ISUID; break;
+ default: return EGRATUITOUS;
+ }
+
+ /* Group perm bits. */
+ switch (*p++)
+ {
+ case '-': break;
+ case 'r': stat->st_mode |= S_IRGRP; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'w': stat->st_mode |= S_IWGRP; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'x': stat->st_mode |= S_IXGRP; break;
+ case 's': stat->st_mode |= S_IXGRP | S_ISGID; break;
+ case 'S': stat->st_mode |= S_ISGID; break;
+ default: return EGRATUITOUS;
+ }
+
+ /* `Other' perm bits. */
+ switch (*p++)
+ {
+ case '-': break;
+ case 'r': stat->st_mode |= S_IROTH; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'w': stat->st_mode |= S_IWOTH; break;
+ default: return EGRATUITOUS;
+ }
+ switch (*p++)
+ {
+ case '-': break;
+ case 'x': stat->st_mode |= S_IXOTH; break;
+ case 't': stat->st_mode |= S_IXOTH | S_ISVTX; break;
+ case 'T': stat->st_mode |= S_ISVTX; break;
+ default: return EGRATUITOUS;
+ }
+
+#define SKIP_WS() \
+ while (isspace (*p)) p++;
+#define PARSE_INT() ({ \
+ unsigned u = strtoul (p, &e, 10); \
+ if (e == p || isalnum (*e)) \
+ return EGRATUITOUS; \
+ p = e; \
+ u; \
+ })
+
+ /* Link count. */
+ SKIP_WS ();
+ stat->st_nlink = PARSE_INT ();
+
+ /* File owner. */
+ SKIP_WS ();
+ if (isdigit (*p))
+ stat->st_uid = PARSE_INT ();
+ else
+ {
+ struct passwd *pw;
+
+ e = p + strcspn (p, " \t\n");
+ *e++ = '\0';
+
+ pw = getpwnam (p);
+
+ if (pw)
+ stat->st_uid = pw->pw_uid;
+ else
+ stat->st_uid = DEFAULT_UID;
+
+ p = e;
+ }
+ stat->st_author = stat->st_uid;
+
+ /* File group. */
+ SKIP_WS ();
+ if (isdigit (*p))
+ stat->st_gid = PARSE_INT ();
+ else
+ {
+ struct group *gr;
+
+ e = p + strcspn (p, " \t\n");
+ *e++ = '\0';
+
+ gr = getgrnam (p);
+
+ if (gr)
+ stat->st_gid = gr->gr_gid;
+ else
+ stat->st_gid = DEFAULT_GID;
+
+ p = e;
+ }
+
+ /* File size / device numbers. */
+ SKIP_WS ();
+ if (S_ISCHR (stat->st_mode) || S_ISBLK (stat->st_mode))
+ /* Block and character devices show the block params instead of the file
+ size. */
+ {
+ stat->st_dev = PARSE_INT ();
+ if (*p != ',')
+ return EGRATUITOUS;
+ stat->st_dev = (stat->st_dev << 8) | PARSE_INT ();
+ stat->st_size = 0;
+ }
+ else
+ /* File size. */
+ stat->st_size = PARSE_INT ();
+
+ stat->st_blocks = stat->st_size >> 9;
+
+ /* Date. Ick. */
+ /* Formats: MONTH DAY HH:MM and MONTH DAY YEAR */
+
+ bzero (&tm, sizeof tm);
+
+ SKIP_WS ();
+ e = p + strcspn (p, " \t\n");
+ for (m = months; *m; m++)
+ if (strncasecmp (*m, p, e - p) == 0)
+ {
+ tm.tm_mon = m - months;
+ break;
+ }
+ if (! *m)
+ return EGRATUITOUS;
+ p = e;
+
+ SKIP_WS ();
+ tm.tm_mday = PARSE_INT ();
+
+ SKIP_WS ();
+ if (p[1] == ':' || p[2] == ':')
+ {
+ struct tm *now_tm;
+ struct timeval now_tv;
+
+ tm.tm_hour = PARSE_INT ();
+ p++;
+ tm.tm_min = PARSE_INT ();
+
+ if (gettimeofday (&now_tv, 0) != 0)
+ return errno;
+
+ now_tm = localtime (&now_tv.tv_sec);
+ if (now_tm->tm_mon < tm.tm_mon)
+ tm.tm_year = now_tm->tm_year - 1;
+ else
+ tm.tm_year = now_tm->tm_year;
+ }
+ else
+ tm.tm_year = PARSE_INT () - 1900;
+
+ stat->st_mtime = mktime (&tm);
+ if (stat->st_mtime == (time_t)-1)
+ return EGRATUITOUS;
+
+ /* atime and ctime are the same as mtime. */
+ stat->st_atime = stat->st_ctime = stat->st_mtime;
+
+ /* Update *LINE to point to the filename. */
+ SKIP_WS ();
+ *line = p;
+
+ return 0;
+}
+
+/* Read stats information from FD, calling ADD_STAT for each new stat (HOOK
+ is passed to ADD_STAT). FD and STATE should be returned from
+ start_get_stats. If this function returns EAGAIN, then it should be
+ called again to finish the job (possibly after calling select on FD); if
+ it returns 0, then it is finishe,d and FD and STATE are deallocated. */
+error_t
+ftp_conn_unix_cont_get_stats (struct ftp_conn *conn, int fd, void *state,
+ ftp_conn_add_stat_fun_t add_stat, void *hook)
+{
+ char *p, *nl;
+ ssize_t rd;
+ size_t name_len;
+ error_t err = 0;
+ struct get_stats_state *s = state;
+
+ /* We always consume full lines, so we know that we have to read more when
+ we first get called. */
+ rd = read (fd, s->buf + s->buf_len, sizeof (s->buf) - s->buf_len);
+ if (rd < 0)
+ {
+ err = errno;
+ goto finished;
+ }
+
+ if (rd == 0)
+ /* EOF */
+ if (s->buf_len == 0)
+ /* We're done! Clean up and return the result in STATS. */
+ {
+ if (s->start)
+ /* No output at all. From many ftp servers, this means that the
+ specified file wasn't found. */
+ err = ENOENT;
+ goto finished;
+ }
+ else
+ /* Partial line at end of file? */
+ nl = s->buf + s->buf_len;
+ else
+ /* Look for a new line in what we read (we know that there weren't any in
+ the buffer before that). */
+ {
+ nl = memchr (s->buf + s->buf_len, '\n', rd);
+ s->buf_len += rd;
+ }
+
+ s->start = 0; /* We've read past the start. */
+
+ if (!nl && s->buf_len < sizeof (s->buf))
+ /* We didn't find any newlines (which implies we didn't hit EOF), and we
+ still have room to grow the buffer, so just wait until next time to do
+ anything. */
+ return EAGAIN;
+
+ /* Where we start parsing. */
+ p = s->buf;
+
+ do
+ {
+ if (! s->name_partial)
+ /* We aren't continuing to read an overflowed name from the previous
+ call, so we know that we are at the start of a line, and can parse
+ the info here as a directory entry. */
+ {
+ /* Parse the directory entry info, updating P to point to the
+ beginning of the name. */
+ err = parse_dir_entry (&p, &s->stat);
+ if (err == EAGAIN)
+ /* This line isn't a real entry and should be ignored. */
+ goto skip_line;
+ if (err)
+ goto finished;
+ }
+
+ /* Now fill in S->last_stat's name field, possibly extending it from a
+ previous buffer. */
+ name_len = (nl ? nl - p : s->buf + s->buf_len - p);
+ if (name_len > 0 && p[name_len - 1] == '\r')
+ name_len--;
+ if (name_len > 0)
+ if (s->name)
+ /* Extending s->name. */
+ {
+ size_t old_len = strlen (s->name);
+ char *new_name = realloc (s->name, old_len + name_len + 1);
+ if (! new_name)
+ goto enomem;
+ s->name = new_name;
+ strncpy (new_name + old_len, p, name_len);
+ new_name[old_len + name_len] = '\0';
+ }
+ else
+ /* A new name. */
+ {
+ s->name = malloc (name_len + 1);
+ if (! s->name)
+ goto enomem;
+ strncpy (s->name, p, name_len);
+ s->name[name_len] = '\0';
+ }
+
+ if (nl)
+ {
+ char *symlink_target = 0;
+
+ if (S_ISLNK (s->stat.st_mode))
+ /* A symlink, see if we can find the link target. */
+ {
+ char *lsep = strstr (s->name, " -> ");
+ if (lsep)
+ {
+ *lsep = '\0';
+ lsep += 4;
+ symlink_target = strdup (lsep);
+ s->name = realloc (s->name, (lsep - 3) - s->name);
+ }
+ }
+
+ /* Call the callback function to process the current entry; it is
+ responsible for freeing S->name and SYMLINK_TARGET. */
+ err = (*add_stat) (s->name, &s->stat, symlink_target, hook);
+ if (err)
+ goto finished;
+
+ s->name = 0;
+ s->name_partial = 0;
+
+ skip_line:
+ p = nl + 1;
+ nl = memchr (p, '\n', s->buf + s->buf_len - p);
+ }
+ else
+ /* We found no newline, so the name extends past what we read; we'll
+ try to read more next time. */
+ {
+ s->name_partial = 1;
+ /* Skip over the partial name for the next iteration. */
+ p += name_len;
+ }
+ }
+ while (nl);
+
+ /* Move any remaining characters in the buffer to the beginning for the
+ next call. */
+ s->buf_len -= (p - s->buf);
+ if (s->buf_len > 0)
+ memmove (s->buf, p, s->buf_len);
+
+ /* Try again later. */
+ return EAGAIN;
+
+enomem:
+ /* Some memory allocation failed. */
+ err = ENOMEM;
+
+finished:
+ /* We're finished (with an error if ERR != 0), deallocate everything &
+ return. */
+ if (s->name)
+ free (s->name);
+ free (s);
+ close (fd);
+
+ return err;
+}