Filewatcher File Search
FTP Search
  
Directory 
  
Content Search 
   
pkg://lurkftp-0.99-1.src.rpm:58404/lurkftp-0.99.tar.gz  info  downloads

lurkftp/ 40755    700     24           0  6367723207  10754 5ustar  darkuserslurkftp/ftpsupt.c100644    700     24       17155  6364673150  12751 0ustar  darkusers/*
 * Support routines for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"
#include <setjmp.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <pwd.h>

/* some generic scratch buffers that prevent this */
/* from ever being thread-safe */
static char buf[4096];
static char name[4096];

#define ret_lo(x) do{ftp_closeconn(site);return (x);}while(0)

#define copyname(s) copystr(s,strlen(s),&dir->names)

/* read remote dir */
static int doftpdir(struct dirmem *dir, struct ftpsite *site,
		    const char **dirs, int ndirs, int recurse,
		    const regex_t *xfilt)
{      
    char *cd, *s;
    unsigned long i;
    int j, noparm = 0;
    struct fnamemem *fm = NULL; /* initialized to satisfy GCC */
    struct fname *f;
    int res;

    if((res = ftp_openconn(site)) != ERR_OK)
      ret_lo(res);
    if((res = ftp_type(site, "A")) != ERR_OK)
      ret_lo(res);
    for(;ndirs--;dirs++) {
	s = buf;
	if(**dirs != '/') {
	    if(ftp_getwd(site, buf) != ERR_OK)
	      ret_lo(ERR_PWD);
	    s = buf+strlen(buf);
	    if(s == buf || s[-1] != '/')
	      *s++ = '/';
	}
	strcpy(s,*dirs);
	s += strlen(s);
	if(s == buf || s[-1] != '/') {
	    *s++ = '/';
	    *s = 0;
	}
	cd = copyname(buf);
	if(!cd)
	  ret_lo(ERR_MEM);
	if(!recurse) {
	    /* first try single recursive list command */
	    strcpy(buf,cd);
	    /* strip trailing / for command */
	    if(buf[1])
	      buf[strlen(buf)-1] = 0;
	    if((i = ftp_port(site)) == ERR_OK &&
	       (i=ftp_cmd2(site, "LIST -lRa ", buf)) == ERR_OK)
	      i=parsels(dir,(read_func)ftp_readln,site,cd,cd);
	}
	/* special hack for non-dirs */
	/* this will force an error if non-dir isn't a symlink to a dir */
	if(!recurse && i == ERR_OK && dir->count &&
	   (f = lastname(&dir->files)) && /* should always be true */
	   !S_ISDIR(f->mode) &&
	   !strncmp(f->dir,f->name,strlen(f->dir)-1)) {
	    recurse = 1;
	    /* remove the non-dir from listing */
	    freelastname(&dir->files);
	    dir->count--;
	}
	/* try manual recursion if recursive list failed */
	if(recurse || i || !dir->count) {
	    s = cd;
	    i = dir->count-1;
	    f = NULL;
	    do {
		/* strip trailing / for command */
		strcpy(buf,cd);
		if(buf[1])
		  buf[strlen(buf)-1] = 0;
		if(ftp_chdir(site, buf) == ERR_OK) {
		    if(noparm || (j = ftp_port(site)) != ERR_OK ||
		       (j = ftp_cmd(site,"list -a")) != ERR_OK) {
			noparm = 1;
			if((j = ftp_port(site)) == ERR_OK)
			  j = ftp_cmd(site,"list");
		    }
		    if(j == ERR_OK) {
			if((j=parsels(dir,(read_func)ftp_readln,site,cd,s)) != ERR_OK)
			  ret_lo(j);
		    } else if(debug & DB_TRACE)
			  fprintf(stderr,"list -a: %s\n",ftperr(j));
		}
		/* the next stuff is in case server doesn't take parameters */
		/* for list, but doesn't report an error */
		if(!noparm && !dir->count) {
		    noparm = 1;
		    continue;
		}
		/* find new dir to cd to */
		while(++i<dir->count) {
		    if(!f) {
			for(j=i/NSKIP,fm=dir->files;j;j--)
			  fm = fm->nxt;
			f = &fm->names[NSKIP-1-i%NSKIP];
		    } else if(!(i % NSKIP)) {
			fm = fm->nxt;
			f = &fm->names[NSKIP-1];
		    } else
		      f--;
		    if(S_ISDIR(f->mode)) {
			strcpy(buf,f->dir);
			strcat(buf,f->name);
			cd = buf+strlen(buf);
			if(cd[-1] != '/') {
			    *cd++ = '/';
			    *cd = 0;
			}
			if(!xfilt || regexec(xfilt, buf, 0, NULL, 0)) {
			    cd = copyname(buf);
			    if(!cd)
			      ret_lo(ERR_MEM);
			    break;
			}
		    }
		}
	    } while(i < dir->count);
	}
    }
    i = toarray(dir);
    if(i)
      ret_lo(i);
    return i;
}

/* read remote dir w/ retry */
int readftpdir(struct dirmem *dir, struct ftpsite *site, const char **dirs,
	       int ndirs, int recurse, const regex_t *xfilt)
{
    int i,err = 0;
    struct timeval rt;

    for(i=site->to.retrycnt?site->to.retrycnt:-1;i;i--) {
	if(debug & DB_TRACE)
	  fprintf(stderr,"Trying %s [#%d]\n",site->host,site->to.retrycnt-i+1);
	if((err=doftpdir(dir, site, dirs, ndirs, recurse, xfilt)) && err != ERR_DIR) {
	    if(debug & DB_TRACE)
	      fprintf(stderr,"%s; waiting %d seconds\n",ftperr(err),site->to.retrytime);
	    rt.tv_sec = site->to.retrytime;
	    rt.tv_usec = 0;
	    select(0,NULL,NULL,NULL,&rt);
	} else
	  break;
    }
    /* return specific error even when max retries */
    /* this is so report shows exact error */
    return !err && !i ? ERR_MAXRETR : err;
}

/* read remote dir from remote file w/ retry */
int readftplsf(struct dirmem *dir, struct ftpsite *site, const char *dirs,
	       const char *rname)
{
    FILE *f;
    int err;
    char *s;

    strcpy(name,dirs);
    if((s = strchr(name,' ')))
      *s = 0;
    else
      s = name+strlen(name);
    if(s == name || s[-1] != '/') {
	*s++ = '/';
	*s = 0;
    }
    if(!(s = copyname(name)))
      return ERR_MEM;
    if(!(f = tmpfile())) {
	perror("tmp file for ftplsf");
	return ERR_OS;
    }
    if((err = getfile(f,site,rname))) {
	fclose(f);
	return err;
    }
    rewind(f);
    /* root may be inaccurate if dirs contains more than one dir, but */
    /* root is only used by mirroring, which guarantees only one dir */
    /* FIXME: no stdio read_func */
    err = parsels(dir,(read_func)NULL,f,s,s);
    fclose(f);
    return err;
}

/* retrieve a file: log in if necessary, try restart if f has data */
/* log off if error, else keep connection open */
/* return: 0 == ftp failure; -1 == write failure; 1 = success */
static int retrfile(FILE *f, struct ftpsite *site, const char *rf)
{
    long n;
    int ret;

    if((ret = ftp_openconn(site)) != ERR_OK)
      ret_lo(ret);
    if((ret = ftp_type(site, "I")) != ERR_OK)
      ret_lo(ret);
    if((ret = ftp_port(site)) != ERR_OK)
      ret_lo(ret);
    if((n = ftell(f)) > 0) {
	sprintf(buf,"%ld",n);
	if(ftp_cmd2(site, "REST ", buf) != ERR_OK)
	  rewind(f);
    }
    if((ret = ftp_cmd2(site, "RETR ", rf)) != ERR_OK && ret != ERR_CMD)
      ret_lo(ret);
    n = 0;
    do {
	if(n && fwrite(buf,n,1,f) != 1) {
	    perror("fwrite");
	    ret_lo(ERR_OS);
	}
    } while((n=ftp_read(site,buf,4096)) > 0 ||
	    errno == EAGAIN || errno == EINTR);
    if(n<0)
      ret_lo((int)-n);
    return 0; /* don't log out for next file */
}

/* get a file with retry */
int getfile(FILE *f, struct ftpsite *site, const char *fname)
{
    int i,err;
    struct timeval rt;

    for(err=0,i=site->to.retrycnt?site->to.retrycnt:-1;i;i--) {
	/* retrfile will set f to correct file position */
	if((err=retrfile(f, site, fname)) && err != ERR_OS && err != ERR_CMD) {
	    if(fflush(f)) {
		err = ERR_OS;
		break;
	    }
	    if(debug & DB_TRACE)
	      fprintf(stderr,"%s; waiting %d seconds\n",ftperr(err),site->to.retrytime);
	    rt.tv_sec = site->to.retrytime;
	    rt.tv_usec = 0;
	    select(0,NULL,NULL,NULL,&rt);
	} else
	  break;
    }
    /* always return MAXRETR if max retries reached */
    /* no specific error will be reported, anyway */
    /* return !i?ERR_MAXRETR:err; */
    return !i && !err?ERR_MAXRETR:err;
}

/* misc functions */
const char *ftperr(int err)
{
    switch(err) {
      case ERR_OK:
	return "No error";
      case ERR_TO:
	return "Timed out";
      case ERR_MEM:
	return "Out of memory";
      case ERR_DIR:
	return "Nothing in directory";
      case ERR_LOGIN:
	return "Can't log in";
      case ERR_PWD:
	return "Can't get current working directory";
      case ERR_OS:
	return "OS error";
      case ERR_CMD:
	return "Error sending command";
      case ERR_MAXRETR:
	return "Maximum retry count reached";
    }
    return "Unknown error";
}
lurkftp/ftpsupt.h100644    700     24        4274  6362440254  12727 0ustar  darkusers/*
 * Support routines' header for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

/* all things needed by either ftpsupt.c or lurkftp.c */
/* Yes, I know, this violates standard coding practices, but I'm lazy */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <regex.h>
#include "ftp.h"
#include <sys/sysmacros.h>

#ifndef major
#waring old major/minor defs for Linux
# ifdef __linux__
#include <linux/kdev_t.h>
#define major MAJOR
#define minor MINOR
#define makedev MKDEV
#else
#define MINORBITS (sizeof(dev_t)/2)
#define MINORMASK (~0UL>>(sizeof(unsigned long)-MINORBITS))
#define major(x) ((x)>>MINORBITS)
#define minor(x) ((x)&MINORMASK)
#ifndef makedev
#define makedev(maj,min) (((maj)<<MINORBITS)|(min))
#endif
#warning you may want to include better defs for major/minor
#endif
#endif

extern char *crpg, *uncrpg; /* compression programs */

extern struct tm curtm;

#define BUFLEN (4096-sizeof(struct strmem *)-sizeof(short))

struct strmem {
    struct strmem *nxt;
    short ptr;
    char buf[BUFLEN];
};

struct fname {
    const char *dir, *name, *lnk, *root;
    mode_t mode;
    unsigned long size;
    short year;
    char month, day;
    signed char hr, min;
    char flags;
};

#define FNF_MOVE	1
#define FNF_APPEND	2
#define FNF_MODEONLY	4
#define FNF_DONE	8
#define FNF_PROCESS	16

#define NSKIP 256

struct fnamemem {
    struct fnamemem *nxt;
    struct fname names[NSKIP];
};

struct dirmem {
    struct strmem *names;
    struct fnamemem *files;
    struct fname **array;
    unsigned long count;
};

enum {
    ERR_DIR = ERR_MAX,
    ERR_PWD,
    ERR_MAXRETR
};

struct myfile {
    void *data;
    int (*readln)(void *data, char *buf, int buflen);
    int (*read)(void *data, char *buf, int buflen);
    int (*write)(void *data, char *buf, int buflen);
};
    
lurkftp/lurkftp.1100644    700     24       50605  6367723511  12646 0ustar  darkusers." Man page for lurkftp
."
." (C) 1997 Thomas J. Moore
." questions/comments -> dark@mama.indstate.edu
." 
." This is free software.  No warranty for applicability or fitness is
." expressed or implied.
." 
." This code may be modified and distributed freely so long as the original
." author is credited and any changes are documented as such.
."
." .TH LURKFTP 1 "UNIX User's Manual"
.TH LURKFTP 1 "May 22, 1997" "" "FTP Site Lurker's Manual"
.SH NAME
lurkftp v0.99 \- monitor and/or mirror FTP sites
.SH SYNOPSIS
.I lurkftp
.RB "[\|" options "\|]"
.RB "[\|" site
.RB "[\|" dir "\|] .. ] .."
.SH DESCRIPTION
.I Lurkftp
is the ultimate FTP site lurker and mirroring program.  It will monitor
changes in source directories and either just report changes or mirror
changes into a destination directory.

.I Lurkftp
in its most basic mode takes site/directory-list "pairs", as follows:
.PP
.nf
site1\ /pub/dir1
site2\ /pub/dir2a\ /pub/dir2b
site3\ dir3
.fi
.PP
These pairs are either separated by newlines (if in an option file), by
the
.B  \-+
option (e.g. "lurkftp\ site1\ /pub/dir1\ \-+\ site2\ /pub/dir2"),
or by the EOF of an option file.

Once all options are parsed, processing begins with the first pair and (by
default) continues with subsequent pairs until all have been processed.

Default processing operates as follows:
.TP
\(bu
the directory is recursively read from the source site
.PP
.TP
\(bu
If no directory was readable from the source site, the line
.IB \fR` ***\  <site> \  <dirs> :\  <error> \ *** \fR'
is printed, and processing continues with the
next site.
.PP
.TP
\(bu
The results are compared with the `destination directory', which is by default
the contents of a placeholder file, normally called
.BI .chkls. <site><dirs> .gz\fR,
with `/''s and `\ ''s removed  from
the dirs list and replaced with `.' and `_', respectively.  Thus, the
default placeholder file for the "pair"
.RB ` site1
.B /pub/dir1
.BR /pub/dir2 '
is
.RB ` .chkls.site1.pub.dir1_.pub.dir2.gz '.
.PP
.TP
\(bu
If any changes occur, the line
.RB ` \-\-\-\ \fI<site>\fB\ \fI<dirs>\fB\ \-\-\- '
is printed, along with a list of changes, sorted by date, then name.  Each
change is preceded by a single character indicating the type of change.
Additions and removals are preceded by
.RB ` + '
and
.RB ` - ',
respectively.  Other prefixes are documented by the options which might
generate those prefixes.
.PP
.TP
\(bu
If any changes occurred, the destination placeholder file is updated.
.PP
.TP
\(bu
Processing then continues with the next site.
.SH OPTIONS
The entire pair list and/or each pair can be preceded by a list of
.IR options .
Actually, any
.I options
that precede the
.B \-+
option (explicit or implied) will apply to the given pair.
.br
Note:  argument-less options take the `+' prefix to mean the opposite of the
normal meaning.  Other options can also take `+', but the meaning doesn't
change.  The
.B \-+
.RB ( ++ )
.B \-h
.RB ( +h ),
and
.B \-\-
.RB ( +\- )
also don't change meaning.

.SS Percent Substitution
There are two types of %-substitution:  outside and inside.  Outside
substitution is for site-wide items, such as file names and report headers.
Inside substitution is for file-specific items, such as mirror pipes and
report lines.

The following outside-substitutions are done:
.RS
.TP
.B %s
Site name
.PP
.TP
.B %d
Underline-separated list of directories, substituting
.RB ` / '
with
.RB ` . '
and
.RB ` \  '
with
.RB ` _ '.
.PP
.TP
.B %p
Space-separated list of directories
.PP
.TP
.B %S
Alternate site
.PP
.TP
.B %D
Alternate
.BR %d \-style
list of directories
.PP
.TP
.B %P
Alternate
.BR %p \-style
list of directories
.PP
.TP
.B %t
Extra text; for headers/footers this is "totals" and for error messages this
is the actual error message.  Otherwise this is an empty string.
.PP
.TP
.B %%
The
.RB ` % '
character
.PP
.RE

The following inside-substitutions are done:
.RS
.TP
.B %f
File name without path
.PP
.TP
.B %L
The link name (if appropriate)
.PP
.TP
.B %r
Full directory path to remote file (without file name; with trailing 
.RB ` / ')
.PP
.TP
.B %l
Full directory path to local file (without file name; with trailing
.RB ` / ')
.PP
.TP
.B %s
Site name
.PP
.TP
.B %b
Size (in bytes)
.PP
.TP
.B %m
Mode (full mode, including file type)
.PP
.TP
.B %d
Modification date (YYYYMMDD)
.PP
.TP
.B %t
Modification time (HHMM)
.PP
.TP
.BI %{ <format> }
Modification date passed with
.I <format>
to
.IR strftime (3)
.PP
.TP
.B %D
The device major number (device nodes only)
.PP
.TP
.B %M
The device minor number (device nodes only)
.PP
.TP
.B %T
The type of operation ('+', '-', etc.)
.PP
.TP
.BI %[ conditional_text ]
Add %-substituted text conditionally.  The format of the conditional text is:
.I <condition>
[
.B ?
.I <true_text>
] [
.B :
.I <false_text>
].
Note that either the
.B ?
or the
.B :
or both must be present.  The
.I <condition>
is evaluated, and either the
.I <true_text>
or the
.I <false_text>
is evaluated and inserted as appropriate.  The following conditions are
available:
.RS
.TP
.BI B\fR[\fB=\fR|\fB>\fR|\fB<\fR][ size ]
Check byte-size of file.  If no directional specifier is given, then
.B >
is implied.  If no
.I size
is given, then 0 is implied.  Size may be specifed as number of bytes,
number of
.BR K ilobytes,
number of
.BR B locks,
or number of
.BR M egabytes
by adding (or omitting) the appropriate capital-lettered suffix.
.PP
.TP
.B l
True if directory entry is a soft link.
.PP
.TP
.B f
True if directory entry is a regular file.
.PP
.TP
.B d
True if directory entry is a directory.
.PP
.TP
.B s
True if directory entry is a socket.
.PP
.TP
.B b
True if directory entry is a block-special file.
.PP
.TP
.B c
True if directory entry is a character-special file.
.PP
.TP
.B p
True if directory entry is named pipe.
.PP
.TP
.B t
True if sticky bit is set for directory entry.
.PP
.TP
.RB [ ugo ] rwxS
Check permissions as specified by given pattern.
.B S
stands for setuid/setgid.  Permissions are anded with given mask (if no ugo
given, then all are implied) and true is returned if any bit is still set.
.PP
.TP
.BI T <type>
True if type of operation is equal to given
.I <type>
character.
.PP
.RE
.PP
.TP
.B %%
The
.RB ` % '
character
.PP
.RE

.SS Generic Directory Specification
All FTP sites may be specified as follows: [ [
.I <user>
] [
.BI , <acct>
] [
.BI : <pass>
]
.B @
] [
.I <host>
]
[
.BI , <port>
] [
.BI : <dir>
].  The
.B \-o
and
.B \-O
options take generic directory specs as arguments.  These are as follows:
.TP
.BI l <ls\-lR\ file>
This specifies a local file with a format parsable by the listing parser.
The file name is processed by outside %-substitution.
.PP
.TP
.BI c <command>
This specifies a command to run which will generate output parsable by the
listing parser.  This is usally an
.IR ls (1)
or a
.IR find (1)
command.  The command is processed by outside %-substitution.
.PP
.TP
.BI m <lsfile>
This specifies a lurkftp-generated placemarker file.  The file name is
processed by outside %-substitution.
.PP
.TP
.BI d <localdir>
This specifies a local directory to recursively read for directory entries.
Multiple directories may be specified.
.PP
.TP
.BI f <ftpsite>
This specifies a site+dir to recursively read for directory entries.
Multiple directories may be specified.
.PP
.TP
.BI L <ftpsite>
This specifies a remote file in a format parsable by the listing parser to
retrieve and use for the directory.
.PP

.SS General Options
.TP
.B \-B
Run in background:  close stdin/stdout/stderr, fork, and dissociate from
parent process group.
.I Lurkftp
should return immediately to the invoking process.
.PP
.TP
.BI \-F\  <filename>
Read an option file (immediately).  In option files, blank lines and
anything on a line after a `#' are ignored.  An implicit
.RB ` \-+ '
option (i.e. site/dir pair separator) is generated at the end of any line
containing a site and/or directory name.  Quotes
.RB (` \(aq '
and `\fB"\fR'), the `\fB\\\fR' character, and the
.RB ` ~ '
character in option files are handled as per
.IR csh (1).
Environment variables
.RI (\fB$ <name>
or
.BI ${ <name> }\fR)
are also expanded when not escaped by single quotes or backslash.
.PP
.TP
.B \-P
Process in parallel by calling
.IR fork (2)
before processing each site.
.PP
.TP
.B \-N
Indicate that subsequent operations depend on their predecessor.  That is,
forks will not separate these operations, and failure in one operation will
terminate all subsequent dependent operations.  There may be multiple
dependency groups.
.PP
.TP
.BI \-z\  <prog>
Program to filter all ls files through when writing (default:
.BR gzip ).  Setting this to an empty string disables output filtering.
.PP
.TP
.BI \-Z\  <prog>
Program to filter ls files or remote listings through if the first character
of the file in question is non-printable as per ANSI
.IR isprint (3).
(default:
.BR gunzip ).  Setting this to an empty string resets to the default.
.PP
.TP
.BI \-v\  <mask>
Set debug mask to
.IR <masks> .
Masks greater than 0 will produce some
.I lurkftp
trace messages.
.PP
.TP
.B \-\-
Next argument is literal.  Note that this differs from
.IR getopt (3)
in that it only literalizes the next option, not all remaining options.
.PP
.TP
.B \-+
Separate multiple site/dir groups.
.PP
.TP
.B \-h
Print help message and exit.
.PP
.SS Reporting Options
.TP
.B \-q
Suppress change report
.PP
.TP
.BR \-R\  <command>
If a report is generated, then pipe that report to the given command.
Otherwise don't invoke the command.
.PP
.TP
.BR \-r\ <type><string>
This option sets various report-related strings.
.RS
.TP
.I Type
.I Function
.PP
.TP
.B t
Sets the report's title string.  Outside %-substitution is performed on this
string.  The default is `\-\-\- %s %d \-\-\-'.
.PP
.TP
.B d
Sets the report's directory-entry line.  Inside %-substitution is performed
on this string.  The default is `%T %d %12b %r%f%[l? -> %L]' if
mirroring is turned on, and is the same, but surrounded by the conditional
`%[T<T>: ... ]' when mirroring is disabled so that moves are not reported.
.PP
.TP
.B f
Sets the report's footer string.  Outside %-substitution is performed on this
string.  The default is `%t'.
.PP
.TP
.B s
Sets the report's sort string.  The sort string is at most 8 comparison
specifiers, and sorting is ordered by performing each comparison in the
order of the string until a mismatch is found.  The default is `fdpnlst'.
The following comparison specifiers can be used, as well as the reverse-order
version (which is the same letter, but capitalized):

.RS
.TP
.B f
Sort numerically by file type.
.PP
.TP
.B m
Sort numerically by mode (other than file type).
.PP
.TP
.B p
Sort alphabetically by file's path.
.PP
.TP
.B n
Sort alphabetically by file's name.
.PP
.TP
.B l
Sort alphabetically by link name, if present.
.PP
.TP
.B d
Sort by date (ymd) if entry is a file.
.PP
.TP
.B t
Sort by time (hm) if entry is a file.
.PP
.TP
.B s
Sort by size (in bytes) if entry is a file.
.PP
.RE
.PP
.TP
.B T
Sets the error report's title string.  Outside %-substitution is performed
on this string.  The default is `\\n*** ERRORS IN %S %P \-> %s %p MIRROR ***'.
.PP
.TP
.B D
Sets the error report's directory-entry line.  Inside %-substitution is
performed on this string.
.PP
.TP
.B F
Sets the error report's footer string.  Outside %-substitution is performed
on this string.
.PP
.TP
.B S
Sets the error report's sort string.  The sort string is in the same format
as that used by the standard report.  The default is `PNLFDST'.
.PP
.TP
.B e
Sets the format for general error messages.  Outside %-substitution is
perfomed on this string.
.PP
.RE
.PP
.SS Site/Directory Specification Options
.TP
.BI \-o\  <dirspec>
Set generic source directory. 
.PP
.TP
.BI \-O\  <dirspec>
Set generic destination directory.
.TP
.BI \-p\  <password>
Set default FTP login password (default:
.IB <myusername> @\fR)
.PP
.TP
.BI \-b\  <base>
Change default name (formerly just base name) for placeholder files (default:
.BR .chkls.%s%d.gz ).  Outside %-substitution is performed on the name.
.TP
.BI \-L\  <rname>
Use file
.I <rname>
on remote site instead of performing remote directory listing.  Note:  this
option overrides the
.B -f
option below.  This option only affects the next site/dir pair.
.PP
.TP
.B \-U
Detect unchanged (i.e. moved) files.  If two regular files have the same
date, size, and name, but are located in different directories, then they
are processed as moved.  When no mirror directory or pipe is defined, moved
files are not reported; otherwise they are reported with
.RB ` < '
and
.RB ` > '
for the original and new location, respectively, and the file is not
retrieved from the remote site, but either ignored (if pipes are enabled) or
moved as if by
.IR mv (1)
if mirroring to a directory is enabled.  An error in moving will be reported
by the characters
.RB ` ( '
and
.RB ` ) '.
.PP
.TP
.B \-M
Force "manual" recursion when retrieving remote listing by using
.I LIST -la
or
.I LIST
(depending on which works) on each directory and issuing a
.I CWD
command to enter subdirectories.  This mode is invoked automatically if the
default
.I LIST -lRa
command fails for any reason (usually because the
.I -lRa
options aren't supported by the remote FTP daemon).  This is especially
useful if specific directories are to be filtered out, as the recursion
routine will match the name of the directory to be entered (with a trailing
.RB ` / ')
against the exclude filter before recursing.
.PP
.SS Mirroring Options
.TP
.B \-m
Perform mirroring when applicable; requires
.B \-d
and/or
.B \-e
and/or
.B \-t
options.  If this option is turned off, reports are still made, so this option
can be used to test what the results of a mirroring operation would be.
Beware:  List files are also updated, however, so some pseudo-directory tricks
to mirror-pipe specific files will pretend complete success.  (e.g. the
sunsite .lsm trick used in the example can't be harmlessly tested before
running).  Any failure to download a file (and, in the case of the
.B \-e
option, complete the pipe successfully) will be reported by an entry preceded
by the
.RB ` * '
character, and any failure to remove a file will be reported by an entry
preceded by the
.RB ` # '
character.
.PP
.TP
.BI \-d\  <ldir>
Set local directory to
.IR <ldir>
and read it instead of a placeholder file.  This option only applies to the
next site/dir pair.
.PP
.TP
.BI \-e\  <cmd>
Don't update the local directory when mirroring; instead pipe each new file
into
.IR <cmd> .
This option only applies to the next site/dir pair.  It would probably also
be useful to use the
.B \-l
and
.B \-f
options with this.  The local directory
.RB ( -d )
is only needed if the
.B %l
%-escape is used.  Inside %-substitution is perfomed on
.IR <cmd> .
.PP
.TP
.BI \-l\  <file>
Read and update placeholder
.I <file>
instead of using contents of local directory.  This option only affects the
next site/dir pair.  The same %-substitutions are done as for the
.B -b
option.
.PP
.TP
.BI \-f\  <file>
Read placeholder
.I <file>
instead of retrieving remote directory.  This option only affects the next
site/dir pair.  The same %-substitutions are done as for the
.B -b
option.  This option overrides the
.B -L
option above.
.PP
.TP
.B -E
Make "exact" comparison: fix modes to match remote site.  The report shows
changes which merely change modes by preceding them with a
.RB ` M '.
Failure to perform the change will be reported by preceding the
entry with
.RB ` $ '.
.PP
.TP
.B -n
Make no file transfers or moves, or deletions; just update date stamps [and
modes if the
.B -E
option is active].
.PP
.TP
.B \-A
Attempt to append to files which increase in size instead of downloading the
entire file.  This is useful in cases where a directory of log files which
always increase in size is to be mirrored.
.PP
.TP
.BI \-t\  <site>
Mirror source files to remote directory.
.PP
.TP
.B \-c
Force source files to be from local directory.
.PP
.TP
.BI \-g\  <pipe>
Get source files by executing
.IR <pipe> .
Inside %-substitution is done on
.IR <pipe> .
.PP
.SS Filtering Options
Note:  Only one include filter and/or one exclude filter can be specified.
The include filter is run first, and then the exclude filter.  Passing the
null string to the
.B \-i
or
.B \-x
options removes the associated filter.

.TP
.BI \-i\  <regex>
Include only files that match the extended regular expression
.IR <regex> .
.PP
.TP
.BI \-I\  <file>
Include only files that match the extended regular expression contained in
.IR <file> .
Newlines in
.I <file>
are converted to
.RB ` | '.
.PP
.TP
.BI \-x\  <regex>
Exclude any files that match the extended regular expression
.IR <regex> .
.PP
.TP
.BI \-X\  <file>
Exclude any files that match the extended regular expression contained in
.IR <file> .
Newlines in
.I <file>
are converted to
.RB ` | '.
.PP
.TP
.B \-D
Filter out directories.  Note that in order to handle automatic directory
processing properly, mirrors that use
.B \-f
to read placeholder files that were generated with this option should also
have this option in effect.
.PP
.TP
.B \-s
.I Don't
filter out specials (device nodes, pipes, and sockets).  Normally
they are filtered out.  Note that when mirroring device nodes and pipes
are created, but sockets aren't.
.PP
.SS Timeout Options
Note that all timeout options use the same base option,
.BR -T .
All timeout options can be specified with the same parameter string by
concatenating desired timeouts.  Also, any timeout set to zero is disabled
completely.
.TP
.BI \-T\ c <seconds>
Initial connection and login timeout (default: 20)
.PP
.TP
.BI \-T\ t <seconds\ per\ K>
Timeout for file and directory transfers (default: 10)
.PP
.TP
.BI \-T\ q <seconds>
Timeout for
.I quit
command and logout (default: 5)
.PP
.TP
.BI \-T\ r <count>
# of times to retry list and/or file retrievals before giving up (default: 10)
.PP
.TP
.BI \-T\ d <seconds>
Amount of time to wait between retries (default: 10)
.PP
.SH EXAMPLES
.SS Command lines
.nf
# Look for new versions of X for Linux & mail report to me
lurkftp -i Linux ftp.xfree86.org /pub/XFree86 -F .mailme

# .mailme is a file containing: -R 'mail -s "lurkftp output" dark'

# Mirror a single directory with reschedule;
# at will mail me the report.
atcron "2:00 tomorrow" lurkftp -m -d /net/ftp/rplay \\
   ftp.sdsu.edu /pub/rplay

# Mirror slackware disk set via sz into /usr/local/sw
# Not recommended if no auto-download in local comm program
lurkftp -d /usr/local/sw -l .sw.gz -e "ONAME=%l%f sz -" \\
   ftp.cdrom.com /pub/linux/slackware/slakware -F .mailme

# Do main lurking; see config files below
lurkftp -F .chksites
.fi
.SS Contents of .chksites
.nf
# An extract from my command file
# no multiple entries from same site, so simplify name
-b .chkls.%s.gz
-R 'mail -s "LurkFTP Output" dark' # mail reports to me
-D # Don't care about changes in directories
-U # ignore moves
-P # fork away!
-X .chkfilt.sunsite # special filter for sunsite
sunsite.unc.edu /pub/Linux # fetch master list
-N # .lsm stuff depends on sunsite
# mail new .lsm's to me
-i '.*\\.lsm$' -x /Incoming/ # include lsm's not in Incoming dir
-f .chkls.%s.gz # Read remote site from previously generated listing
#Note: the following file was primed so that old .lsms wouldn't
#be sent.  This was done by *not* using -m.  It could've also
# been primed by using the command:
# zgrep .lsm .chkls.sunsite.gz | gzip >.chkls.lsms.gz
-l .chkls.lsms.gz # Keep track of sent .lsm's in this file
-m -e 'mail -s "lurkftp: %f" dark' # mirror through pipe
sunsite.unc.edu /pub/Linux # same site/dir as above
-i "" # reset include filter
+N # No more dependencies
-X .chkfilt # filter for everyone else
tsx-11.mit.edu /pub/linux/680x0 /pub/linux/packages/GCC
ftp.kernel.org /pub/linux/kernel
# etc.
.fi
.SS Contents of .chkfilt
.nf
INDEX.whole
INDEX.short
ls-lR
/INDEX(|.html|.old)$
00-find-ls(|.gz)$
.fi
.SS Contents of .chkfilt.sunsite
.nf
/README$
/distributions/
/!INDEX
/archive-current/
linux-announce.archive
INDEX.whole
INDEX.short
ls-lR
/INDEX(|.html|.old)$
00-find-ls(|.gz)$
.fi
.SH SEE ALSO
.IR regexec (3),
.IR gzip (1),
.IR ftp (1),
.IR mail (1),
.IR at (1),
.IR mirror (1L).
.SH BUGS
.nf
[+: may want to fix; *: definitely want to fix; \-: may never fix]

* Doesn't handle non-UNIX remote sites [I know of none any more]
+ Some fixed-sized buffers may overflow
\- Groups & user names aren't mirrored
\- Sockets aren't mirrored
- Exact time isn't used for comparison (only accurate to what ls gives)
- All options in external program option group are obsolete
+ Few options are really range-checked
* Probably plenty of nasty hidden bugs
.fi
.SH DIAGNOSTICS
Failed transfers are marked in the report.  Specific errors are printed
to stderr.  Debugging messages and some error messages are only printed
when the debug level (as set by the
.B -v
option) is greater than 0.
.SH AUTHOR
Thomas J. Moore, dark@mama.indstate.edu
lurkftp/opt.html100644    700     24       21706  6345104636  12562 0ustar  darkusers<HEAD><TITLE>Lurkftp v1.0 Option file generator</TITLE>

<SCRIPT LANGUAGE="JavaScript">
<!-- Hide script from old browsers.
var state = 0;

var coms = "\<!--";
coms = "\<NOSCRIPT\>";
var come = "--\>";

function reloadme(which)
{
  document.options.state.value = which;
  document.options.ACTION.value = "CHST";
  document.options.submit();
}

function chkstate(which)
{
  if(parseInt(document.options.state.value) != which)
    document.writeln(coms);
}

function goRept()
{
  location.href = "rept.html";
}

function goTO()
{
  location.href = "to.html";
}

function goGlob()
{
  location.href = "glob.html";
}
// End Hide -->
</SCRIPT>

</HEAD>

<BODY>

<H1>Lurkftp v1.0 Option file generator</H1>

<FORM NAME="options" ACTION="frm.html" TARGET=_self METHOD=GET>

<INPUT TYPE="HIDDEN" NAME="state" VALUE="0">

<SCRIPT LANGUAGE="JavaScript">
<!--
chkstate(1);
// -->
</SCRIPT>

<P>
<TABLE>
<CAPTION><H3>Global Options</H3></CAPTION>
<TR><TH>Report Output Pipe</TH><TD><INPUT NAME=REPTPIPE></TD></TR>
<TR><TH>Default Placeholder Name</TH><TD><INPUT NAME=BASEN></TD></TR>
<TR><TH>Compress Command</TH><TD><INPUT NAME=COMPR></TD></TR>
<TR><TH>Uncompress Command</TH><TD><INPUT NAME=UCOMPR></TD></TR>
<TR><TH COLSPAN=2><INPUT TYPE=CHECKBOX NAME=BG>
Fork & Dissociate From Terminal</TD></TR>
</TABLE>
<SCRIPT LANGUAGE="JavaScript">
<!--
document.write('\
<INPUT TYPE=BUTTON VALUE="Back" onClick="reloadme(0)">\
');
//-->
</SCRIPT>
</P>

</NOSCRIPT>
<SCRIPT LANGUAGE="JavaScript">
<!--
chkstate(0);
// -->
</SCRIPT>

<P>
<TABLE>
<CAPTION><H3>Site Options</H3></CAPTION>
<TR><TH COLSPAN=2>Source Directory</TH>
<TH COLSPAN=2>Destination Directory</TH></TR>
<TR><TH>Dir type</TH><TD>
<SELECT NAME=SDIR>
 <OPTION SELECTED VALUE="f">FTP Site
 <OPTION VALUE="d">Local Dir
 <OPTION VALUE="p">Local Pipe
 <OPTION VALUE="l">Local ls-lR File
 <OPTION VALUE="L">Remote ls-lR File
 <OPTION VALUE="m">Local Placeholder
</SELECT>
<TH>Dir type</TH><TD>
<SELECT NAME=DDIR>
 <OPTION VALUE="f">FTP Site
 <OPTION VALUE="d">Local Dir
 <OPTION VALUE="p">Local Pipe
 <OPTION VALUE="l">Local ls-lR File
 <OPTION VALUE="L">Remote ls-lR File
 <OPTION SELECTED VALUE="m">Local Placeholder
</SELECT>
</TD></TR>
</TD>
<TR><TH>FTP Site</TH><TD><INPUT NAME="SSITE"></TD>
<TH>FTP Site</TH><TD><INPUT NAME="DSITE"></TD></TR>
<TR><TH>Directory</TH><TD>
<INPUT NAME="SDIR">
</TD>
<TH>Directory</TH><TD>
<INPUT NAME="DDIR">
</TD></TR>
<TR>
<TD COLSPAN=2 ALIGN=CENTER>
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Prev">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Del">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Add">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Next">
</TD>
<TD COLSPAN=2 ALIGN=CENTER>
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Prev">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Del">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Add">
<INPUT TYPE=SUBMIT NAME=ACTION VALUE="Next">
</TD>
</TR>
<TR><TH>File/Pipe</TH><TD><INPUT NAME="SFILE"></TD>
<TH>File/Pipe</TH><TD><INPUT NAME="DFILE"></TD></TR>
<TR><TH COLSPAN=4><HR WIDTH="100%"></TH></TR>
<TR><TH COLSPAN=4><INPUT TYPE=CHECKBOX NAME="FORK"> Fork
<INPUT TYPE=CHECKBOX NAME="MOVE"> Detect Moved Files
<INPUT TYPE=CHECKBOX NAME="RECURSE"> Force Manual List Recursion
<INPUT TYPE=CHECKBOX NAME="MODES"> Detect (and Change) Modes
</TH></TR>
<TR><TH>Default Passwd</TH><TD><INPUT NAME="APW"></TD>
<TH>Done Command</TH><TD><INPUT NAME="POSTCMD"></TD></TR>
<TR><TH>Include Filter</TH><TD><INPUT NAME=IFILT></TD>
<TD><INPUT TYPE=CHECKBOX NAME="IFFILE"> File</TD>
<TH><INPUT TYPE=CHECKBOX NAME="XDIR"> Exclude Dirs</TH></TR>
<TR><TH>Exclude Filter</TH><TD><INPUT NAME=XFILT></TD>
<TD><INPUT TYPE=CHECKBOX NAME="XFFILE"> File</TD>
<TH><INPUT TYPE=CHECKBOX NAME="XSPEC"> Exclude Specials</TH></TR>
<TR>
<TH>Mirror</TH><TD COLSPAN=3>
<INPUT TYPE=CHECKBOX NAME="MIREN"> Enable
<INPUT TYPE=CHECKBOX NAME="STAMP"> Just Update Date/Mode Stamp
<INPUT TYPE=CHECKBOX NAME="APPEND"> Append (instead of reget) files that grow
</TR>
<TR><TH>Mirror Source</TH><TD><SELECT NAME="MIRSRC">
 <OPTION VALUE="">None
 <OPTION VALUE="f">FTP Site
 <OPTION VALUE="d">Local Dir
 <OPTION VALUE="p">Pipe
</SELECT>
</TD><TH>Mirror Dest</TH><TD><SELECT NAME="MIRDST">
 <OPTION VALUE="">None
 <OPTION VALUE="f">FTP Site
 <OPTION VALUE="d">Local Dir
 <OPTION VALUE="p">Pipe
</SELECT>
</TD></TR>
<TR><TH>Pipe Command</TH><TD><INPUT NAME="SRCPIPE"></TD>
<TH>Pipe Command</TH><TD><INPUT NAME="DSTPIPE"></TD>
</TR>
<TR><TH COLSPAN=4><HR WIDTH="100%"></TH></TR>
<SCRIPT LANGUAGE="JavaScript">
<!--
document.write("\
<TH><INPUT TYPE='BUTTON' VALUE='Report Options' onClick='reloadme(2)'></TH>\
<TH><INPUT TYPE='BUTTON' VALUE='Timeout Options' onClick='reloadme(3)'></TH>\
<TH></TH>\
<TH><INPUT TYPE='BUTTON' VALUE='Global Options' onClick='reloadme(1)'></TH></TR>\
");
// -->
</SCRIPT>
<TR>
<TH><INPUT TYPE=SUBMIT NAME="ACTION" VALUE="Prev"></TH>
<TH><INPUT TYPE=SUBMIT NAME="ACTION" VALUE="Remove"></TH>
<TH><INPUT TYPE=SUBMIT NAME="ACTION" VALUE="Add"></TH>
<TH><INPUT TYPE=SUBMIT NAME="ACTION" VALUE="Next"></TH></TR>
<TR><TH COLSPAN=4><HR WIDTH="100%"></TH></TR>
</TABLE>
</P>

</NOSCRIPT>
<SCRIPT>
<!--
chkstate(2);
// -->
</SCRIPT>

<P>
<TABLE>
<CAPTION><H3>Report Options</H3></CAPTION>
<TR><TH COLSPAN=2>Standard Report</TH>
<TH ROWSPAN=6>Sort Order</TH><TD>
<SELECT NAME=SDATE>
 <OPTION SELECTED VALUE="1">1 Ascending
 <OPTION VALUE="2">2 Ascending
 <OPTION VALUE="3">3 Ascending
 <OPTION VALUE="4">4 Ascending
 <OPTION VALUE="5">5 Ascending
 <OPTION VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>Date
</TD></TR>
<TR><TH>Header</TH><TD><INPUT NAME="HEAD"></TD>
<TD>
<SELECT NAME=SDIRN>
 <OPTION VALUE="1">1 Ascending
 <OPTION SELECTED VALUE="2">2 Ascending
 <OPTION VALUE="3">3 Ascending
 <OPTION VALUE="4">4 Ascending
 <OPTION VALUE="5">5 Ascending
 <OPTION VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>Dir Name
</TD></TR>
<TR><TH>Footer</TH><TD><INPUT NAME="FOOT"></TD>
<TD>
<SELECT NAME=SFILEN>
 <OPTION VALUE="1">1 Ascending
 <OPTION VALUE="2">2 Ascending
 <OPTION SELECTED VALUE="3">3 Ascending
 <OPTION VALUE="4">4 Ascending
 <OPTION VALUE="5">5 Ascending
 <OPTION VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>File Name
</TD></TR>
<TR><TH>Item</TH><TD><INPUT NAME="ITEM"></TD>
<TD>
<SELECT NAME=SLINKN>
 <OPTION VALUE="1">1 Ascending
 <OPTION VALUE="2">2 Ascending
 <OPTION VALUE="3">3 Ascending
 <OPTION SELECTED VALUE="4">4 Ascending
 <OPTION VALUE="5">5 Ascending
 <OPTION VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>Link Name
</TD></TR>
<TR><TH COLSPAN=2>Mirror Report</TH>
<TD>
<SELECT NAME=SMODE>
 <OPTION VALUE="1">1 Ascending
 <OPTION VALUE="2">2 Ascending
 <OPTION VALUE="3">3 Ascending
 <OPTION VALUE="4">4 Ascending
 <OPTION SELECTED VALUE="5">5 Ascending
 <OPTION VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>Mode (&amp File Type)
</TD></TR>
<TR><TH>Header</TH><TD><INPUT NAME="MHEAD"></TD>
<TD>
<SELECT NAME=SSIZE>
 <OPTION VALUE="1">1 Ascending
 <OPTION VALUE="2">2 Ascending
 <OPTION VALUE="3">3 Ascending
 <OPTION VALUE="4">4 Ascending
 <OPTION VALUE="5">5 Ascending
 <OPTION SELECTED VALUE="6">6 Ascending
 <OPTION VALUE="-1">1 Descending
 <OPTION VALUE="-2">2 Descending
 <OPTION VALUE="-3">3 Descending
 <OPTION VALUE="-4">4 Descending
 <OPTION VALUE="-5">5 Descending
 <OPTION VALUE="-6">6 Descending
</SELECT>Size</TD></TR>
</TD></TR>
<TR><TH>Footer</TH><TD><INPUT NAME="MFOOT"></TD></TR>
<TR><TH>Item</TH><TD><INPUT NAME="MITEM"></TD>
<TH>Suppress</TH><TD><INPUT TYPE=RADIO VALUE="y" NAME=QUIET> Yes
<INPUT TYPE=RADIO CHECKED VALUE="n" NAME=QUIET> No</TD></TR>
<SCRIPT LANGUAGE="JavaScript">
<!--
document.write('\
<TR><TD><INPUT TYPE=BUTTON VALUE="Back" onClick="reloadme(0)"></TD></TR>\
');
//-->
</SCRIPT>
</TABLE>
</P>


</NOSCRIPT>
<SCRIPT>
<!--
chkstate(3);
// -->
</SCRIPT>

<P>
<TABLE>
<CAPTION><H3>Timeout Options</H3></CAPTION>
<TR><TH>Connection/Simple Command</TH><TD><INPUT NAME=CONNTO> Sec</TD></TR>
<TR><TH>Transfer Rate</TH><TD><INPUT NAME=TRATE> Sec/K</TD></TR>
<TR><TH>Quit/Logoff</TH><TD><INPUT NAME=QTO> Sec</TD></TR>
<TR><TH>Retry Count</TH><TD><INPUT NAME=RETRIES> Times</TD></TR>
<TR><TH>Retry Delay</TH><TD><INPUT NAME=RETRTO> Sec</TD></TR>
<SCRIPT LANGUAGE="JavaScript">
<!--
document.write('\
<TR><TD><INPUT TYPE=BUTTON VALUE="Back" onClick="reloadme(0)"></TD></TR>\
');
//-->
</SCRIPT>
</TABLE>
</P>


<!-- another
-->
</BODY>
lurkftp/TODO100644    700     24        1761  6367722025  11543 0ustar  darkusersClosely examine & fix problems with ftp.c
add telnet negotiation to ftp.c
Add protocol tweaking options (MODE/STRU/TYPE etc.)
Add string-message to header fmt
Move reported errors to new fmt
Check login/connect state; disconnect if not logged in to same site
Fix field limits in format strings
Make generic mir_transfer (status: 1/3 in, 2/3 out)
Add ls-lR file to dir src
Add generic FTP commands (cmdpipe/header parsing)
Make "text/binary" filter
Add "Dirscan-include" filter
allow nl in opt file for " & '
Check documentation
Maybe reimplement long help()
Implement opt.html/opt.cgi or equivalent
Auto-update link farm
Allow glob-style wc's, either from wc.c or pat.c
Make sure it still compiles w/ c89 (Solaris/AIX)
--- ideas from mirror I might implement:
Make "ignore" filter
Make option to ignore dates &/or ignore size &/or look for "newer" files
Add option for "skip mirror if too many changes"
Add option for "Force transfer of everything"
Add new list parsing routines (VMS/DOS/Mac) from lsparse.pl
lurkftp/ftp.h100644    700     24        3712  6362507274  12015 0ustar  darkusers#include <stdlib.h> /* mainly for sys/types.h on Sun */
#include <sys/socket.h>
#include <time.h>
#include <netinet/in.h>

#ifdef SOCKS
#define connect Rconnect
#define getsockname Rgetsockname
#define bind Rbind
#define accept Raccept
#define listen Rlisten
#define select Rselect
#endif

extern int debug;

#define DB_FTPOUT 1
#define DB_FTPIN  2

enum {
    ERR_OK, ERR_NOHOST, ERR_CONNECT, ERR_TO, ERR_LOGIN, ERR_OS, ERR_MEM, ERR_CMD,
    ERR_MAX
};

union sa {
    struct sockaddr_in in;
    struct sockaddr    sa;
};

#define TVTBUF 4096

struct tvt {
    union sa addr;
    int fd;
    unsigned char state;
    unsigned char flags;
    unsigned short buflen, bufp;
    char buf[TVTBUF];
};

enum {
    TS_NONE, TS_OPEN, TS_CONNECT
};

enum {
    FTP_READY = '1', FTP_DONE, FTP_MORE, FTP_EAGAIN, FTP_ERROR
};

struct to {
    unsigned short retrycnt, retrytime, connect, quit, xfer, cmd;
};

extern struct to defto; /* where to get default timeouts from */

struct ftpsite {
    const char *host;
    const char *user;
    const char *pass;
    const char *acct;
    unsigned short port;
    struct to to;
    const char *lastresp;
    /* private info */
    struct tvt tvt; /* control connection */
    struct tvt dfd; /* data conection */
    int dport; /* port command */
};

/* FTP stuff */
char ftp_cmd2(struct ftpsite *site, const char *cmd, const char *arg);
#define ftp_cmd(s,c) ftp_cmd2(s,c,NULL)
int ftp_openconn(struct ftpsite *site);
void ftp_closeconn(struct ftpsite *site);
int ftp_port(struct ftpsite *site);
int ftp_read(struct ftpsite *site, char *buf, int len);
int ftp_readln(struct ftpsite *site, char *buf, int len);
int ftp_write(struct ftpsite *site, const char *buf, int len);
void ftp_init(const char *progname);

/* general support */
#define ftp_type(s, fmt) ftp_cmd2(s, "TYPE ", fmt)
int ftp_getwd(struct ftpsite *site, char *buf);
#define ftp_chdir(site, dir) ftp_cmd2(site, "CWD ", dir)
int ftp_filetm(struct ftpsite *site, const char *name, struct tm *tm);
lurkftp/Makefile100644    700     24        2141  6367713420  12503 0ustar  darkusers# This makefile is too simple to require autoconf/imake/whatever.
# Edit as needed if SOCKS support or non-GCC/Linux support is needed.

#For SOCKS support, uncomment following lines
#SOCKS=-DSOCKS
#SLIB=-lsocks

# GCC:
CC=gcc -Wall -Wwrite-strings -Wstrict-prototypes
# XOPEN compliant systems:
#CC=c89 -D_XOPEN_SOURCE=1 -D_XOPEN_SOURCE_EXTENDED=1 # Sun: -D__EXTENSIONS__

#gcc
CFLAGS=-g3 -O2
#cc
#CFLAGS=-O

#for Solaris:
#LIBS=-lsocket -lnsl

IDIR=/u2/h0/dark
#IDIR=/usr/local
BINDIR=$(IDIR)/bin
MANDIR=$(IDIR)/man/man1

INSTALL=install -c
INSTSTRIP=-s
INSTFLAGS=

EXE = lurkftp pipe
SCRIPT = atcron

all: $(EXE)

OBJS = lurkftp.o opt.o diff.o rept.o mir.o misc.o ftp.o ftpsupt.o

pipe: pipe.o
	$(CC) -o $@ pipe.o

lurkftp: $(OBJS) $(LIB)
	$(CC) -o $@ $(OBJS) $(LIB) $(LIBS) $(SLIB)

install: $(EXE)
	$(INSTALL) $(INSTFLAGS) $(INSTSTRIP) $(EXE) $(BINDIR)
	$(INSTALL) $(INSTFLAGS) $(SCRIPT) $(BINDIR)
	rm -f $(MANDIR)/lurkftp.1 $(MANDIR)/lurkftp.1.gz $(MANDIR)/lurkftp.1.Z
	$(INSTALL) lurkftp.1 $(MANDIR)/lurkftp.1

$(OBJS): lurkftp.h ftpsupt.h ftp.h

clean:
	rm -f *.o core

realclean: clean
	rm -f $(EXE) .chk* *.bak
lurkftp/README100644    700     24       12557  6367722257  11767 0ustar  darkusers
lurkftp v0.99

What it is: This is the ultimate FTP site lurker program.  It is a simple
program to monitor changes in FTP sites and either just report changes or
mirror changes into a local directory.

So why another mirror program?  Because I couldn't get any of the mirror
programs on sunsite to do what I wanted half-way reliably & efficiently.

Read the man page for more details.

As far as I know, Alex deVries <adevries@engsoc.carleton.ca> has created an
RPM package for this (and uploaded it to ftp.redhat.com), and Christian
Schwarz <schwarz@monet.m.isar.de> has created a Debian package for this.
Thanks guys.  I do not (and probably will not) maintain anything other than
ftp://sunsite.unc.edu/pub/Linux/system/network/file-transfer/lurkftp*.tar.gz,
although I might change my mind and add distribution generation to a Makefile
instead of doing it manually.  If I ever decide to "upgrade" my system
(which will take forever, since I never log any of the plentiful changes I
make to the system), I'll make sure I put a recent rpm/dpkg on my system so
I can maintain these, too.

***************************************************************

Installation notes:

Based on bug reports by people who tried to run this on non-Linux systems,
this bugger should compile on just about any POSIX-compliant system.  If it
doesn't, then feel free to send me a compiler error dump or whatever.  The
only non-Linux system(s) I have access to for free are some Sun workstations
(Solaris 2.5).  I can probably  look up information on AIX 4.2/3.2.5 machines
and DEC Ultrix machines, too, although I can't compile on them.  I guess I
could also try getting it to work under AIX 2.1, but I'd rather not bother.

SOCKS support isn't something I use, so you'll have to fix it yourself if
it's broken.  Instead of using the big, nasty collection of -D's, I put
those defines into ftp.h and modified ftp.c appropriately.  You'll have to
modify the Makefile to include the -lsocks and the -DSOCKS, as shown
therein.

***************************************************************

Changelog

  0.9 - first release 4/23/97 - I thought it worked.  What did I know?

  0.91 - 4/25/97 - Major overhaul; Seems to work now
	man page cleaned up & updated
	-L (listing timeout) removed; use -T instead
	-P option output file corruption fixed
	-u (non-anonymous FTP) option added
	-z/Z (auto gzip/gunzip) options added
	-L (use remote ls-lR[.(Z|gz)] file) option added
	-U (detect moved files) option added
	-n (stamp only) option added
	-A (append) option added
	general code cleanups

  0.92 - 4/27/97 - minor bug fix + SVR4 fixes
        more examples added to man page (thanks to Andy Wick)
	Solaris fixes [i.e. removal of BSD calls] (thanks to Andy Wick &
          Tobias Oetiker & the dept. of Geo @ Indiana State University)
	Fixed -S option's "any output" detection (hopefully)

  0.93 - 4/29/97 - minor bug fix + new -M option
        Fixed version # in man page & code
	Fixed a few comparison & sorting bugs
	Added -M (force manual recursion) option

  0.94 - 4/30/97 - minor bug fix + csh-style option file parsing
	Removed . & .. from listing & recursion
	"Fixed" symlink-as-root problem
	"Fixed" cryptic "unreachable" error message
	Changed '/"/$/~ handling in parsing of files
	More man page cleanups
	Fixed ls-file written before downloads verified

  0.95 ("bugfree") - 5/2/97 - socks support + minor bug fixes
	Fixed include/exclude pattern reset
	Added socks support (thanks to Mark Hindess)
	Refixed SYSV support (thanks to Jens Schleusener)
	Refixed option parsing bugs introduced in 0.94

  0.96 ("redface") - - minor bug fixes
	Fixed minor listing bug (thanks again Jens Schleusener)
	Fixed SOCKS support (sorta)
	Fixed minor argument parsing bugs
	Removed superfluous MDTM command for soft links
	Changed LIST -A to -a (since "." and ".." are manually filtered anyway)
	Removed include filter from manual recursion

  0.99 ("bloated hog") - misc feature enhancements
  	Misc minor documentation updates
	Code split/cleanup
	-A (append) flag fixed, as well as other append problems
	Added more %-switches to pipe
	Added -B (background execute) option
	Added -c (copy local) and -t (FTP to) options (not working yet)
	Added -o/-O (alternate dir location) options
	Added -N (no continue if failure) option
	Added -k (ACCT login) option

***************************************************************

KNOWN BUGS

[+: may want to fix; *: definitely want to fix; -: may never fix]

* Doesn't handle non-UNIX remote sites [I know of none any more]
* Probably plenty of nasty hidden bugs
+ Some fixed-sized buffers may overflow
+ Few options are really range-checked
- Groups & user names aren't mirrored
- Sockets aren't mirrored
- Exact time isn't used for comparison (only accurate to what ls gives)

Ok, to give some credit here:

I finally took a detailed look at mirror.pl's documentation, and decided to
add the following to lurkftp:
- "ignore" filter
- option to ignore dates &/or ignore size &/or look for "newer" files
- option for "skip mirror if too many changes"
- option for "Force transfer of everything"
- new list parsing routines (VMS/DOS/Mac) from lsparse.pl

----------------------------------------------------------------------------
 Thomas J. Moore, Hacker/SysAdmin  | Must ... Kill ... Bugs ... AAARRRGGHHH
 inet:  dark@mama.indstate.edu     | unix ada asm c/c++ mcu ee amiga bored
----------------------------------------------------------------------------
lurkftp/lurkftp.c100644    700     24       30471  6367667626  12745 0ustar  darkusers/*
 * lurkftp - lurk around FTP sites, possibly grabbing things
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"
#include <sys/wait.h>
#include <utime.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int debug;

char *progname;
char *curstat;

char *reptpipe = NULL;

int bg = 0;

static int anyfork = 0;

FILE *of;
static int oflock = -1; /* semaphore */

char scratch[4096]; /* temp. buffer */

/* fork() support: output file locking */
void lockof(void)
{
    struct sembuf sop;

    if(oflock < 0)
      return;
    if(debug & DB_LOCK)
      fputs("Attempting to lock output file...\n",stderr);
    sop.sem_num = 0;
    sop.sem_op = -1;
    sop.sem_flg = 0; /* maybe SEM_UNDO */
    errno = 0;
    while(semop(oflock,&sop,1) < 0 && errno == EAGAIN) errno = 0;
    if(errno)
      perror("semop");
    if(debug & DB_LOCK)
      fputs("Output file locked\n",stderr);
}

void unlockof(void)
{
    struct sembuf sop;

    if(oflock < 0)
      return;
    fflush(of);
    sop.sem_num = 0;
    sop.sem_op = 1;
    sop.sem_flg = 0;
    semop(oflock,&sop,1);
    if(debug & DB_LOCK)
      fputs("Output file unlocked\n",stderr);
}

static void rmoflock(void)
{
#ifdef _GNU_SOURCE
    union semun arg;
#endif

    if(anyfork) {
#ifdef _GNU_SOURCE
	arg.val = 0;
	semctl(oflock,0,IPC_RMID,arg);
#else
	semctl(oflock,0,IPC_RMID);
#endif
    }
}

static int getoflock(void)
{
    if(oflock >= 0)
      return 1;
    oflock = semget(IPC_PRIVATE,1,IPC_CREAT|S_IRWXU);
    if(oflock < 0)
      return 0;
    atexit(rmoflock);
    unlockof();
    return 1;
}

/* fork() support: kill all children */
static void killchld(void)
{
    if(anyfork) {
	signal(SIGTERM,SIG_IGN);
	kill(-getpgrp(),SIGTERM);
    }
}

static int file_read(FILE *f, void *buf, int buflen)
{
    return fread(buf, buflen, 1, f);
}

static int read_dir(struct ftpdir *dir, char manrecurs, regex_t *xfilt)
{
    FILE *lf;
    int err;

    switch(dir->type) {
      case ST_FTPD:
	return readftpdir(&dir->dirm, &dir->site, dir->dirs, dir->ndirs,
			  manrecurs, xfilt);
      case ST_LDIR:
	return readtree(&dir->dirm,dir->dir0);
      case ST_MFILE:
	headsubst(scratch,dir->lsfile,dir,NULL,NULL);
	if((lf = fopen(scratch, "r"))) {
	    err = readlsfile(&dir->dirm, lf, dir->dir0);
	    fclose(lf);
	    return err;
	} else {
	    lockof();
	    fprintf(of,"*** %s doesn't exist! ***\n",scratch);
	    unlockof();
	    return ERR_DIR;
	}
      case ST_LSLRF:
	/* FIXME */
	break;
      case ST_FTPLSLRF:
	return readftplsf(&dir->dirm, &dir->site, dir->dir0, dir->lsfile);
      case ST_CMD:
	headsubst(scratch,dir->lsfile,dir,NULL,NULL);
	if((lf = popen(scratch, "r"))) {
	    err = parsels(&dir->dirm, (read_func)file_read, lf, dir->dir0,
			  dir->dir0);
	    pclose(lf);
	    return err;
	} else {
	    lockof();
	    fprintf(of,"*** Can't execute %s! ***\n",scratch);
	    unlockof();
	    return ERR_OS;
	}
    }
    return 0;
}


void filterdir(struct dirmem *dir, const regex_t *filter, int cut)
{
    struct fname **p, **q;
    int i, cnt = dir->count;

    for(p=q=dir->array,i=dir->count;i--;p++) {
	strcpy(scratch,(*p)->dir);
	strcat(scratch,(*p)->name);
	if(S_ISLNK((*p)->mode)) {
	    strcat(scratch," -> ");
	    strcat(scratch,(*p)->lnk);
	}
	if(!regexec(filter, scratch, 0, NULL, 0) == !cut)
	  *q++ = *p;
	else {
	    if(debug & DB_ALL)
	      fprintf(stderr,"Filtering out %s\n",scratch);
	    cnt--;
	}
    }
    dir->count = cnt;
}
static void filt_dir(struct dirmem *dir, const char *ifilter, regex_t *iregex,
		     const char *xfilter, regex_t *xregex,
		     char filtdir, char filtspec)
{
    struct fname **o, **n;
    int i, j;

    if(ifilter) {
	if(debug & DB_TRACE)
	  fprintf(stderr,"Filtering out all but '%s'\n",ifilter);
	filterdir(dir, iregex, 0);
    }
    if(xfilter) {
	if(debug & DB_TRACE)
	  fprintf(stderr,"Filtering out '%s'\n",xfilter);
	filterdir(dir, xregex, 1);
    }
    if(filtdir) {
	for(o=n=dir->array,j=0,i=dir->count;i;i--,o++)
	  if(!S_ISDIR((*o)->mode)) {
	      *n++ = *o;
	      j++;
	  }
	dir->count = j;
	}
	if(filtspec) {
	    for(o=n=dir->array,j=0,i=dir->count;i;i--,o++)
	      if(!S_ISSOCK((*o)->mode) && !S_ISCHR((*n)->mode) &&
		 !S_ISBLK((*o)->mode) && !S_ISFIFO((*n)->mode)) {
		  *n++ = *o;
		  j++;
	      }
	    dir->count = j;
	}
}

#if TIMECORRECT
void timecorrect(struct ftpdir *dir)
{
    /* time-correct */
    if(op->srcls.type == ST_FTPD && op->srcls.dirm.count) {
	/* since this list sorted in date-first ascending order, get last */
	/* (i.e. most recent) entry & see if it matches file's MDTM date */
	for(i=op->srcls.dirm.count,n=op->srcls.dirm.array+i-1;i;i--,n--)
	  if(S_ISREG((*n)->mode)) /* only files have important dates */
	    break;
	if(i) { /* if there was any file at all, n points to most recent */
	    strcpy(scratch,(*n)->dir);
	    strcat(scratch,(*n)->name);
	    if(filetm(site,scratch,&tm) && /* mdtm command successful */
	       (tm.tm_year != (*n)->year-1900 || tm.tm_mon != (*n)->month-1 ||
		tm.tm_mday != (*n)->day ||
		((*n)->hr >= 0 && tm.tm_hour != (*n)->hr) ||
		((*n)->min >= 0 && tm.tm_min != (*n)->min))) {
		time_t t1, t2;
		/* ugh! remote ls returns inaccurate times! */
		/* see how inaccurate it is.. */
		t1 = mktime(&tm);
		tm.tm_year = (*n)->year-1900;
		tm.tm_mon = (*n)->month-1;
		tm.tm_mday = (*n)->day;
		if((*n)->hr >= 0)
		  tm.tm_hour = (*n)->hr;
		else
		  tm.tm_hour = 12; /* why not? */
		if((*n)->min >= 0)
		  tm.tm_min = (*n)->min;
		else
		  tm.tm_min = 0;
		tm.tm_sec = 0;
		tm.tm_isdst = -1;
		t2 = mktime(&tm);
		i = (time_t)difftime(t1,t2);
		if(debug & DB_TRACE) {
		    j = i<0?-i:i;
		    fprintf(stderr,"Correcting time by %s%d:%d:%d:%d\n",
			    i<0?"-":"",j/(24*60*60),j/(60*60)%24,j/60%60,j%60);
		}
		for(i=op->srcls.dirm.count,n=op->srcls.dirm.array;i;i--,n++) {
		    tm.tm_year = (*n)->year-1900;
		    tm.tm_mon = (*n)->month-1;
		    tm.tm_mday = (*n)->day;
		    if((*n)->hr >= 0)
		      tm.tm_hour = (*n)->hr;
		    else
		      tm.tm_hour = 12; /* why not? */
		    if((*n)->min >= 0)
		      tm.tm_min = (*n)->min;
		    else
		      tm.tm_min = 0;
		    tm.tm_sec = 0;
		    t1 = mktime(&tm) + i;
		    tm = *localtime(&t1);
		    (*n)->year = tm.tm_year+1900;
		    (*n)->month = tm.tm_mon+1;
		    (*n)->day = tm.tm_mday;
		    if((*n)->hr >= 0)
		      (*n)->hr = tm.tm_hour;
		    if((*n)->min >= 0)
		      (*n)->min = tm.tm_min;
		}
	    }
	}
    }
}
#endif

static void process_all(void)
{
    struct op *op;
    int i;
    pid_t pid = -1;
    struct fname **mirarray;
    int ndel, nadd, malen;
    int domir;

    if(ops)
      ops->dep = 0; /* can't depend on nothing */
    for(op = ops;op;op = op->next) {
	/* in case of continue when forked, quit */
	if(!pid && !op->dep)
	  exit(0);

	/* set up main listing file */
	if(!of && (!reptpipe || !(of = tmpfile())))
	  of = stdout;

	/* clean up zombie processes */
	if(pid && oflock >= 0)
	  while((i = waitpid(-1,NULL,WNOHANG)) > 0 || (i && errno != ECHILD));
	errno = 0;

	/* prepare for fork */
	if(op->dofork && !op->dep && getoflock()) {
	    if((pid = fork()) > 0) { /* parent */
		anyfork = 1;
		while(op->next && op->next->dep)
		  op = op->next;
		continue;
	    }
	    if(!pid) { /* child */
		anyfork = 0; /* no children */
		if(debug & DB_TRACE)
		  fprintf(stderr,"Forked pid %ld\n",(unsigned long)getpid());
		if(debug & DB_ALL) /* give gdb time to attach */
		  sleep(5);
	    }
	    /* here, it's either the child or no child was created */
	}

	/* perform actual processing: */
	strcpy(curstat, op->srcls.site.host);
	
	domir = op->trymir && op->domirop;

	/* read "remote" dir */
	if(debug & DB_TRACE)
	  fputs("Reading remote directory\n",stderr);
	i = read_dir(&op->srcls, op->manrecurs, op->xfilter?&op->xregex:NULL);
	if(i) {
	    if(!op->quiet) {
		lockof();
		fprintf(of,"*** %s %s: %s ***\n",op->srcls.site.host,
			op->srcls.dir0, ftperr(i));
		unlockof();
	    }
	    continue;
	}
	/* run filters on it */
	filt_dir(&op->srcls.dirm, op->ifilter, &op->iregex,
		 op->xfilter, &op->xregex, op->filtdir, op->filtspec);
	/* check if anything left after filtering */
	if(!op->srcls.dirm.count) {
	    lockof();
	    fprintf(of,"*** %s %s: Nothing left after filter ***\n",
		    op->srcls.site.host,op->srcls.dir0);
	    unlockof();
	    continue;
	}
	/* sort; ready for diffing. */
	sort((const struct fname **)op->srcls.dirm.array, op->srcls.dirm.count,
	     op->detmove?MVCMP:REPTCMP);
#if TIMECORRECT
	if(op->srcls.type != ST_LDIR && op->srcls.type != ST_MFILE)
	  timecorrect(&op->srcls);
#endif
	/* read "local" dir */
	if(debug & DB_TRACE)
	  fputs("Reading local directory\n",stderr);
	i = read_dir(&op->dstls,op->manrecurs,
		 op->xfilter?&op->xregex:NULL);
	if(i && i != ERR_DIR) {
	    fprintf(stderr,"Internal error: %s\n", ftperr(i));
	    continue;
	}
	/* sort; ready for diffing. */
	sort((const struct fname **)op->dstls.dirm.array, op->dstls.dirm.count,
	     op->detmove?MVCMP:REPTCMP);
#if TIMECORRECT
	if(op->dstls.type != ST_LDIR && op->dstls.type != ST_MFILE)
	  timecorrect(&op->dstls);
#endif
	/* diff listings */
	mirarray = diff_dir(&op->srcls, &op->dstls, &malen, &nadd, &ndel,
			    op->detmove, op->mirmodes && domir);
	/* report differences */
	if(debug & DB_TRACE)
	  fputs("Printing report\n",stderr);
	if(!op->quiet && (nadd || ndel))
	  pr_rept(mirarray, malen, nadd, ndel, op->rhead?op->rhead:DEFHEAD,
		  op->rtail?op->rtail:DEFTAIL, &op->srcls, &op->dstls,
		  op->rfmt?op->rfmt:(op->trymir? /* or maybe domir? */
				     (op->filtdir?"%[d:" DEFFMT "]":DEFFMT):
				     (op->filtdir?
				      "%[dT<T>TM:" DEFFMT "]":
				      "%[T<T>TM:" DEFFMT "]")),
		  op->rsort?op->rsort:REPTCMP, op->rsort || op->detmove);
	/* now perform mirroring functions */
	if(domir) {
	    if(op->mirmodes && nadd)
	      mir_chmod(&op->dstls, mirarray, nadd);
	    if(!op->mirstmp && ndel)
	      mir_del(&op->dstls, mirarray+malen-ndel,ndel, op->filtdir);
	    if(op->detmove && ndel && nadd)
	      mir_move(&op->dstls, mirarray, malen, &nadd, &ndel);
	    if(nadd)
 	      mir_transfer(mirarray, nadd, &op->srcls, &op->dstls,
			   op->mirstmp, op->mirmodes, op->mirappend);
	    mir_errrpt(mirarray, mirarray+malen-ndel, nadd, ndel,
		       op->ehead?op->ehead:DEFEHEAD,
		       op->etail?op->etail:DEFETAIL, &op->dstls, &op->srcls,
		       op->filtdir?"%[d:" DEFFMT "]":DEFFMT);
	}
	/* write out new list file */
	/* deferred until here so that failed transfers aren't added */
	if((ndel || nadd) && op->dstls.type == ST_MFILE) {
	    headsubst(scratch,op->dstls.lsfile,&op->dstls,NULL,NULL);
	    if((i = writelsfile(scratch,&op->srcls.dirm,domir)))
	      if(!op->quiet) {
		  lockof();
		  fprintf(of,"*** %s %s: %s ***\n",op->srcls.site.host,
			  op->srcls.dir0, ftperr(i));
		  unlockof();
	      }
	}
	/* reset variables between site/dir lists */
	ftp_closeconn(&op->srcls.site); /* in case file or dir retrieval was in progress */
	ftp_closeconn(&op->dstls.site); /* in case file or dir retrieval was in progress */
	/* in case we got here when forked, quit */
	if(!pid && (!op->next || !op->next->dep))
	  exit(0);
	freedir(&op->srcls.dirm); /* dstls on same dirm */
	if(mirarray) {
	    free(mirarray);
	    mirarray = NULL;
	}
    }
    if(!pid)
      exit(0);
}

static void cleanexit(int ret)
{
    FILE *f;
    int i;

    /* wait for all children */
    if(oflock >= 0)
      while(waitpid(-1,NULL,0) > 0 || errno != ECHILD);
    anyfork = 0;

    /* pipe out the report */
    if(reptpipe && of != stdout && ftell(of) > 0) {
	f = popen(reptpipe,"w");
	rewind(of);
	while((i = fread(scratch, 1, 2048, of)) > 0)
	  fwrite(scratch,i,1,f);
	pclose(f);
    }
    if(of != stdout)
      fclose(of);

    exit(ret);
}

int main(int argc, char **argv)
{
    time_t tm;
    pid_t pid;

    ftp_init(argv[0]);

    progname = argv[0];
    curstat = argv[1];

    /* for dir listings -- mainly current year/month */
    time(&tm);
    memcpy(&curtm,localtime(&tm),sizeof(curtm));

    signal(SIGCHLD,SIG_IGN);
    atexit(killchld);

    parseargs(argc, argv); /* parse all args */

    if(bg) { /* dissociate if requested */
	freopen("/dev/null","w",stdout);
	fclose(stdin);
	freopen("/dev/null","w",stderr);
	pid = fork();
	if(pid > 0) {
	    anyfork = 0;
	    exit(0);
	} else if(!pid)
	    setsid();
    }

    process_all();

    cleanexit(0);

    return 0;
}
lurkftp/pipe.c100644    700     24        1162  6354761735  12156 0ustar  darkusers#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char buf[4096];

int main(int argc, char **argv)
{
    FILE *f;
    int i;
    char bg = 0;

    if(argc < 2) {
	fprintf(stderr, "pipe: make pipe seekable: %s <cmd>\n", argv[0]);
	exit(1);
    }
    if(argc > 2 && !strcmp(argv[1], "-b")) {
	argv++;
	argc--;
	bg = 1;
    }
    f = tmpfile();
    while((i = fread(buf, 1, 4096, stdin)) > 0)
      if(fwrite(buf, i, 1, f) != 1) {
	  perror("tmp file write");
	  exit(1);
      }
    rewind(f);
    close(0);
    dup2(fileno(f), 0);
    close(fileno(f));
    execvp(argv[1], argv+1);
    perror("exec");
    exit(1);
}
lurkftp/opt.c100644    700     24       40523  6367713745  12051 0ustar  darkusers/*
 * Option parsing for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"
#include <stddef.h> /* for offsetof() */
#include <pwd.h>

struct op *ops;
static struct op defop = {0}, **nxtop = &ops;

static char *anonpasswd;
static char *lsbase; /* default lsfile name */

static void help(int ret)
{
    fprintf(stderr,PROGRAMNAME ": monitor and/or mirror FTP sites\n\n"
	    "usage: %s [[options] [<site> [<dir>]+]]+\n\n",progname);
    fputs("(C) 1997 Thomas J. Moore\n",stderr);
    exit(ret);
}

static void parseopts(const char *(*)(void *,int), void *);

struct ofile {
    FILE *f;
    char buf[4096], pbuf[4096];
    char *bptr;
};

/* returns pointer to end of string or NULL if error */
static char *homesubst(char *buf)
{
    struct passwd *pw;
    char *s;

    if(!*buf) {
	s = getenv("HOME");
	if(s) {
	    strcpy(buf,s);
	    return buf+strlen(buf);
	} else {
	    pw = getpwuid(getuid());
	    if(!pw) {
		perror("My own homedir");
		help(1);
	    }
	}
    } else {
	pw = getpwnam(buf);
	if(!pw) {
	    strcat(buf,"'s homedir");
	    perror(buf);
	    help(1);
	}
    }
    strcpy(buf,pw->pw_dir);
    return buf+strlen(buf);
}

static const char *readfile(void *data, int getopt)
{
    struct ofile *of = data;
    char *s, *p, *d, quote, home;

    if(!of->f)
      return NULL;
    if (!*of->bptr || *of->bptr == '#') {
	do {
	    if(!fgets(of->buf,4096,of->f)) {
		fclose(of->f);
		of->f = NULL;
		return NULL;
	    }
	    of->bptr = of->buf;
	    while(isspace(*of->bptr))
	      of->bptr++;
	} while(!*of->bptr || *of->bptr == '#');
	for(s=of->bptr+strlen(of->bptr)-1;isspace(*s);s--);
	*++s = 0;
	if(defop.srcls.site.host) {
	    if(debug & DB_OPT)
	      fputs("optfile: '\\n' -> '-+'\n",stderr);
	    return "-+";
	}
    }
    d = of->pbuf;
    if(!getopt)
      d += strlen(d)+1;
    p = of->bptr;
    quote = 0;
    if(*p == '~') {
	p++;
	home = 1;
    } else
      home = 0;
    while(*p) {
	if(!quote && *p == '\'') {
	    while(*++p && *p != '\'')
	      *d++ = *p;
	    p++;
	    continue;
	} else if(*p == '"') {
	    quote ^= 1;
	    p++;
	    continue;
	}
	/* csh uses the !quote test; I don't */
	if(home && /* !quote && */ *p == '/') {
	    *d = 0;
	    d = getopt?of->pbuf:of->pbuf+strlen(of->pbuf)+1;
	    d = homesubst(d);
	    home = 0;
	}
	if(*p == '$') {
	    p++;
	    s = d;
	    if(*p == '{') {
		while(*++p && *p != '}')
		  *d++ = *p;
		p++;
	    } else
	      while(*p && isalnum(*p))
		*d++ = *p++;
	    *d = 0;
	    if((d = getenv(s)))
	      strcpy(s,d);
	    else
	      *s = 0;
	    d = s+strlen(s);
	    continue;
	}
	if(!quote && isspace(*p))
	  break;
	if(*p == '\\' && p[1])
	  p++;
	*d++ = *p++;
    }
    *d = 0;
    d = getopt?of->pbuf:of->pbuf+strlen(of->pbuf)+1;
    if(home)
      homesubst(d);
    if(*p)
      while(isspace(*++p));
    if(debug & DB_OPT)
      fprintf(stderr,"optfile: '%.*s' -> '%s'\n",p-of->bptr,of->bptr,d);
    of->bptr = p;
    return d;
}

static void optfile(const char *n)
{
    FILE *f = fopen(n,"r");
    struct ofile *of = NULL; /* init to keep GCC happy */

    if(!f || !(of = malloc(sizeof(*of))))
      help(1);
    of->f = f;
    of->bptr = of->buf;
    of->buf[0] = 0;
    parseopts(readfile,of);
}

struct prargs {
    int argc;
    char **argv;
};

static const char *getarg(void *data, int getopt)
{
    struct prargs *args = data;
    if(!--args->argc)
      return NULL;
    else
      return *++args->argv;
}

void parseargs(int argc, char **argv)
{
    struct prargs ar;

    ar.argc = argc;
    ar.argv = argv;
    parseopts(getarg,&ar);
}

static void endarg(void)
{
    struct op *op;

    if(defop.srcls.site.host) {
	*nxtop = op = malloc(sizeof(*op));
	if(!op)
	  help(1);
	nxtop = &op->next;
	*op = defop;
	/* fix up self-references */
	if(op->srcls.dirs == &defop.srcls.dir0)
	  op->srcls.dirs = &op->srcls.dir0;
	if(op->dstls.dirs == &defop.dstls.dir0)
	  op->dstls.dirs = &op->dstls.dir0;
	/* all counts & arrays are 0, so just share fname & strmem space */
	op->dstls.dirm = op->srcls.dirm;
	/* mangle defaults in */
	if(op->srcls.type == ST_NONE)
	  op->srcls.type = ST_FTPD;
	if(op->dstls.type == ST_NONE)
	  op->dstls.type = ST_MFILE;
	if(op->dstls.type == ST_MFILE && !op->dstls.lsfile)
	  op->dstls.lsfile = lsbase?lsbase:DEF_LSFILE;
	if(op->srcls.type == ST_MFILE && !op->srcls.lsfile)
	  op->srcls.lsfile = lsbase?lsbase:DEF_LSFILE;
	if(!op->dstls.site.host)
	  op->dstls.site.host = op->srcls.site.host;
	if(!op->dstls.dir0) {
	    op->dstls.dir0 = op->srcls.dir0;
	    op->dstls.dirs = op->srcls.dirs;
	    op->dstls.ndirs = op->srcls.ndirs;
	}
	/* reset for next pass */
	memset(&defop.srcls, 0, sizeof(defop.srcls));
	/* save timeouts */
	defop.srcls.site.to = defop.dstls.site.to;
	memset(&defop.dstls, 0, sizeof(defop.dstls));
	/* save timeouts */
	defop.dstls.site.to = defop.srcls.site.to;
	defop.srcls.type = defop.dstls.type = 0;
	defop.dstf_remt = defop.srcf_local = 0;
	defop.trymir = 0;
    }
}

/* minor parse helper macros */

#define copyparm(s) copystr(s,strlen(s),&defop.srcls.dirm.names)
#define copyparml(s,l) copystr(s,l,&defop.srcls.dirm.names)

#define optstrm(parm) do { \
    if(!(s = (*getarg)(data,0))) \
      help(1); \
    if(parm) \
      free((void *)parm); \
    if(*s) { \
	parm = malloc(strlen(s)+1); \
	if(!parm) \
	  help(1); \
	strcpy((char *)parm,s); \
    } else \
      parm = NULL; \
} while(0)

#define optstr(parm) do { \
    if(!(s = (*getarg)(data,0))) \
      help(1); \
    if(*s) { \
	parm = copyparm(s); \
	if(!parm) \
	  help(1); \
    } else \
      parm = NULL; \
} while(0)

#define optint(parm) do {\
    if(!(s = (*getarg)(data,0))) \
      help(1); \
    parm = atoi(s); \
} while(0)

#define setfilt(reg, str) do {\
    if((i=regcomp(&reg, str, REG_EXTENDED|REG_NOSUB))) { \
	str = NULL; \
	regerror(i, &reg, scratch, 4096); \
	fprintf(stderr,"Can't compile %s: %s\n",str,scratch); \
	help(1); \
    } \
} while(0)

#define filtfile(filt) do { \
    if(!(s = (*getarg)(data,0))) \
      help(1); \
    if(stat(s, &st)) \
      help(1); \
    defop.filt##filter = allocstr(st.st_size,&defop.srcls.dirm.names); \
    if(!defop.filt##filter || !(i=open(s,O_RDONLY)) || \
       read(i,(void *)defop.filt##filter,st.st_size)!=st.st_size || close(i)) \
      help(1); \
    p=(char *)defop.filt##filter + st.st_size - 1; \
    p[1] = 0; \
    for(i = st.st_size;i && *p == '\n' ; i--, p--) \
      *p = 0; \
    for(;i;i--,p--) { \
	if(*p == '\n') \
	  *p = '|'; \
    } \
    if(!*defop.filt##filter) \
      defop.filt##filter = NULL; \
    else \
      setfilt(defop.filt##regex, defop.filt##filter); \
} while(0)

/* minor parse helper functions for literal args */

/* strips one more layer of \'s off & parses out acct. & passwd */
/* return: NULL or location of a directory */
static const char *parsesite(const char *_s, struct ftpsite *site)
{
    const char *s = _s;
    char *d, *sp = NULL, *ap = NULL, *pp = NULL, *dp = NULL, *port = NULL;

    for(d = scratch; *s; s++, d++) {
	if(*s == '\\' && s[1]) {
	    *d = *++s;
	    continue;
	}
	if(!sp && *s == '@') {
	    *d = 0;
	    sp = d+1;
	    continue;
	}
	if(!sp && !pp) {
	    if(*s == ':') {
		*d = 0;
		pp = d+1;
		continue;
	    }
	    if(!ap && *s == ',') {
		*d = 0;
		ap = d+1;
		continue;
	    }
	}
	if(sp && !dp) {
	    if(!port && *s == ',') {
		port = d+1;
		*d = 0;
		continue;
	    }
	    if(*s == ':') {
		*d = 0;
		dp = d+1;
		continue;
	    }
	}
	*d = *s;
    }
    *d = 0;
    if(sp) {
	if(*sp)
	  site->host = copyparm(sp);
	if(ap && (!pp || !*scratch)) {
	    fprintf(stderr,"Warning: acct %s w/o user & passwd in %s ignored\n",
		    ap, _s);
	    ap = NULL;
	}
	site->user = *scratch?copyparm(scratch):NULL;
	site->acct = ap?copyparm(ap):NULL;
	site->pass = pp?copyparm(pp):anonpasswd?copyparm(anonpasswd):NULL;
	site->port = port?0:atoi(port); /* FIXME: getportbyname() */
    } else {
	if(*scratch)
	  site->host = copyparm(scratch);
	site->user = NULL;
	site->acct = NULL;
	site->pass = anonpasswd?copyparm(anonpasswd):NULL;
	site->port = ap?atoi(ap):0; /* FIXME: getportbyname() */
	dp = pp;
    }
    return dp;
}

static void litarg(const char *arg)
{
    if(!defop.srcls.site.host)
      arg = parsesite(arg, &defop.srcls.site);
    if(arg) {
	arg = copyparm(arg);
	if(defop.srcls.ndirs) {
	    if(defop.dstls.type == ST_FTPD) {
		fputs("There must be a one-one remote/local dir correspondence\n",stderr);
		help(1);
	    }
	    if(defop.srcls.ndirs == 1) {
		defop.srcls.dirs = malloc(sizeof(char *)*5);
		defop.srcls.dirs[0] = defop.srcls.dir0;
	    }
	    else if(!(defop.srcls.ndirs%5))
	      defop.srcls.dirs = realloc(defop.srcls.dirs,
					 sizeof(char *)*5*
					 (defop.srcls.ndirs/5+1));
	    defop.srcls.dirs[defop.srcls.ndirs] = arg;
	    defop.srcls.ndirs++;
	} else {
	    defop.srcls.dir0 = arg;
	    defop.srcls.dirs = &defop.srcls.dir0;
	    defop.srcls.ndirs++;
	}
    }
}

static void setdir(struct ftpdir *dir, const char *set)
{
    const char *loc = strchr(set, ':');

    if(!loc++ || !*loc) {
	fprintf(stderr,"%s: bad location specifier\n", set);
	help(1);
    }
    switch(set[0]) {
      case 'l':
	dir->lsfile = copyparm(loc);
	dir->type = ST_LSLRF;
	return;
      case 'c':
	dir->lsfile = copyparm(loc);
	dir->type = ST_CMD;
	return;
      case 'm':
	dir->lsfile = copyparm(loc);
	dir->type = ST_MFILE;
	return;
      case 'd':
	dir->type = ST_LDIR;
	break;
      case 'f':
	dir->type = ST_FTPD;
	set = parsesite(loc, &dir->site);
	break;
      case 'L':
	dir->type = ST_FTPLSLRF;
	dir->lsfile = copyparm(parsesite(loc, &dir->site));
	if(!dir->lsfile) {
	    fprintf(stderr, "%s: bad location specifier\n", set);
	    help(1);
	}
	return;
      default:
	fprintf(stderr,"%s: bad location specifier\n", set);
	help(1);
    }
    /* this here's only for 'd'/'f' */
    if(!dir->ndirs)
      dir->dirs = &dir->dir0;
    else {
	if(dir->ndirs == 1) {
	    dir->dirs = malloc(sizeof(char *)*5);
	    dir->dirs[0] = dir->dir0;
	} else if(!(dir->ndirs % 5))
	    dir->dirs = realloc(dir->dirs, sizeof(char *)*5*(dir->ndirs/5+1));
    }
    dir->dirs[dir->ndirs++] = set;
}

static void parseopts(const char *(*getarg)(void *, int), void *data)
{
    const char *a, *s = NULL; /* s init to keep GCC quiet */
    char *p = NULL; /* p init to keep GCC quiet */
    struct stat st;
    int i;
    char tog;

    defop.srcls.site.to = defop.dstls.site.to = defto;
#ifdef DEF_CRPG
    crpg = malloc(strlen(DEF_CRPG)+1);
    strcpy(crpg, DEF_CRPG);
#endif

    while((a = (*getarg)(data,1))) {
	if(*a == '-' || *a == '+') {
	    tog = *a == '-';
	    while(*++a) {
		switch(*a) {
		  case 'F': /* site file */
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    optfile(s);
		    break;
		  case 'B': /* run in background */
		    bg = 1;
		    break;
		  case 'q': /* suppress report */
		    defop.quiet = tog;
		    break;
		  case 'P': /* fork */
		    defop.dofork = tog;
		    break;
		  case 'N': /* don't continue if error */
		    defop.dep = tog;
		    break;
		  case 'p': /* anonymous password */
		    optstrm(anonpasswd);
		    break;
		  case 'b': /* base for ls files */
		    optstrm(lsbase);
		    break;
		  case 'z': /* compressor program */
		    optstrm(crpg);
		    break;
		  case 'Z': /* uncompressor program */
		    optstrm(uncrpg);
		    break;
		  case 'U': /* detect unchanged files */
		    defop.detmove = tog;
		    break;
		  case 'M': /* force manual recursion */
		    defop.manrecurs = tog;
		    break;
		  case 'L': /* remote ls file (next site only) */
		    if(defop.srcls.type != ST_NONE)
		      fputs("Warning: -f overrides -L\n", stderr);
		    optstr(defop.srcls.lsfile);
		    defop.srcls.type = ST_FTPLSLRF;
		    break;
		  case 'm': /* mirror */
		    defop.domirop = tog;
		    break;
		  case 'd': /* local dir */
		    if(defop.trymir && defop.dstf_remt)
		      fputs("Warning: -d overrides -t\n", stderr);
		    optstr(defop.dstls.dir0);
		    defop.dstls.dirs = &defop.dstls.dir0;
		    defop.dstls.ndirs = 1;
		    defop.dstls.type = ST_LDIR;
		    defop.trymir = 1;
		    defop.dstf_remt = 0;
		    break;
		  case 'e': /* execute command for each file */
		    if(defop.trymir && defop.dstf_remt)
		      fputs("Warning: -e overrides -t\n", stderr);
		    optstr(defop.dstls.pipe);
		    defop.trymir = 1;
		    break;
		  case 't':
		    defop.dstls.type = ST_FTPD;
		    defop.dstls.dir0 = parsesite(s, &defop.dstls.site);
		    defop.dstls.dirs = &defop.dstls.dir0;
		    defop.dstls.ndirs = 1;
		    defop.trymir = 1;
		    defop.dstf_remt = 1;
		    break;
		  case 'c':
		    defop.srcf_local = tog;
		    break;
		  case 'g':
		    optstr(defop.srcls.pipe);
		    break;
		  case 'l': /* mirror from listfile */
		    optstr(defop.dstls.lsfile);
		    defop.dstls.type = ST_MFILE;
		    break;
		  case 'f': /* use prev. generated listfile for remt ls */
		    if(defop.srcls.type != ST_NONE)
		      fputs("Warning: -f overrides -L\n", stderr);
		    optstr(defop.srcls.lsfile);
		    defop.srcls.type = ST_MFILE;
		    break;
		  case 'o':
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    setdir(&defop.srcls, s);
		    break;
		  case 'O':
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    setdir(&defop.dstls, s);
		    if(defop.trymir && (defop.dstls.type == ST_FTPD ||
					defop.dstls.type == ST_LDIR))
		      fputs("Warning: -O f/d overrides -d/-t\n", stderr);
		    break;
		  case 'E':
		    defop.mirmodes = tog;
		    break;
		  case 'n':
		    defop.mirstmp = tog;
		    break;
		  case 'A':
		    defop.mirappend = tog;
		    break;
		  case 'i': /* filter */
		    optstr(defop.ifilter);
		    if(defop.ifilter)
		      setfilt(defop.iregex, defop.ifilter);
		    break;
		  case 'I': /* filter (from file) */
		    filtfile(i);
		    break;
		  case 'x': /* filter */
		    optstr(defop.xfilter);
		    if(defop.xfilter)
		      setfilt(defop.xregex, defop.xfilter);
		    break;
		  case 'X': /* filter (from file) */
		    filtfile(x);
		    break;
		  case 'D': /* filter out all directories */
		    defop.filtdir = tog;
		    break;
		  case 's': /* don't filter out all specials */
		    defop.filtspec = tog^1;
		    break;
		  case 'T': /* timeouts */
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    while(*s) {
			if(!isdigit(s[1])) {
			    fprintf(stderr,"Bad timeout specifier %s\n",s);
			    help(1);
			}
			switch(*s++) {
			  case 't': /* transfer */
			    defop.srcls.site.to.xfer = atoi(s);
			    break;
			  case 'c': /* connect */
			    defop.srcls.site.to.connect = atoi(s);
			    break;
			  case 'q': /* quit */
			    defop.srcls.site.to.quit = atoi(s);
			    break;
			  case 'r': /* retry # */
			    defop.srcls.site.to.retrycnt = atoi(s);
			    break;
			  case 'd': /* retry delay */
			    defop.srcls.site.to.retrytime = atoi(s);
			    break;
			  default:
			    fprintf(stderr,"Bad timeout specifier %s\n",s-1);
			    help(1);
			}
			while(isdigit(*s))
			  s++;
		    }
		    break;
		  case 'R':
		    optstrm(reptpipe);
		    break;
		  case 'r':
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    switch(*s++) {
		      case 't': /* std report title */
			defop.rhead = copyparm(s);
			break;
		      case 'd': /* std report line */
			defop.rfmt = copyparm(s);
			break;
		      case 'f': /* std report footer */
			defop.rtail = copyparm(s);
			break;
		      case 's': /* std erport sort */
			defop.rsort = parsesort(s);
			break;
		      case 'T': /* mir report title */
			defop.ehead = copyparm(s);
			break;
		      case 'D': /* mir report line */
			defop.efmt = copyparm(s);
			break;
		      case 'F': /* mir report footer */
			defop.etail = copyparm(s);
			break;
		      case 'S': /* mir report sort */
			defop.esort = parsesort(s);
			break;
		      case 'e': /* error line (title) */
			defop.errln = copyparm(s);
			break;
		      default:
			fprintf(stderr,"Bad report string specifier %s\n",s);
			help(1);
		    }
		    break;
		  case 'v': /* debug level */
		    optint(debug);
		    if(debug)
		      /* perhaps linebuf would be better, but this is easier */
		      setbuf(stdout, NULL);
		    break;
		  case '-': /* next arg literal */
		    if(!(s = (*getarg)(data,0)))
		      help(1);
		    litarg(s);
		    break;
		  case '+': /* end of arg list */
		    endarg();
		    break;
		  case 'h': /* help */
		    help(0);
		  default:
		    help(1);
		}
	    }
	} else
	  litarg(a);
    }
    endarg();
    if(!ops)
      help(0);
}
lurkftp/mir.c100644    700     24       25210  6363423464  12022 0ustar  darkusers/*
 * Mirroring functions for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"
#include <utime.h>
#include <sys/wait.h>

/* return: 0 = error; else success */
/* assumes name begins with '/' */
static int mkpath(char *dir)
{
    struct stat st;
    char *s;

    s = strrchr(dir,'/');
    if(!s[1])
      *s = 0;
    if(!stat(dir,&st)) {
	if(!s[1])
	  *s = '/';
	return S_ISDIR(st.st_mode);
    }
    if(!s[1]) {
	*s = '/';
	while(*--s != '/' && s > dir);
    }
    while(s > dir) {
	*s = 0;
	if(!stat(dir,&st)) {
	    *s = '/';
	    if(!S_ISDIR(st.st_mode))
	      return 0;
	    break;
	}
	*s = '/';
	while(*--s != '/' && s > dir);
    }
    while((s = strchr(s+1,'/'))) {
	*s = 0;
	if(mkdir(dir, 0777)) {
	    *s = '/';
	    return 0;
	}
	*s = '/';
	if(!s[1])
	  return 1;
    }
    /* only gets here for last element in path */
    return !mkdir(dir, 0777);
}

/* FIXME: using wrong name on moved files, most likely */
void mir_chmod(struct ftpdir *dst, struct fname **ap, int nadd)
{
    char *s;

    if(debug & DB_TRACE)
      fputs("Performing chmod operations\n",stderr);
    for(;nadd;nadd--,ap++)
      if((*ap)->flags & FNF_MODEONLY) {
	  if(dst->pipe) {
	      namesubst(scratch, dst->pipe, NULL, dst, *ap, 'M');
	      if((!scratch[0] || !system(scratch)) &&
		 !((*ap)->flags & FNF_MOVE))
		(*ap)->flags |= FNF_DONE;
	  } else {
	      strcpy(scratch,dst->dir0);
	      s = scratch+strlen(scratch);
	      if(s == scratch || s[-1] != '/')
		*s++ = '/';
	      strcpy(s,(*ap)->dir+strlen((*ap)->root));
	      strcat(scratch,(*ap)->name);
	      if(!chmod(scratch,(*ap)->mode&~S_IFMT) && !((*ap)->flags & FNF_MOVE))
		(*ap)->flags |= FNF_DONE;
	  }
      }
}

void mir_del(struct ftpdir *dst, struct fname **dp, int ndel, char filtdir)
{
    const char *s;
    char *d;
    struct fname **n;
    size_t j;

    if(debug & DB_TRACE)
      fputs("Performing remove operations\n",stderr);
    /* sort for depth-first deletion */
    sort((const struct fname **)dp,ndel,PROCCMP);
    for(;ndel;ndel--,dp++) {
	if((*dp)->flags & FNF_MOVE) /* ignore moves */
	  continue;
	if(dst->pipe) {
	    namesubst(scratch, dst->pipe, NULL, dst, *dp, '-');
	    if(!scratch[0] || !system(scratch))
	      (*dp)->flags |= FNF_DONE;
	} else {
	    strcpy(scratch,(*dp)->dir);
	    strcat(scratch,(*dp)->name);
	    if(debug & DB_ALL)
	      fprintf(stderr,"Removing %s%s\n",(*dp)->dir,(*dp)->name);
	    /* rm -f */
	    if(remove(scratch) && rmdir(scratch)) {
		/* try to fudge dir permissions to remove file */
		/* but only if dir to be removed, too */
		s = (*dp)->dir;
		if(!filtdir) {
		    /* first non-matching dir should be parent dir */
		    for(n=dp,j=ndel;j;n++,j--)
		      if(s != (*n)->dir && strcmp(s,(*n)->dir))
			break;
		    if(j) {
			for(j=strlen(s)-2;j>=0;j--)
			  if(s[j] == '/')
			    break;
			if(strncmp((*n)->dir,s,j+1) ||
			   strlen((*n)->dir) != j ||
			   strncmp((*n)->name, s+j+1,strlen((*n)->name)) ||
			   (*(s += strlen((*n)->name)) && *s != '/')) {
			    if(!chmod((*n)->dir,0700) && (!remove(scratch) ||
							  !rmdir(scratch)))
			      s = NULL; /* flag that it's deleted */
			}
		    }
		}
		if(s) { /* s will be NULL'd only if item was deleted */
		    if(debug & DB_TRACE)
		      fprintf(stderr,"Can't delete %s\n",scratch);
		    continue;
		}
	    }
	    (*dp)->flags |= FNF_DONE;
	    if(debug & DB_TRACE)
	      fprintf(stderr,"Deleted %s\n",scratch);
	    /* need to add following to pipe, too */
	    if(filtdir) { /* if dirs not explicit, remove empty directories */
		d = strrchr(scratch,'/');
		if(!d[1]) /* trailing / */
		  while(*--d != '/' && d > scratch);
		while(d) {
		    *d = 0;
		    if(rmdir(scratch))
		      break;
		}
	    }
	}
    }
}

void mir_move(struct ftpdir *dst, struct fname **mirarray, int malen,
	      int *nadd, int *ndel)
{
    int i = *ndel, j = *nadd, k = 0, l;
    struct fname **o, **n;
    FILE *lf, *rf;
    struct utimbuf ut;

    /* to prevent sorting everything again, remove finished items */
    for(o=n=mirarray;j;j--,o++) {
	if((*o)->flags & FNF_DONE)
	  continue;
	if((*o)->flags & FNF_MOVE)
	  k++;
	*n++ = *o;
    }
    *nadd = n-mirarray;
    for(o=n=mirarray+malen,j=i;j;j--) {
	if((*--o)->flags & FNF_DONE) {
	    i--;
	    continue;
	}
	*--n = *o;
    }
    *ndel = i;
    j = *nadd;
    if(k) {
	n = mirarray;
	o = mirarray+malen-i;
	sort((const struct fname **)n,j,MVCMP);
	sort((const struct fname **)o,i,MVCMP);
	for(k=i;k && j;k--,j--,o++,n++) {
	    while(k && !((*o)->flags & FNF_MOVE)) {
		k--;
		o++;
	    }
	    if(!k)
	      break;
	    while(j && !((*n)->flags & FNF_MOVE)) {
		j--;
		n++;
	    }
	    if(!j)
	      break;
	    /* perform mv */
	    if(dst->pipe) {
		/* FIXME: *n not anywhere in here */
		namesubst(scratch, dst->pipe, NULL, dst, *o, '>');
		if(!scratch[0] || !system(scratch)) {
		    (*o)->flags |= FNF_DONE;
		    (*n)->flags |= FNF_DONE;
		}
	    } else {
		strcpy(scratch+2048,(*o)->dir);
		strcat(scratch+2048,(*o)->name);
		strcpy(scratch,(*o)->root);
		strcat(scratch,(*n)->dir+strlen((*n)->root));
		strcat(scratch,(*n)->name);
		if(!rename(scratch+2048,scratch)) {
		    (*o)->flags |= FNF_DONE;
		    (*n)->flags |= FNF_DONE;
		} else {
		    /* try copy + rm */
		    if((rf = fopen(scratch+2048,"r")) &&
		       (lf = fopen(scratch,"w")))
		      while((l = fread(scratch+4096,1,4096,rf)) &&
			    fwrite(scratch+4096,l,1,lf) == 1);
		    if(rf && lf && !ferror(lf)) {
			if(!fclose(lf)) {
			    struct stat st;

			    (*o)->flags |= FNF_DONE;
			    (*n)->flags |= FNF_DONE;
			    chmod(scratch,(*n)->mode&~S_IFMT);
			    if(!stat(scratch+2048,&st)) {
				ut.actime = st.st_atime;
				ut.modtime = st.st_mtime;
				utime(scratch,&ut);
			    } /* else it'll just have to stay bad */
			    remove(scratch+2048);
			} else
			  remove(scratch);
		    } else {
			if(rf && lf) {
			    fclose(lf);
			    remove(scratch);
			}
		    }
		    if(rf)
		      fclose(rf);
		}
	    }
	}
    }
}

void mir_transfer(struct fname **ap, int nadd, struct ftpdir *src,
		  const struct ftpdir *dst, char mirstmp, char mirmodes,
		  char mirappend)
{
    int j;
    char *s;
    struct utimbuf ut;
    struct tm tm;
    FILE *lf = NULL; /* lf = NULL to keep GCC happy */

    if(debug & DB_TRACE)
      fputs("Performing add operations\n",stderr);
    /* resort for reverse depth order */
    sort((const struct fname **)ap,nadd,PROCCMP);
    for(;nadd;nadd--,ap++) {
	/* ignore moves & chmods */
	if((*ap)->flags & (FNF_MOVE|FNF_MODEONLY))
	  continue;
	/* open local file for regular files */
	if(dst->pipe) {
	    /* pipe: local file is tmp file, but "local file name"
	     * is the command (with %-substitutions) to run */
	    /* pipe is only run for regular files */
	    if(!S_ISREG((*ap)->mode) || mirstmp) {
		(*ap)->flags |= FNF_DONE;
		continue;
	    }
	    namesubst(scratch, dst->pipe, &src->site, dst, *ap, '+');
	    if(!scratch[0]) {
		(*ap)->flags |= FNF_DONE;
		continue;
	    }
	    if(!mirstmp) {
		lf = popen(scratch, "w");
		if(!lf) {
		    lockof();
		    fprintf(of, "*** Can't open pipe %s ***\n", scratch);
		    unlockof();
		    continue;
		}
	    }
	} else {
	    /* non-piping */
	    /* construct local name already for non-file, too */
	    strcpy(scratch,dst->dir0);
	    s = scratch+strlen(scratch);
	    if(s == scratch || s[-1] != '/')
	      *s++ = '/';
	    strcpy(s,(*ap)->dir+strlen((*ap)->root));
	    /* should only need mkpath if(filtdir), but just in case.. */
	    mkpath(scratch); /* make dir to put file into */
	    strcat(s,(*ap)->name);
	    /* only open the file for regulars */
	    if(!mirstmp && S_ISREG((*ap)->mode) &&
	       !(lf = fopen(scratch, "w"))) {
		perror(scratch);
		continue; /* needn't try retrieving any more */
	    }
	}
	/* construct remote name in s */
	s = scratch+strlen(scratch)+1; /* hope there's enough room... */
	strcpy(s,(*ap)->dir);
	strcat(s,(*ap)->name);
	/* construct time stamp already so it's ready for non-files */
	/* this had to wait until here so that the remote name was */
	/* already constructed */
	if(!S_ISLNK((*ap)->mode)) {
	    time(&ut.actime);
	    if(ftp_filetm(&src->site, s, &tm))
	      /* tm has UTC stamp of file */
	      ut.modtime = mktime(&tm);
	    else {
		tm.tm_sec = 0;
		tm.tm_min = (*ap)->min;
		if(tm.tm_min < 0)
		  tm.tm_min = 0;
		tm.tm_hour = (*ap)->hr;
		if(tm.tm_hour < 0)
		  tm.tm_hour = 12; /* good as any time, I guess */
		tm.tm_mday = (*ap)->day;
		tm.tm_mon = (*ap)->month-1;
		tm.tm_year = (*ap)->year-1900;
		tm.tm_isdst = 0;
		ut.modtime = mktime(&tm) - timezone;
	    }
	    if(ut.modtime == -1)
	      ut.modtime = ut.actime;
	    /* if just stamping, stamp & continue */
	    /* FIXME:  this should work with pipes, too */
	    if(mirstmp && !dst->pipe && access(scratch,F_OK)) {
		if(mirmodes)
		  chmod(scratch,(*ap)->mode&~S_IFMT);
		utime(scratch,&ut);
	    }
	}
	if(mirstmp)
	  continue;
	/* if not a regular file, create node and continue with next */
	if(!S_ISREG((*ap)->mode)) {
	    if(dst->pipe) /* FIXME:  yet another failure for pipes */
	      continue;
	    if(debug & DB_TRACE)
	      fprintf(stderr,"Making node %s\n",scratch);
	    if(!mirmodes)
	      (*ap)->mode = ((*ap)->mode & S_IFMT) | 0777;
	    /* I don't want to mess with sockets yet; they fail */
	    if(S_ISDIR((*ap)->mode)?(mkdir(scratch,(*ap)->mode) &&
				     (!mkpath(scratch) ||
				      (mirmodes &&
				       chmod(scratch,(*ap)->mode)))):
	       S_ISLNK((*ap)->mode)?symlink((*ap)->lnk,scratch):
	       S_ISSOCK((*ap)->mode)?-1:
	       mknod(scratch,(*ap)->mode,
		     makedev((*ap)->size>>16,(*ap)->size&0xffff)))
	      perror(scratch);
	    else
	      (*ap)->flags |= FNF_DONE;
	    if(!S_ISLNK((*ap)->mode))
	      utime(scratch,&ut);
	    continue;
	}
	/* retrieve regular file */
	/* preseek if in append mode */
	if(!dst->pipe && mirappend && ((*ap)->flags&FNF_APPEND))
	  fseek(lf,0,SEEK_END);
	/* actually retrieve the file w/ to's & retries */
	j = getfile(lf, &src->site, s);
	if(j || fflush(lf)) {
	    if(!j || j == ERR_OS)
	      perror(scratch);
	    fclose(lf);
	    if(!dst->pipe)
	      remove(scratch);
	    if(j == ERR_MAXRETR) /* don't try every file to max retry */
	      break;
	    continue;
	}
	/* now process the results */
	if(dst->pipe) {
	    if(!pclose(lf))
	      (*ap)->flags |= FNF_DONE;
	} else {
	    /* file: close file & set mode+date */
	    fclose(lf); /* fflush above checked err */
	    /* don't really care if chmod/utime fail */
	    (*ap)->flags |= FNF_DONE;
	    if(mirmodes)
	      chmod(scratch,(*ap)->mode&~S_IFMT);
	    utime(scratch,&ut);
	}
    }
}
lurkftp/lurkftp.h100644    700     24       14140  6367723472  12735 0ustar  darkusers/*
 * lurkftp - lurk around FTP sites, possibly grabbing things
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "ftpsupt.h"
#define PROGRAMNAME "lurkftp v0.99"

#define DEF_LSFILE ".chkls.%s%d.gz"

/* DEF_CRPG can be commented out for no default */
#define DEF_CRPG "gzip"
#define DEF_UNCRPG "gunzip"


/*
 * Set this to 1 to enable experimental time correction code.
 * This may end up being needed for mirroring sites that report time
 * incorrectly either with LIST (which should provide local time) or
 * with MDTM (which should provide UTC).
 * The time-correction code was written when I discovered that all the date
 * stamps on my old debian mirror were screwed up.  I decided not to add it
 * after all when I discovered that the reason for this mismatch was a bug
 * in ncftp (used by my old mirror script), not lurkftp.
 */
#define TIMECORRECT 0

/* default report line format */
/* #define DEFFMT "%T %{%Y %m %d} %12b %r%f%[L? -> %L]" */
#define DEFFMT "%T %d %12b %r%f%[l? -> %L]"

/* default report headers */
#define DEFHEAD "--- %s %d ---"
#define DEFEHEAD "\n*** ERRORS IN %S %P -> %s %p MIRROR ***"
#define DEFTAIL "%t"
#define DEFETAIL "\n"

/* -------------------- end of user-configurable items ------------------*/

#define DB_LOCK  4
#define DB_TRACE 8
#define DB_OPT   16
#define DB_ALL   32

enum ls_type {
    ST_NONE = 0,
    ST_FTPD,	 /* remote LIST [site+dirs] */
    ST_LDIR,	 /* local readdir [dirs] */
    ST_MFILE,	 /* placemarker [lsfile] */
    ST_LSLRF,	 /* local ls-lR file [lsfile] */
    ST_FTPLSLRF, /* remote ls-lR file [site+lsfile] */
    ST_CMD	 /* local command producing ls-lR-type output [lsfile] */
};

struct ftpdir {
    struct ftpsite site;
    struct dirmem dirm;
    const char *lsfile;
    const char *pipe;
    const char **dirs;
    const char *dir0;
    unsigned short ndirs;
    unsigned short type;
};

struct op {
    struct op *next;
    struct ftpdir srcls, dstls;
    const char *rhead, *rfmt, *rtail;
    const char *ehead, *efmt, *etail;
    unsigned rsort, esort;
    const char *errln;
    const char *xfilter, *ifilter;
    regex_t xregex, iregex;
    unsigned int
      srcf_local: 1,
      dstf_remt: 1,
      filtdir: 1,
      filtspec: 1, /* default: 1 */
      quiet: 1,
      dep: 1,
      dofork: 1,
      detmove: 1,
      manrecurs: 1,
      domirop: 1,
      trymir: 1,
      mirmodes: 1,
      mirstmp: 1,
      mirappend: 1;
};

struct totals {
    unsigned long addb, subb, addd, subd, addf, subf, adds, subs;
};

/* lurkftp.c */
extern char *progname;

extern char *reptpipe;

extern int bg;

extern char scratch[4096];

void unlockof(void);
void lockof(void);
extern FILE *of;

/* mir.c */
void mir_chmod(struct ftpdir *dst, struct fname **ap, int nadd);
void mir_del(struct ftpdir *dst, struct fname **dp, int ndel, char filtdir);
void mir_move(struct ftpdir *dst, struct fname **mirarray, int malen,
	      int *nadd, int *ndel);
void mir_transfer(struct fname **ap, int nadd, struct ftpdir *src,
		  const struct ftpdir *dst, char mirstmp, char mirmodes,
		  char mirappend);
void mir_errrpt(struct fname **ap, struct fname **dp, int nadd,
		int ndel, const char *header, const char *tail,
		const struct ftpdir *site, const struct ftpdir *other,
		const char *fmt);

/* opt.c */
extern struct op *ops;
extern int nops;
void parseargs(int argc, char ** argv);

/* diff.c */
/*
 * sort for reports & default diff:
 * order by type, then
 * year, month, day, name, [lnname], size, [min/sec]
 */
#define REPTCMP  0x7654320 /* "fdpnlst" */

/*
 * sort for move detection:
 * type, name (w/o dir), [linkname], date, size, dir: ascending
 */
#define MVCMP 0x7362540 /* "fnldspt" */

/*
 * diff-compare for move detection:
 * same as sorter above but no dir name difference
 */
#define MVCHK 0x762540 /* "fnldst" */

/*
 * sort for mirror processing:
 * name, [linkname], type, date, size: descending
 * [only files care about date & size]
 */
#define PROCCMP 0xfea8dcb /* "PNLFDST" */

extern unsigned sorder;
unsigned parsesort(const char *str);
int gencmp(const void *__a, const void *__b);
#define sort(a,n,o) do {sorder = o; qsort(a,n,sizeof(*a),gencmp);}while(0)
struct fname **diff_dir(struct ftpdir *src, struct ftpdir *dst,
			int *arraylen, int *nadd, int *ndel,
			char detmove, char chkmodes);

/* rept.c */
void headsubst(char *buf, const char *pat, const struct ftpdir *site,
	       const struct ftpdir *other, const struct totals *tot);
void namesubst(char *buf, const char *pat, const struct ftpsite *src,
	       const struct ftpdir *dst, const struct fname *item, char type);
void pr_rept(struct fname **mirarray, int malen, int nadd, int ndel,
	     const char *header, const char *tail, const struct ftpdir *site,
	     const struct ftpdir *other, const char *fmt, unsigned sort,
	     char resort);

/* misc.c */
void freedir(struct dirmem *dir);
char *allocstr(int len, struct strmem **sp);
char *copystr(const char *str, int len, struct strmem **sp);
struct fname *lastname(struct fnamemem **pd);
void freelastname(struct fnamemem **pd);
int toarray(struct dirmem *dir);

int readtree(struct dirmem *dir, const char *wd);
typedef int (*read_func)(void *data, void *buf, int buflen);
int parsels(struct dirmem *dir, read_func read_func, void *data,
	    const char *cd, const char *root);
void filterdir(struct dirmem *dir, const regex_t *filter, int cut);

int readlsfile(struct dirmem *dir, FILE *f, const char *root);
int writelsfile(const char *name, struct dirmem *dir, int mustproc);

/* routines in ftpsupt.c */
int getfile(FILE *f, struct ftpsite *site, const char *rf);
int filetm(struct ftpsite *site, const char *name, struct tm *tm);

void logout(struct ftpsite *site);

const char *ftperr(int err);
int readftplsf(struct dirmem *dir, struct ftpsite *site,
	       const char *dirs, const char *rname);
int readftpdir(struct dirmem *dir, struct ftpsite *site, const char **dirs,
	       int ndirs, int recurse, const regex_t *xfilt);
lurkftp/diff.c100644    700     24       17042  6363423041  12136 0ustar  darkusers/*
 * Compare/diff functions for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"

/* sort order; qsort() doesn't take parms, so this is static */
unsigned sorder;

/* for following compare function: compare 2 directory names */
static int dircmp(const struct fname *a, const struct fname *b)
{
    int i,j;

    if(a->root != b->root && a->root && b->root) { /* probably mirroring */
	i = strlen(a->root);
	j = strlen(b->root);
	if(!i || a->root[i-1] != '/')
	  i++;
	if(!j || b->root[j-1] != '/')
	  j++;
	return strcmp(a->dir+i,b->dir+j);
    } else
      return strcmp(a->dir, b->dir);
}

/*
 * Generic sort routine
 * sort order is in string:
 * 0 = f = filetype
 * 1 = m = mode
 * 2 = d = date (YYYY.MM.DD) (if file)
 * 3 = p = dir (path)
 * 4 = n = name
 * 5 = l = link name (if present)
 * 6 = s = size (if file)
 * 7 = t = time (HH:MM) (if file)
 * |8 = caps = reverse sort order.
 */
unsigned parsesort(const char *str)
{
    unsigned res = 0;
    while(*str) {
	res <<= 4;
	switch(*str++) {
	  case 'f':
	    res |= 0;
	    break;
	  case 'F':
	    res |= 8;
	    break;
	  case 'm':
	    res |= 1;
	    break;
	  case 'M':
	    res |= 9;
	    break;
	  case 'd':
	    res |= 2;
	    break;
	  case 'D':
	    res |= 10;
	    break;
	  case 'p':
	    res |= 3;
	    break;
	  case 'P':
	    res |= 11;
	    break;
	  case 'n':
	    res |= 4;
	    break;
	  case 'N':
	    res |= 12;
	    break;
	  case 'l':
	    res |= 5;
	    break;
	  case 'L':
	    res |= 13;
	    break;
	  case 's':
	    res |= 6;
	    break;
	  case 'S':
	    res |= 14;
	    break;
	  case 't':
	    res |= 7;
	    break;
	  case 'T':
	    res |= 15;
	    break;
	}
    }
    return res;
}

int gencmp(const void *__a, const void *__b)
{
    const struct fname * const * const _a = __a, * const * const _b = __b;
    const struct fname * const au = *_a, * const bu = *_b;
    const struct fname *a, *b;
    int md;
    int i;
    register unsigned s;

    md = (au->mode&S_IFMT)-(bu->mode&S_IFMT);
    for(s = sorder; s; s >>= 4) {
	if(s & 8) {
	    a = bu;
	    b = au;
	} else {
	    a = au;
	    b = bu;
	}
	switch(s & 7) {
	  case 0: /* f = filetype */
	    if(md)
	      return s&8?-md:md;
	    break;
	  case 1: /* m = mode */
	    i = (a->mode&~S_IFMT)-(b->mode&~S_IFMT);
	    if(i)
	      return i;
	    break;
	  case 2: /* d = date */
	    if(!md && S_ISREG(a->mode)) {
		if(a->year != b->year)
		  return a->year - b->year;
		if(a->month != b->month)
		  return a->month - b->month;
		if(a->day != b->day)
		  return a->day - b->day;
	    }
	    break;
	  case 3: /* p = path */
	    if((i = dircmp(a,b)))
	      return i;
	    break;
	  case 4: /* n = name */
	    i = strcmp(a->name,b->name);
	    if(i)
	      return i;
	    break;
	  case 5: /* l = link */
	    if(!md && S_ISLNK(a->mode)) {
		i = strcmp(a->lnk, b->lnk);
		if(i)
		  return i;
	    }
	    break;
	  case 6: /* s = size */
	    if(!md && S_ISREG(a->mode) && (i = (int)a->size - (int)b->size))
	      return i;
	    break;
	  case 7: /* t = time */
	    /* Check hour & minute, only if all else is the same */
	    /* otherwise diffing may be screwed up because remote list */
	    /* doesn't have hr+min, but local stuff does */
	    if(!md && S_ISREG(a->mode)) {
		if(a->hr != b->hr && a->hr >= 0 && b->hr >= 0)
		  return a->hr - b->hr;
		if(a->min != b->min && a->min >= 0 && b->min >= 0)
		  return a->min - b->min;
	    }
	    break;
	}
    }
    return 0;
}

/* convenience macro for move detection; see description below */
#define chk1mov(a,b,i,j) do { \
    while(j && !(k=gencmp(&a[-1],b)) && !dircmp(a[-1],*b)) { \
	b++; \
	j--; \
	if(chkmodes && a[-1]->mode != b[-1]->mode) { \
	    n[-1]->flags |= FNF_MODEONLY|FNF_PROCESS; \
	    *ap++ = n[-1]; \
	} \
	if(!i) { \
	    k = 0; /* flag: bad */ \
	    break; \
	} \
	a++; \
	i--; \
    } \
    if(j && i && !k) { \
	while(j && i && !(k=gencmp(a,b)) && !dircmp(*a,*b)) { \
	    if(chkmodes && (*a)->mode != (*b)->mode) { \
		(*b)->flags |= FNF_MODEONLY|FNF_PROCESS; \
		*ap++ = *b; \
	    } \
	    a++; \
	    b++; \
	    i--; \
	    j--; \
	} \
	/* flag bad if any more near-matches */ \
	k = (!i || gencmp(&a[-1],a)) && (!j || gencmp(&b[-1],b)); \
    } else if(j && !gencmp(&b[-1],b)) \
	  k = 0; /* flag: bad */ \
    if(k && !gencmp(&a[-1],&b[-1])) { /* good */ \
	a[-1]->flags |= FNF_MOVE|FNF_PROCESS; \
	b[-1]->flags |= FNF_MOVE|FNF_PROCESS; \
	*ap++ = n[-1]; \
	*--dp = o[-1]; \
	continue; \
    } \
} while(0)

struct fname **diff_dir(struct ftpdir *src, struct ftpdir *dst,
			int *arraylen, int *nadd, int *ndel,
			char detmove, char chkmodes)
{
    struct fname **mirarray, **ap, **dp, **o, **n;
    int i, j, k;

    *arraylen = src->dirm.count+dst->dirm.count;
    /* create difference array big enough to hold all old & new items */
    ap = mirarray = malloc(*arraylen*sizeof(*ap));
    if(!ap) {
	perror("diff tmp storage");
	return NULL;
    }
    dp = ap+*arraylen; /* deletions stored at top of list */
    /* since remt and local sorted in same order, diff by running */
    /* running up both lists; which ever pointer is lower in comparison */
    /* gets incremented & reported. */
    if(debug & DB_TRACE)
      fputs("Finding differences in listings\n",stderr);
    sorder = detmove?MVCHK:REPTCMP;
    for(o=dst->dirm.array,n=src->dirm.array,i=dst->dirm.count,j=src->dirm.count;j && i;) {
	k = gencmp(n,o);
	if(debug & DB_ALL)
	  fprintf(stderr,"diff: %02d:%02d %02d/%02d/%04d (%ld/%o) %s%s%s%s - "
		  " %02d:%02d %02d/%02d/%04d (%ld/%o) %s%s%s%s: %d\n",
		  (*o)->hr,(*o)->min,(*o)->month,(*o)->day,(*o)->year,
		  (*o)->size,(unsigned)(*o)->mode,(*o)->dir,(*o)->name,
		  S_ISLNK((*o)->mode)?" -> ":"",
		  S_ISLNK((*o)->mode)?(*o)->lnk:"",
		  (*n)->hr,(*n)->min,(*n)->month,(*n)->day,(*n)->year,
		  (*n)->size,(unsigned)(*n)->mode,(*n)->dir,(*n)->name,
		  S_ISLNK((*n)->mode)?" -> ":"",
		  S_ISLNK((*n)->mode)?(*n)->lnk:"",
		  k);
	if(!k) {
	    n++;
	    o++;
	    i--;
	    j--;
	    /* extra check for mere mode change */
	    if(chkmodes && o[-1]->mode != n[-1]->mode)
	      n[-1]->flags |= FNF_MODEONLY|FNF_PROCESS;
	    /* extra check for mere move */
	    if(detmove && (k=dircmp(n[-1],o[-1]))) {
		/* now make sure only one file moved */
		/* procedure: check if additional items match.  */
		/* if so, skip ones that are also in the same dir */
		/* finally, if a move condition still exists, */
		/* and no additional items match, flag as moved */
		/* this is in a macro because we need to do the same thing */
		/* with either (o,n) or (n,o) depending on which is higher */
		/* from sort */
		/* procedure is duplicated, so it's written as a macro above */
		if(k < 0)
		  chk1mov(o,n,i,j);
		else
		  chk1mov(n,o,j,i);
	    }
	    /* if not already added by move logic, add for mode change */
	    if(n[-1]->flags&FNF_MODEONLY)
	      *ap++ = n[-1];
	    continue;
	}
	if(k<0) {
	    *ap++ = *n; /* mark added */
	    (*n)->flags |= FNF_PROCESS;
	    n++;
	    j--;
	} else {
	    *--dp = *o; /* mark removed */
	    (*o)->flags |= FNF_PROCESS;
	    o++;
	    i--;
	}
    }
    /* add any remaining new items */
    while(j) {
	*ap++ = *n; /* mark added */
	(*n)->flags |= FNF_PROCESS;
	n++;
	j--;
    }
    /* remove any remaining old items */
    while(i) {
	*--dp = *o; /* mark removed */
	(*o)->flags |= FNF_PROCESS;
	o++;
	i--;
    }
    *nadd = ap - mirarray;
    *ndel = (mirarray+*arraylen)-dp;
    return mirarray;
}
lurkftp/NEW100644    700     24        4604  6363674353  11434 0ustar  darkusers-a (at) is replaced by script: atcron
guarantee of seekability on pipes is replaced by command: pipe
guarantee of not executing command unless/until transfer successful is
removed.  SIGPIPE is given on error instead.  Can use command: pipe instead
-S (mail) is replaced by generic pipe: -R <pipe>
-R/-Q/-T/-r/-C (timeout) replaced by generic timeout: -T <type><val>...

Custom reports (see man page): -R/-r options

There are more %-switches for pipes; see man page.  Most importantly,
%[<cond>?<yes>:<no>] - if <cond> then add <yes>, else <no>.  Actually, the
parsing of this is nonrecursive, so ?<yes>:<no>:<yes>:<no>... works.

You can now read source/destination directories in any of 6 possible ways,
and retrieve/store files in any of 3 possible ways.

Directory read method is set by old options and/or new -o/-O options.
Note: directories in 'd' and 'f' options are cumulative & may be added to
for the source dir by simply listing more.  If multiple directories are
listed for src, all are read & combined.  If multiple dirs are in dest, only
the last dir is read and updated, and soft links are resolved to alternate
directories.  In this mode, new files which match ones already in other
directories are updated as a soft link into the old file.

"source" dir:
<ftp_addr> <dirs> -> FTP site <ftp_addr>, containing given <dirs>.
-L <name> -> Retrieve ls-lR file from already-specified FTP site
-f <name> -> Retrieve marker file, with root specified by given dirs.
-O<type>:<parm>: generic specifier

"dest" dir:
If placeholder is ever specified for "dest", then it is updated.

default -> placeholder file
-d <ldir> -> default = local directory
-t <site> -> default = remote FTP dir
-L <site> -> remote FTP ls-lR file
-l <name> -> local placeholder file
-o<type>:<parm>: generic specifier; see above.

"dest" file:
default -> no mirroring is done unless a dest is specified
-d <dir> -> files local dir
-e <cmd> -> send to command; compatible w/ -d
-t <site> -> put on FTP site

"source" file:
default -> retrieve from given FTP site/dir
-c -> ignore site & get files from local dir
-g <cmd> -> "retrieve" file by running pipe

Valid transfer methods:
pipe->pipe
pipe->ftp
pipe->local
local->ftp
local->local
local->pipe
ftp->local  - implemented & tested w/ -d option
ftp->pipe   - implemented & tested w/ -e option
ftp->ftp  -> This is a special mode, and requires PASV support on at least one
             of the two sites.
lurkftp/indmac.sl100644    700     24        3505  6353513663  12645 0ustar  darkusers% Jed C-Mode enhancement for indenting multi-line macros
% Still slightly buggy, but it works well enough for me..
%  - dark@mama.indstate.edu -

define indmac ()
{
    variable numlines;

    push_spot();
    EXIT_BLOCK
    {
	pop_spot();
    }
    bol();
    !if (looking_at("#define"))
      return;
    eol();
    go_left_1();
    !if(looking_at_char('\\'))
      return;
    eol();
    insert(" \n{");
    go_down_1();
    eol();
    go_left_1();
    numlines = 0;
    while(looking_at_char('\\')) {
	++numlines;
	c_indent_line();
	eol();
	go_left_1();
	del();
	trim();
	go_down_1();
	eol();
	go_left_1();
    }
    pop_spot();
    push_spot();
    eol(); trim();
    go_down_1();
    bol();
    del_through_eol();
    go_up_1();
    loop(numlines) {
	go_down_1();
	eol();
	insert(" \\");
    }
}

define do_c_indent()
{
    push_spot();
    while(up_1()) {
	eol(); trim();
	!if(bolp())
	  go_left_1();
	!if(looking_at_char('\\')) {
	    go_down_1();
	    break;
	}
    }
    bol();
    if (looking_at("#define")) {
	indmac();
	pop_spot();
	push_spot();
	eol();
	go_left_1();
	while(looking_at_char('\\')) {
	    go_down_1();
	    eol();
	    go_left_1();
	}
	bol_skip_white();
	pop_spot();
    } else {
	pop_spot();
	c_indent_line();
    }
}

define do_c_indent_nl()
{
    variable did_up = 0;

    push_spot();
    do {
	eol(); trim();
	!if(bolp())
	  go_left_1();
	!if(looking_at_char('\\')) {
	    if(did_up)
	      go_down_1();
	    break;
	}
	did_up = 1;
    } while(up_1());
    bol();
    if (looking_at("#define")) {
	push_mark();
	pop_spot();
	newline();
	push_spot();
	insert(";\\");
	pop_mark_1();
	indmac();
	pop_spot();
	skip_white();
	del();
    } else {
	pop_spot();
	c_newline_and_indent();
    }
}

define c_mode_hook ()
{
    set_buffer_hook("indent_hook", "do_c_indent");
    set_buffer_hook("newline_indent_hook", "do_c_indent_nl");
}
lurkftp/rept.c100644    700     24       34137  6363421725  12213 0ustar  darkusers/*
 * Compare and report functions for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"

static char *addtot(char *buf, const char *type, unsigned long bytes,
		    unsigned long files, unsigned long dirs,
		    unsigned long specials)
{
    int got1 = 0;
    const char *sz;

    if(!bytes && !files && !dirs && !specials)
      return buf;
    if(dirs) {
	buf += sprintf(buf,"%lu directories", dirs);
	got1 = 1;
    }
    if(specials) {
	buf += sprintf(buf,"%s%lu special nodes", got1?", ":"",specials);
	got1 = 1;
    }
    if(files) {
	if(bytes < 10000)
	  sz = "bytes";
	else {
	    bytes /= 1000;
	    if(bytes < 10000)
	      sz = "KB";
	    else {
		bytes /= 1000;
		if(bytes < 10000)
		  sz = "MB";
		else
		  sz = "GB";
	    }
	}
	buf += sprintf(buf,"%s%lu %s in %lu files",got1?", ":"",bytes,sz,files);
    }
    buf += sprintf(buf," %s\n",type);
    return buf;
}

/* perform %-substitutions for lsfile names & headers/footers */
/* FIXME: add string for err msg */
void headsubst(register char *buf, register const char *pat,
	       const struct ftpdir *site, const struct ftpdir *other,
	       const struct totals *tot)
{
    int i;
    char *s;
    int wid, maxc;
    char rl;
    char *bufs = buf;

    if(debug & DB_ALL)
      fprintf(stderr,"head: '%s' -> ",pat);
    for(;*pat;pat++) {
	if(*pat != '%')
	  *buf++ = *pat;
	else {
	    wid = maxc = -1;
	    if(*++pat == '-' && isdigit(pat[1])) {
		rl = 1;
		pat++;
	    } else
	      rl = 0;
	    while(isdigit(*pat)) {
		if(wid == -1)
		  wid = *pat-'0';
		else
		  wid = (wid*10)+(*pat-'0');
		pat++;
	    }
	    if(*pat == '.' && isdigit(pat[1])) {
		while(isdigit(*++pat)) {
		    if(maxc == -1)
		      maxc = *pat-'0';
		    else
		      maxc = (maxc*10)+(*pat-'0');
		}
	    }
	    switch(*pat) {
	      case 's':
		if(!site->site.host)
		  break;
		if(maxc >= 0)
		  strncpy(buf,site->site.host,maxc);
		else
		  strcpy(buf,site->site.host);
		maxc = strlen(buf);
		break;
	      case 'd':
		if(!site->ndirs) {
		    maxc = 0;
		    break;
		}
		strcpy(buf,site->dirs[0]);
		s = buf+strlen(buf);
		for(i = 1; i< site->ndirs; i++) {
		    *s++ = '_';
		    strcpy(s, site->dirs[i]);
		    s += strlen(s);
		}
		s = buf;
		do {
		    if(*s == ' ')
		      *s = '_';
		    else if(*s == '/')
		      *s = '.';
		} while(*++s);
		maxc = s - buf;
		break;
	      case 'p':
		if(!site->ndirs) {
		    maxc = 0;
		    break;
		}
		strcpy(buf,site->dirs[0]);
		s = buf+strlen(buf);
		for(i = 1; i< site->ndirs; i++) {
		    *s++ = ' ';
		    strcpy(s, site->dirs[i]);
		    s += strlen(s);
		}
		maxc = s - buf;
		break;
	      case 'S':
		if(!other->site.host)
		  break;
		if(maxc >= 0)
		  strncpy(buf,other->site.host,maxc);
		else
		  strcpy(buf,other->site.host);
		maxc = strlen(buf);
		break;
	      case 'D':
		if(!other->ndirs) {
		    maxc = 0;
		    break;
		}
		strcpy(buf,other->dirs[0]);
		s = buf+strlen(buf);
		for(i = 1; i< other->ndirs; i++) {
		    *s++ = '_';
		    strcpy(s, other->dirs[i]);
		    s += strlen(s);
		}
		s = buf;
		do {
		    if(*s == ' ')
		      *s = '_';
		    else if(*s == '/')
		      *s = '.';
		} while(*++s);
		maxc = s - buf;
		break;
	      case 'P':
		if(!other->ndirs) {
		    maxc = 0;
		    break;
		}
		strcpy(buf,other->dirs[0]);
		s = buf+strlen(buf);
		for(i = 1; i< other->ndirs; i++) {
		    *s++ = ' ';
		    strcpy(s, other->dirs[i]);
		    s += strlen(s);
		}
		maxc = s - buf;
		break;
	      case 't':
		/* totals */
		if(tot) {
		    buf = addtot(buf, "added", tot->addb, tot->addf, tot->addd,
				 tot->adds);
		    buf = addtot(buf, "removed", tot->subb, tot->subf, tot->subd,
				 tot->subs);
		}
		maxc = wid = -1;
		break;
	      default:
		*buf++ = *pat;
		maxc = wid = -1;
		break;
	    }
	    if(maxc < 0)
	      maxc = 0;
	    if(wid > maxc) {
		if(rl)
		  while(wid > maxc)
		    buf[maxc++] = ' ';
		else {
		    for(s = buf + maxc; s > buf; s--)
		      s[wid-maxc-1] = s[-1];
		    while(wid > maxc) {
			*buf++ = ' ';
			wid--;
		    }
		}
	    }
	    buf += maxc;
	}
    }
    *buf = 0;
    if(debug & DB_ALL)
      fprintf(stderr,"'%s'\n",bufs);
}

/* perform %-substitutions for names & pipes */
void namesubst(register char *buf, const char *pat, const struct ftpsite *src,
	       const struct ftpdir *dst, const struct fname *item,
	       char type)
{
    register char *t;
    struct tm tm;
    int wid, maxc;
    unsigned char rl;
    unsigned char bnest = 0, modval;
    mode_t xmod = 0;
    char *bufs = buf;
    size_t sz;

    if(debug & DB_ALL)
      fprintf(stderr,"name: '%s' -> ",pat);
    for(;*pat;pat++) {
	if(*pat == '%') {
	    wid = maxc = -1;
	    if(*++pat == '-' && isdigit(pat[1])) {
		rl = 1;
		pat++;
	    } else
	      rl = 0;
	    while(isdigit(*pat)) {
		if(wid == -1)
		  wid = *pat-'0';
		else
		  wid = (wid*10)+(*pat-'0');
		pat++;
	    }
	    if(*pat == '.' && isdigit(pat[1])) {
		while(isdigit(*++pat)) {
		    if(maxc == -1)
		      maxc = *pat-'0';
		    else
		      maxc = (maxc*10)+(*pat-'0');
		}
	    }
	    switch(*pat) {
	      case 'T':
		*buf = type;
		maxc = 1;
		break;
	      case 's':
		if(src) {
		    strcpy(buf,src->host);
		    maxc = strlen(buf);
		} else
		  maxc = 0;
		break;
	      case 'f':
		strcpy(buf,item->name);
		maxc = strlen(buf);
		break;
	      case 'L':
		if(S_ISLNK(item->mode)) {
		    strcpy(buf,item->lnk);
		    maxc = strlen(buf);
		} else
		  maxc = 0;
		break;
	      case '[':
		modval = 0;
		maxc = wid = 0;
		while(*++pat && *pat != '?' && *pat != ':') {
		    xmod &= ~S_IFMT;
		    if(!modval)
		      switch(*pat) {
			case 'B':
			  modval = 0;
			  switch(pat[1]) {
			    case '=':
			      pat++;
			      modval = 1;
			      break;
			    case '>':
			      pat++;
			      break;
			    case '<':
			      pat++;
			      modval = 2;
			      break;
			  }
			  sz = 0;
			  while(isdigit(pat[1]))
			    sz = sz*10 + *++pat - '0';
			  switch(pat[1]) {
			    case 'K':
			      sz *= 1024;
			      pat++;
			      break;
			    case 'B':
			      sz *= 512;
			      pat++;
			      break;
			    case 'M':
			      sz *= 1024*1024;
			      pat++;
			      break;
			  }
			  if(!S_ISREG(item->mode)) {
			      modval = 0;
			      break;
			  }
			  switch(modval) {
			    case 0:
			      modval = item->size > sz;
			      break;
			    case 1:
			      modval = item->size == sz;
			      break;
			    case 2:
			      modval = item->size < sz;
			      break;
			  }
			  break;
			case 'l':
			  if(S_ISLNK(item->mode))
			    modval = 1;
			  break;
			case 'f':
			  if(S_ISREG(item->mode))
			    modval = 1;
			  break;
			case 'd':
			  if(S_ISDIR(item->mode))
			    modval = 1;
			  break;
			case 's':
			  if(S_ISSOCK(item->mode))
			    modval = 1;
			  break;
			case 'b':
			  if(S_ISBLK(item->mode))
			    modval = 1;
			  break;
			case 'c':
			  if(S_ISCHR(item->mode))
			    modval = 1;
			  break;
			case 'p':
			  if(S_ISFIFO(item->mode))
			    modval = 1;
			  break;
			case 't':
			  if(item->mode & S_ISVTX)
			    modval = 1;
			  break;
			case 'u':
			  xmod |= S_IFMT|S_IRWXU|S_ISUID;
			  break;
			case 'g':
			  xmod |= S_IFMT|S_IRWXG|S_ISGID;
			  break;
			case 'o':
			  xmod |= S_IFMT|S_IRWXO;
			  break;
			case 'r':
			  if(!xmod && (item->mode & (S_IRUSR|S_IRGRP|S_IROTH)))
			    modval = 1;
			  else if(xmod && (item->mode & xmod & (S_IRUSR|S_IRGRP|S_IROTH)))
			    modval = 1;
			  xmod |= S_IFMT;
			  break;
			case 'w':
			  if(!xmod && (item->mode & (S_IWUSR|S_IWGRP|S_IWOTH)))
			    modval = 1;
			  else if(xmod && (item->mode & xmod & (S_IWUSR|S_IWGRP|S_IWOTH)))
			    modval = 1;
			  xmod |= S_IFMT;
			  break;
			case 'x':
			  if(!xmod && (item->mode & (S_IXUSR|S_IXGRP|S_IXOTH)))
			    modval = 1;
			  else if(xmod && (item->mode & xmod & (S_IXUSR|S_IXGRP|S_IXOTH)))
			    modval = 1;
			  xmod |= S_IFMT;
			  break;
			case 'S':
			  if(!xmod && (item->mode & (S_ISUID|S_ISGID)))
			    modval = 1;
			  else if(xmod && (item->mode & xmod & (S_ISUID|S_ISGID)))
			    modval = 1;
			  xmod |= S_IFMT;
			  break;
			case 'T':
			  if(pat[1] && *++pat == type)
			    modval = 1;
			  break;
		      }
		    if(!(xmod & S_IFMT))
		      xmod = 0;
		}
		if(*pat) {
		    if((modval && *pat == '?') || (!modval && *pat == ':'))
		      bnest++;
		    else {
			modval = 0;
			while(*++pat) {
			    if(*pat == '%') {
				if(*++pat == '[')
				  modval++;
				else if(!*pat)
				  break;
			    } else if(*pat == ']') {
				if(!modval--)
				  break;
			    } else if(!modval && *pat == ':') {
				bnest++;
				break;
			    }
			}
		    }
		}
		break;
	      case 'r':
		strcpy(buf,item->dir);
		maxc = strlen(buf);
		break;
	      case 'l':
		if(dst && dst->dir0)
		  strcpy(buf,dst->dir0);
		else
		  strcpy(buf,item->root); /* better than nothing */
		if(!*buf)
		  *buf++ = '/';
		buf += strlen(buf);
		if(buf[-1] != '/')
		  *buf++ = '/';
		if(item->dir[strlen(item->root)] == '/')
		  buf--;
		strcpy(buf,item->dir+strlen(item->root));
		maxc = strlen(buf);
		break;
	      case 'b':
		maxc = sprintf(buf,"%lu",item->size);
		break;
	      case 'm':
		maxc = sprintf(buf,"0%o",item->mode);
		break;
	      case 'D':
		if(S_ISCHR(item->mode) || S_ISBLK(item->mode))
		  maxc = sprintf(buf,"%lu",item->size>>16);
		else
		  maxc = 0;
		break;
	      case 'M':
		if(S_ISCHR(item->mode) || S_ISBLK(item->mode))
		  maxc = sprintf(buf,"%lu",item->size&0xffff);
		else
		  maxc = 0;
		break;
	      case 'd':
		maxc = sprintf(buf,"%4hu%02hu%02hu",item->year,item->month,item->day);
		break;
	      case 't':
		maxc = sprintf(buf,"%02hu:%02hu",item->hr,item->min);
		break;
	      case '{':
		t = strchr(pat, '}');
		tm.tm_sec = 0;
		tm.tm_min = item->min;
		if(tm.tm_min < 0)
		  tm.tm_min = 0;
		tm.tm_hour = item->hr;
		if(tm.tm_hour < 0)
		  tm.tm_hour = 12; /* good as any time */
		tm.tm_mday = item->day;
		tm.tm_mon = item->month-1;
		tm.tm_year = item->year-1900;
		tm.tm_isdst = 0;
		/* 64 is enough for anyone, I'm sure */
		memcpy(buf+64,pat,t-pat);
		buf[64+t-pat] = 0;
		maxc = strftime(buf,64,buf,&tm);
		if(t)
		  pat = t;
		break;
	      default:
		*buf++ = *pat;
		maxc = wid = 0;
		break;
	    }
	    if(maxc < 0)
	      maxc = 0;
	    if(wid > maxc) {
		if(rl)
		  while(wid > maxc)
		    buf[maxc++] = ' ';
		else {
		    for(t = buf + maxc; t > buf; t--)
		      t[wid-maxc-1] = t[-1];
		    while(wid > maxc) {
			*buf++ = ' ';
			wid--;
		    }
		}
	    }
	    buf += maxc;
	} else if(bnest && *pat == ']')
	      bnest--;
	else if(bnest && *pat == ':') {
	    modval = 0;
	    while(*++pat) {
		if(*pat == '%')
		  if(*++pat == '[')
		    modval++;
		else if(*pat == ']')
		  if(!modval--)
		    break;
		else if(!modval && *pat == ':')
		  break;
	    }
	    if(*pat == ']')
	      bnest--;
	} else
	  *buf++ = *pat;
    }
    *buf = 0;
    if(debug & DB_ALL)
      fprintf(stderr,"'%s'\n",bufs);
}

static char prname(char didrept, const char *fmt, char prefix,
		   const struct fname *n, const char *header,
		   const struct ftpdir *site, const struct ftpdir *other)
{
    char *s;

    namesubst(scratch, fmt, NULL, NULL, n, prefix);
    if(!scratch[0])
      return didrept;
    if(!didrept) {
	didrept = 1;
	lockof();
	s = scratch+strlen(scratch)+1;
	headsubst(s,header, site, other, NULL);
	if(*s) {
	    fputs(s, of);
	    if(s[strlen(s)-1] != '\n')
	      fputc('\n', of);
	}
	didrept = 1;
    }
    fputs(scratch, of);
    if(scratch[strlen(scratch)-1] != '\n')
      fputc('\n', of);
    return didrept;
}

void pr_rept(struct fname **mirarray, int malen, int nadd, int ndel,
	     const char *header, const char *tail, const struct ftpdir *site,
	     const struct ftpdir *other, const char *fmt, unsigned _sort,
	     char resort)
{
    const struct fname **o, **n;
    int i, j, k;
    char didrept = 0;
    struct totals tot = {0};

    /* print report */
    i = ndel;
    j = nadd;
    o = (const struct fname **)mirarray+malen-ndel;
    n = (const struct fname **)mirarray;
    if(resort) {
	/* resort by full name */
	sort(o, i, _sort);
	sort(n, j, _sort);
    } else
      o += ndel-1; /* reverse order sort on deletions */
    sorder = _sort;
    while(i || j) {
	if(!i)
	  k = 1;
	else if(!j)
	  k = -1;
	else
	  k = gencmp(o,n);
	if(k <= 0) {
	    if(S_ISREG((*o)->mode)) {
		tot.subf++;
		tot.subb += (*o)->size;
	    } else if(S_ISDIR((*o)->mode)) /* && !filtdir */
		  tot.subd++;
	    else
	      tot.subs++;
	    didrept = prname(didrept, fmt,((*o)->flags&FNF_MOVE)?'<':'-',*o,
			     header, site, other);
	    i--;
	    if(resort)
	      o++;
	    else
	      o--;
	} else {
	    if(S_ISREG((*n)->mode)) {
		tot.addf++;
		tot.addb += (*n)->size;
	    } else if(S_ISDIR((*n)->mode))
		  tot.addd++;
	    else
	      tot.adds++;
	    if((*n)->flags & FNF_MODEONLY) {
		didrept = prname(didrept, fmt,'M',*n, header, site, other);
		if((*n)->flags & FNF_MOVE)
		  didrept = prname(didrept, fmt,'>',*n, header, site, other);
	    } else
	      didrept = prname(didrept, fmt,((*n)->flags&FNF_MOVE)?'>':'+',*n,
			       header, site, other);
	    n++;
	    j--;
	}
    }
    if(didrept) {
	headsubst(scratch, tail, site, other, &tot);
	if(*scratch) {
	    fputs(scratch, of);
	    if(scratch[strlen(scratch)-1] != '\n')
	      fputc('\n', of);
	}
	unlockof();
    }
}

/* report mirroring errors */
void mir_errrpt(struct fname **ap, struct fname **dp, int nadd,
		int ndel, const char *header, const char *tail,
		const struct ftpdir *site, const struct ftpdir *other,
		const char *fmt)
{
    int anyout = 0;

    /* report errors in deletions */
    for(;ndel;ndel--,dp++) {
	if((*dp)->flags & FNF_DONE)
	  continue;
	anyout = prname(anyout,fmt,(*dp)->flags & FNF_MOVE?'(':'#',*dp,
			header,site,other);
    }
    /* report errors in downloads */
    for(;nadd;ap++,nadd--) {
	if((*ap)->flags & FNF_DONE)
	  continue;
	anyout = prname(anyout,fmt,(*ap)->flags & FNF_MOVE?')':
			(*ap)->flags & FNF_MODEONLY?'$':'*',*ap,header,site,
			other);
    }
    if(anyout) {
	headsubst(scratch, tail, site, other, NULL);
	if(*scratch) {
	    fputs(scratch, of);
	    if(scratch[strlen(scratch)-1] != '\n')
	      fputc('\n', of);
	}
	unlockof();
    }
}
lurkftp/misc.c100644    700     24       32626  6367714167  12206 0ustar  darkusers/*
 * Support routines for lurkftp
 * 
 * (C) 1997 Thomas J. Moore
 * questions/comments -> dark@mama.indstate.edu
 * 
 * This is free software.  No warranty for applicability or fitness is
 * expressed or implied.
 * 
 * This code may be modified and distributed freely so long as the original
 * author is credited and any changes are documented as such.
 * 
 */

#include "lurkftp.h"
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <pwd.h>

char *crpg = NULL;
char *uncrpg = NULL;

struct tm curtm; /* to be filled in by main() */

/* some generic scratch buffers that prevent this */
/* from ever being thread-safe */
static char buf[4096];
static char name[4096];

/* memory functions */

#define copyname(s) copystr(s,strlen(s),&dir->names)
#define copynamel(s,l) copystr(s,l,&dir->names)

char *allocstr(int len, struct strmem **sp)
{
    struct strmem *sm;
    char *s;

    while((sm = *sp)) {
	sp = &sm->nxt;
	if(BUFLEN-1-sm->ptr > (unsigned)len) {
	    s = sm->buf+sm->ptr;
	    sm->ptr += len+1;
	    return s;
	}
    }
    sm = *sp = malloc(sizeof(**sp));
    if(!sm)
      return NULL;
    sm->nxt = NULL;
    sm->ptr = len+1;
    return sm->buf;
}

char *copystr(const char *str, int len, struct strmem **sp)
{
    char *s = allocstr(len, sp);

    if(s) {
	memcpy(s,str,len);
	s[len] = 0;
    }
    return s;
}

static struct fname *allocname (struct fnamemem **pd)
{
    struct fnamemem *dir;
    struct fname *f;

    while((dir = *pd)) {
	pd = &dir->nxt;
	/* this assumes size will be set immediately to non-~0 */
	if(dir->names[0].size == ~0UL) {
	    f = &dir->names[dir->names[0].year--];
	    f->flags = 0;
	    return f;
	}
    }
    dir = *pd = malloc(sizeof(**pd));
    if(!dir)
      return NULL;
    dir->nxt = NULL;
    dir->names[0].size = ~0;
    dir->names[0].year = NSKIP-2;
    f = &dir->names[NSKIP-1];
    f->flags = 0;
    return f;
}

void freelastname(struct fnamemem **pd)
{
    struct fnamemem *dir = NULL;

    while(*pd) {
	dir = *pd;
	pd = &dir->nxt;
	/* this assumes size will be set immediately to non-~0 */
	if(dir->names[0].size == ~0UL) {
	    dir->names[0].year++;
	    return;
	}
    }
    if(!dir) /* error, actually */
      return;
    dir->names[0].size = ~0UL;
    dir->names[0].year = 0;
}

struct fname *lastname(struct fnamemem **pd)
{
    struct fnamemem *dir = NULL;

    while(*pd) {
	dir = *pd;
	pd = &dir->nxt;
	/* this assumes size will be set immediately to non-~0 */
	if(dir->names[0].size == ~0UL)
	  return &dir->names[dir->names[0].year+1];
    }
    if(!dir) /* error, actually */
      return NULL;
    return &dir->names[0];
}

void freedir(struct dirmem *dir)
{
    struct fnamemem *fm, *ft;
    struct strmem *sm, *st;
    
    for(fm=dir->files;fm;fm=ft) {
	ft = fm->nxt;
	free(fm);
    }
    dir->files = NULL;
    for(sm=dir->names;sm;sm=st) {
	st = sm->nxt;
	free(sm);
    }
    dir->names = NULL;
    dir->count = 0;
    if(dir->array)
      free(dir->array);
    dir->array = NULL;
}

int toarray(struct dirmem *dir)
{
    int i,m,c = dir->count;
    struct fname **p, *q;
    struct fnamemem *fm = dir->files;

    if(!c)
      return ERR_DIR;
    p = dir->array = malloc(sizeof(*dir->array)*dir->count);
    if(!p) {
	freedir(dir);
	return ERR_MEM;
    }
    while(fm && c--) {
	m = fm->names[0].size == ~0UL?fm->names[0].year:-1;
	for(i=NSKIP-1,q=fm->names+i;i>m;i--)
	  *p++ = q--;
	fm = fm->nxt;
    }
    return ERR_OK;
}

/* parse functions */

/* I only check 1st character for binary; this should suffice for most */
/* compression formats & it keeps me from having to use a magic->prog table */
/* if fork() or exec() fails, then this'll return nothing */
/* FIXME: this should work with generic file i/o stuff */
FILE *autouncompress(FILE *f)
{
    int i;
    pid_t pid;
    int p[2];

    i = getc(f);
    if(i == EOF)
      return f;
    ungetc(i,f);
    if(isprint(i) || isspace(i))
      return f;
    if((i = pipe(p)) || (pid = fork()) < 0) {
	perror("uncompress pipe");
	fseek(f,0,SEEK_END); /* so EOF is given to caller */
	if(i) {
	    close(p[0]);
	    close(p[1]);
	}
	return f;
    }
    if(!pid) {
	FILE *pf;

	close(p[0]);
	fclose(stdout);
	close(1); /* just in case fclose() didn't */
	dup2(p[1],1);
	pf = popen(uncrpg?uncrpg:DEF_UNCRPG, "w");
	if(!pf) {
	    fprintf(stderr,"Can't execute uncompressor \"%s\"\n",uncrpg?uncrpg:DEF_UNCRPG);
	    exit(-1);
	}
	while((i = fread(buf, 1, 4096, f)) > 0)
	  if((i = fwrite(buf, i, 1, pf)) != 1)
	    break;
	pclose(pf);
	if(i < 0) {
	    perror("uncompress");
	    exit(1);
	}
	exit(0);
    }
    close(p[1]);
    fclose(f);
    return fdopen(p[0],"r");
}

int readlsfile(struct dirmem *dir, FILE *f, const char *root)
{
    char *s;
    const char *cd = "./";
    short y, m, d, hr, min;
    unsigned int md;
    unsigned long sz;
    struct fname *cur;

    f = autouncompress(f);
    /* FIXME: use manual scan instead */
    while(fscanf(f, "%hd %hd %hd %hd:%hd %o %ld %[^\n]\n", &y, &m, &d, &hr,
		 &min, &md, &sz, name) == 8) {
	cur = allocname(&dir->files);
	if(!cur)
	  return ERR_MEM;
	dir->count++;
	cur->year = y;
	cur->month = m;
	cur->day = d;
	cur->hr = hr;
	cur->min = min;
	cur->mode = md;
	cur->size = sz;
	if(S_ISLNK(md) && (s = strstr(name," -> "))) { /* soft link */
	    *s = 0;
	    cur->lnk = copyname(s+4);
	    if(!cur->lnk)
	      return ERR_MEM;
	}
	if((s = strrchr(name,'/'))) {
	    s++;
	    cur->name = copyname(s);
	    if(!cur->name)
	      return ERR_MEM;
	    if(strlen(cd) != (unsigned)(s-name) || strncmp(cd,name,s-name)) {
		cd = copynamel(name,s-name);
		if(!cd)
		  return ERR_MEM;
	    }
	} else {
	    cd = "./";
	    cur->name = copyname(name);
	    if(!cur->name)
	      return ERR_MEM;
	}
	cur->dir = cd;
	cur->root = root;
    }
    return toarray(dir);
}

int writelsfile(const char *name, struct dirmem *dir, int mustproc)
{
    struct fname **n;
    int i;
    FILE *f, *pf = NULL;

    if(crpg) {
	strcpy(buf, crpg);
	strcat(buf, " > ");
	strcat(buf, name);
	if(!(pf = f = popen(buf, "w")))
	  f = fopen(name, "w");
    } else
      f = fopen(name, "w");
    if(!f)
      return ERR_OS;
    for(n=dir->array,i=dir->count;i;i--,n++)
      if((!mustproc || !((*n)->flags & FNF_PROCESS) ||
	  ((*n)->flags & FNF_DONE)) &&
	 fprintf(f, "%4hd %2hd %02hd %02hd:%02hd %7o %12ld %s%s%s%s\n",
		 (*n)->year, (*n)->month, (*n)->day, (*n)->hr, (*n)->min,
		 (unsigned)(*n)->mode, (*n)->size, (*n)->dir, (*n)->name,
		 S_ISLNK((*n)->mode)?" -> ":"",
		 S_ISLNK((*n)->mode)?(*n)->lnk:"") <= 0)
	break;
    if(i || (pf ? pclose(f):fclose(f)))
      return ERR_OS;
    return ERR_OK;
}

static char *skipfld(char *src)
{
    while(*src && *src != ' ' && *src != '\t') src++;
    if(*src)
      *src++ = 0;
    while(*src == ' ' || *src == '\t') src++;
    return src;
}

/* parse results of ls-lR from remote site */
int parsels(struct dirmem *dir, read_func read_func, void *data, const char *cd,
	    const char *root)
{
    int i, y, hr, min;
    struct tm tm;
    mode_t m;
    char *s, *t, *curdir;
    struct fname *fn;
    int totx = 0;

    /* note: compression makes timeouts less accurate, but who cares? */
    /* FIXME: autouncompress doesn't understand read_func */
/*    f = autouncompress(f); */
    if(cd) {
	i = strlen(cd);
	if(cd[i-1] != '/') {
	    curdir = copynamel(cd,i+1);
	    if(!curdir)
	      return ERR_MEM;
	    curdir[i] = '/';
	    curdir[i+1] = 0;
	} else {
	    curdir = copynamel(cd,i);
	    if(!curdir)
	      return ERR_MEM;
	}
    } else {
	curdir = copynamel("",0);
	if(!curdir)
	  return ERR_MEM;
    }
    if(debug & DB_TRACE)
      fprintf(stderr,"Reading directory %s\n",curdir);
    while((*read_func)(data,buf,4096) == ERR_OK) {
	totx += strlen(buf);
	if(debug & DB_ALL)
	  fprintf(stderr,"Read: %s", buf);
	if(sscanf(buf,"total %d",&i) == 1)
	  continue;
	i = strlen(buf)-1;
	while(isspace(buf[i])) i--;
	buf[i+1] = 0;
	if(buf[i] == ':' && sscanf(buf,"%[^ \t:]:",name) == 1) {
	    i = strlen(name);
	    if(name[i-1] != '/')
	      name[i++] = '/';
	    curdir = copynamel(name,i);
	    if(!curdir)
	      return ERR_MEM;
	    if(debug & DB_TRACE)
	      fprintf(stderr,"Reading directory %s\n",curdir);
	    continue;
	}
	/* parse file type */
	switch(buf[0]) {
	  case 'd':
	    m = S_IFDIR;
	    break;
	  case 'l':
	    m = S_IFLNK;
	    break;
	  case 'c':
	    m = S_IFCHR;
	    break;
	  case 'b':
	    m = S_IFBLK;
	    break;
	  case 's':
	    m = S_IFSOCK;
	    break;
	  case 'p':
	    m = S_IFIFO;
	    break;
	  default:
	    m = S_IFREG;
	    break;
	}
	for(i=1,y=0;i<=9;y+=3) {
	    if(buf[i++] == 'r')
	      m += S_IRUSR>>y;
	    if(buf[i++] == 'w')
	      m += S_IWUSR>>y;
	    switch(buf[i++]) {
	      case 'x':
		m += S_IXUSR>>y;
		break;
	      case 's':
		m += S_IXUSR>>y;
	      case 'S':
		if(i == 3)
		  m += S_ISUID;
		else if(i == 6)
		  m += S_ISGID;
		break;
	      case 't':
		m += S_IXUSR>>y;
	      case 'T':
		if(i==9)
		  m += S_ISVTX;
		break;
	    }
	}
	s = skipfld(buf); /* protection */
	s = skipfld(s); /* # of links */
	s = skipfld(s); /* user */
	/* should just check here if group or not, but what the hell... */
	/* skip any other intervening fields until size + month/date */
	i = 0; /* major/minor dev# */
	do {
	    if(S_ISBLK(m) || S_ISCHR(m)) {
		i = (i&0xffff)<<16;
		i |= atoi(s)&0xffff; /* hopefully large enough */
		s = skipfld(s);
	    } else
	      i = atoi(s);
	    s = skipfld(s); /* size or minor dev# */
	    t = s;
	    if(!isdigit(*t)) {
		s = strptime(t,"%b",&tm);
		if(!s)
		  s = t;
	    }
	} while(*s && s == t);
	if(!*s)
	  continue;
	s = skipfld(s); /* month */
	t = s;
	s = skipfld(s); /* day */
	/* For older files exact time should be reflected in size */
	if(!strchr(s,':')) {
	    y = atoi(s);
	    hr = min = -1;
	} else {
	    y = curtm.tm_year+1900;
	    if(tm.tm_mon > curtm.tm_mon)
	      y--;
	    hr = atoi(s);
	    min = atoi(strchr(s,':')+1);
	}
	s = skipfld(s);
	/* skip if . or .. */
	if(S_ISDIR(m) && s[0] == '.' && (!s[1] || (s[1] == '.' && !s[2])))
	  continue;
	fn = allocname(&dir->files);
	if(!fn)
	  return ERR_MEM;
	fn->dir = curdir;
	fn->root = root;
	fn->mode = m;
	fn->size = i;
	fn->year = y;
	fn->month = tm.tm_mon+1;
	fn->day = atoi(t);
	fn->hr = hr;
	fn->min = min;
	if(S_ISLNK(m)) {
	    if((t = strstr(s," -> "))) { /* soft link */
		*t = 0;
		fn->lnk = copyname(t+4);
		if(!fn->lnk)
		  return ERR_MEM;
	    } else /* don't know link contents */
	      fn->lnk = "~*UNKNOWN_LINK*~";
	}
	fn->name = copyname(s);
	if(!fn->name)
	  return ERR_MEM;
	if(debug & DB_ALL)
	  fprintf(stderr,"Created: %02d:%02d %02d/%02d/%04d (%ld/%o) %s%s%s%s\n",
		  fn->hr,fn->min,fn->month,fn->day,fn->year,
		  fn->size,(unsigned)fn->mode,fn->dir,fn->name,
		  S_ISLNK(fn->mode)?" -> ":"",
		  S_ISLNK(fn->mode)?fn->lnk:"");
	dir->count++;
    }
    alarm(0);
    return ERR_OK;
}

/* descend local directory tree for file list */
/* Can't use ftw because ftw's brain-dead */
int readtree(struct dirmem *dir, const char *_wd)
{
    struct stat st;
    struct fname *f, *dirf;
    struct fnamemem *fm = NULL;
    struct tm *tm;
    char *root;
    int wdlen;
    unsigned long i;
    DIR *d;
    struct dirent *de;
    char *wd;

    if(!*_wd)
      _wd = "./";
    wdlen = strlen(_wd);
    if(_wd[wdlen-1] != '/') {
	wdlen++;
	wd = copynamel(_wd,wdlen);
	if(wd) {
	    wd[wdlen-1] = '/';
	    wd[wdlen] = 0;
	}
    } else
      wd = copyname(_wd);
    if(!wd)
      return ERR_MEM;
    root = wd;
    dirf = NULL;
    i = dir->count - 1;
    do {
	/* first, read in dir */
	strcpy(name,wd);
	wdlen = strlen(name);
	if(wdlen > 1)
	  wd[wdlen-1] = 0; /* strip trailing '/' for opendir */
	d = opendir(wd);
	if(wdlen > 1)
	  wd[wdlen-1] = '/'; /* replace trailing '/' */
	if(!d)
	    fprintf(stderr,"Can't scan directory %s\n",wd);
	else {
	    while((de = readdir(d))) {
		/* skip "." and ".." */
		if(de->d_name[0] == '.' && (!de->d_name[1] ||
					    (de->d_name[1] == '.' &&
					     !de->d_name[2])))
		  continue;
		strcpy(name+wdlen,de->d_name);
		if(lstat(name,&st)) {
		    fprintf(stderr,"Can't stat %s\n",name);
		    continue;
		}
		if(!(f=allocname(&dir->files)))
		  return ERR_MEM;
		f->mode = st.st_mode;
		if(S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode))
		  f->size = (major(st.st_rdev)<<16)+minor(st.st_rdev);
		else
		  f->size = st.st_size;
		tm = gmtime(&st.st_mtime);
		f->year = tm->tm_year+1900;
		f->month = tm->tm_mon+1;
		f->day = tm->tm_mday;
		f->hr = tm->tm_hour;
		f->min = tm->tm_min;
		if(!(f->name = copyname(de->d_name)))
		  return ERR_MEM;
		f->root = root;
		f->dir = wd;
		/* read link contents */
		if(S_ISLNK(st.st_mode)) {
		    int len = readlink(name,buf,4096);

		    if(len < 0) {
			perror(name);
			return ERR_OS;
		    }
		    buf[len] = 0;
		    if(!(f->lnk = copyname(buf)))
		      return ERR_MEM;
		}
		if(debug & DB_ALL)
		  fprintf(stderr,"Created: %02d:%02d %02d/%02d/%04d (%ld/%o) %s%s%s%s\n",
			  f->hr,f->min,f->month,f->day,f->year,
			  f->size,(unsigned)f->mode,f->dir,f->name,
			  S_ISLNK(f->mode)?" -> ":"",
			  S_ISLNK(f->mode)?f->lnk:"");
		dir->count++;
	    }
	    closedir(d);
	}
	/* then, find new dir to cd to */
	while(++i<dir->count) {
	    if(!dirf) {
		for(wdlen=i/NSKIP,fm=dir->files;wdlen;wdlen--)
		  fm = fm->nxt;
		dirf = &fm->names[NSKIP-1-i%NSKIP];
	    } else if(!(i % NSKIP)) {
		fm = fm->nxt;
		dirf = &fm->names[NSKIP-1];
	    } else
	      dirf--;
	    if(S_ISDIR(dirf->mode)) {
		strcpy(name,dirf->dir);
		strcat(name,dirf->name);
		wd = name+strlen(name);
		if(wd[-1] != '/') {
		    *wd++ = '/';
		    *wd = 0;
		}
		wd = copyname(name);
		if(!wd)
		  return ERR_MEM;
		wdlen = strlen(wd);
		break;
	    }
	}
    } while(i < dir->count);
    return toarray(dir);
}
lurkftp/atcron100755    700     24         260  6343365417  12242 0ustar  darkusers#!/bin/sh
if [ "$1" = "-t" ]; then
  shift; time="$1"; shift
else
  time="22:00 tomorrow"
fi

cmd=$1
shift
for x; do
  cmd="$cmd \"$x\""
done
echo "$cmd
atrun $cmd" | at $time
lurkftp/ftp.c100644    700     24       35735  6364674162  12045 0ustar  darkusers#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/uio.h> /* Linux-only */
#include <stddef.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <string.h>
#include <pwd.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <time.h>
#include "ftp.h"

#ifndef SHUT_RDWR
#define SHUT_RDWR 2
#endif

/* default timeouts */
struct to defto = {
    10, /* retrycnt */
    10, /* retrytime */
    20, /* connect */
    5, /* quit */
    10, /* xfer */
    5 /* cmd */
};

#if 0
/* old signal handling code */
/* I don't think this is needed any more, but just in case, I don't */
/* want to forget what I wrote the first time around... */
static sigjmp_buf alarmjmp;

static void doalarm(int ign)
{
    siglongjmp(alarmjmp,1);
}

static int savalid = 0;
static struct sigaction oa[2];

#ifndef SA_RESETHAND
#ifdef SA_ONESHOT
#define SA_RESETHAND SA_ONESHOT
#else
#define SA_RESETHAND 0
#endif
#endif

#define setsig(sig,n) do {\
   sigaction(sig,NULL,&sa); \
   oa[n] = sa; \
   sa.sa_handler = doalarm; \
   sa.sa_flags &= ~SA_RESTART; \
   sa.sa_flags |= SA_RESETHAND; \
   sigaction(sig,&sa,NULL); \
} while(0)

static void setalarm(void)
{
    struct sigaction sa;

    if(savalid)
      return;
    setsig(SIGALRM,0);
    setsig(SIGINT,1);
    savalid = 1;
}

#define clrsig(sig,n) do {\
  if(savalid) \
     sigaction(sig,oa+n,NULL); \
  else \
     signal(sig,SIG_DFL); \
} while(0)

static void clralarm(void)
{
    alarm(0);
    clrsig(SIGALRM,0);
    clrsig(SIGINT,1);
    savalid = 0;
}
#endif

static int timedout = 0, canto = 0;
static void (* oalarm)(int);

static void doalarm(int ign)
{
    if(canto)
      timedout = 1;
    if(oalarm != SIG_IGN && oalarm != SIG_DFL)
      oalarm(ign);
}

static void setalarm(int aval)
{
    void (* al)(int);

    alarm(0);
    al = signal(SIGALRM, doalarm);
    if(al != doalarm)
      oalarm = al;
    canto = 1;
    timedout = 0;
    alarm(aval);
}

static int clralarm(void)
{
    alarm(0);
    canto = 0;
    if(timedout) {
	timedout = 0;
	return ERR_TO;
    }
    return ERR_OK;
}
    
/* simple telnet stuff */
static int open_tn(const char *host, unsigned short port, struct tvt *tvt)
{
    struct hostent *he;
    char **p;
    unsigned long opt;

    tvt->state = TS_NONE;
    tvt->bufp = tvt->buflen = 0;
    if(!(he = gethostbyname(host)) || !he->h_addr_list[0] ||
       he->h_addrtype != AF_INET)
      return ERR_NOHOST;
    tvt->fd = socket(PF_INET, SOCK_STREAM, 0);
    tvt->state = TS_OPEN;
    if(tvt->fd < 0)
      return ERR_OS;
    opt = -1;
    setsockopt(tvt->fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
/*    tvt->addr.in.sin_len = sizeof(tvt->addr.in); */
    tvt->addr.in.sin_family = AF_INET;
    tvt->addr.in.sin_port = htons(port);
    for(p=he->h_addr_list;*p;p++) {
	memcpy((void *)&tvt->addr.in.sin_addr, *p, he->h_length);
	if(!connect(tvt->fd, (struct sockaddr *)&tvt->addr.sa,
		    sizeof(tvt->addr.sa)))
	  break;
    }
    if(!*p)
      return ERR_CONNECT;
    tvt->state = TS_CONNECT;
    return ERR_OK;
}

static void close_tn(struct tvt *tvt)
{
    switch(tvt->state) {
      case TS_CONNECT:
	shutdown(tvt->fd, SHUT_RDWR);
	tvt->state = TS_OPEN;
      case TS_OPEN:
	close(tvt->fd);
	tvt->state = TS_NONE;
    }
}

static int tn_read(struct tvt *tvt, char *buf, int len)
{
    int res, off = 0;
    fd_set fds;

    if(tvt->buflen > tvt->bufp) {
	res = tvt->buflen - tvt->bufp;
	if(res > len)
	  res = len;
	memcpy(buf, tvt->buf, res);
	len -= res;
	off += res;
	tvt->bufp += res;
	if(!len)
	  return ERR_OK;
    }
    do {
	errno = 0;
	do {
	    FD_ZERO(&fds);
	    FD_SET(tvt->fd, &fds);
	    res = select(tvt->fd + 1, &fds, NULL, NULL, NULL);
	} while(res < 0 && (errno == EINTR || errno == EAGAIN) && !timedout);
	if(res < 0)
	  break;
	errno = 0;
	res = read(tvt->fd, buf, len);
	if(res > 0) {
	    off += res;
	    len -= res;
	}
    } while(res && len > 0 && (errno == EAGAIN || errno == EINTR) && !timedout);
    if(clralarm())
      res = ERR_TO;
    else if(errno)
      res = ERR_OS;
    else
      res = off;
    return res;
}

static char *tn_readln(struct tvt *tvt, char *dst, int maxdlen)
{
    char *s;
    int i, n;

    if(tvt->state != TS_CONNECT)
      return NULL;
    if(tvt->bufp == tvt->buflen)
      tvt->bufp = tvt->buflen = 0;
    n = tvt->bufp;
    s = tvt->buf + tvt->bufp;
    do {
	for(i = tvt->buflen - n; i; i--, s++) {
	    if(dst)
	      *dst++ = *s;
	    if(*s == '\n' || (dst && --maxdlen <= 1))
	      break;
	}
	if(i) {
	    if(dst)
	      *dst = 0;
	    else
	      *s = 0;
	    i = s - tvt->buf + 1;
	    if(dst)
	      dst -= i-tvt->bufp;
	    s = tvt->buf + tvt->bufp;
	    tvt->bufp = i;
	    if(debug & DB_FTPIN)
	      fprintf(stderr, "%s\n", dst?dst:s);
	    return dst?dst:s;
	}
	if(dst) {
	    s = tvt->buf;
	    tvt->bufp = tvt->buflen = 0;
	}
	n = tvt->buflen;
	if(tvt->buflen == TVTBUF && tvt->bufp) {
	    memcpy(tvt->buf, tvt->buf + tvt->bufp, tvt->buflen - tvt->bufp);
	    tvt->buflen -= tvt->bufp;
	    n -= tvt->bufp;
	    s -= tvt->bufp;
	    tvt->bufp = 0;
	}
	if(tvt->buflen == TVTBUF) {
	    tvt->buf[TVTBUF-1] = 0; /* silently truncate */
	    tvt->bufp = tvt->buflen;
	    return tvt->buf;
	}
	errno = 0;
	i = read(tvt->fd, tvt->buf + tvt->buflen, TVTBUF - tvt->buflen);
	if(i > 0)
	  tvt->buflen += i;
    } while(i > 0 || errno == EAGAIN);
    return NULL;
}

static int rewrite(int fd, const void *buf, int buflen)
{
    int off = 0, res;
    fd_set wfd;

    do {
	do {
	    FD_ZERO(&wfd);
	    FD_SET(fd, &wfd);
	    res = select(fd + 1, NULL, &wfd, NULL, NULL);
	} while(res < 0 && (errno == EINTR || errno == EAGAIN) && !timedout);
	if(res < 0)
	  break;
	res = write(fd, buf, buflen);
	if(res > 0) {
	    off += res;
	    buflen -= res;
	}
    } while(buflen > 0 && (errno == EAGAIN || errno == EINTR) && !timedout);
    return buflen > 0?ERR_OS:ERR_OK;
}

/* This ONEWR crap has to be here because some sites don't adhere strictly
 * to the statement in rfc-959:
 *    FTP commands are "Telnet strings" terminated by the "Telnet end of
 *    line code".  The command codes themselves are alphabetic characters
 *    terminated by the character <SP> (Space) if parameters follow and
 *    Telnet-EOL otherwise.
 * Specifically, some sites hang on the PORT command if it isn't sent as
 * one transmission.
 * 
 * Since I'm tired of messing with it, I'm just going to blindly reuse
 * the receive buffer.
 */
#define ONEWR

static int tn_writeln2(struct tvt *tvt, const char *str1, const char *str2)
{
#ifndef ONEWR
    int ret;
#endif

    if(tvt->state != TS_CONNECT)
      return ERR_CONNECT;
	if(debug & DB_FTPOUT)
      fprintf(stderr, "> %s%s\n", str1?str1:"", str2?str2:"");
#ifndef ONEWR
    if(str1)
      if((ret = rewrite(tvt->fd, str1, strlen(str1))) != ERR_OK)
	return ret;
    if(str2)
      if((ret = rewrite(tvt->fd, str2, strlen(str2))) != ERR_OK)
	return ret;
    if(str1 || str2)
      rewrite(tvt->fd, "\r\n", 2);
    return ERR_OK;
#else
    if(!str1 && !str2)
      return ERR_OK;
    tvt->buflen = tvt->bufp = 0;
    if(str1)
      strcpy(tvt->buf, str1);
    else
      tvt->buf[0] = 0;
    if(str2)
      strcat(tvt->buf, str2);
    strcat(tvt->buf, "\r\n");
    return rewrite(tvt->fd, tvt->buf, strlen(tvt->buf));
#endif
}

static const struct retries {
    short resp; const char *item; size_t tosend; const char *def;
} retries[] = {
/*    {30, "USER ", offsetof(struct ftpsite, user), "anonymous"}, */
    {31, "PASS ", offsetof(struct ftpsite, pass), NULL},
    {32, "ACCT ", offsetof(struct ftpsite, acct), "ftp"}
};

static void ftp_endport(struct ftpsite *site)
{
    if(site->dfd.fd >= 0) {
	shutdown(site->dfd.fd, SHUT_RDWR);
	close(site->dfd.fd);
	site->dfd.fd = -1;
	ftp_cmd(site, NULL);
    }
    if(site->dport >= 0) {
	close(site->dport);
	site->dport = -1;
    }
}

static char pwbuf[256] = {0};

char ftp_cmd2(struct ftpsite *site, const char *cmd, const char *arg)
{
    char *s;
    short rc, rcls;

    setalarm(site->to.cmd);
    if(cmd) {
	tn_writeln2(&site->tvt, cmd, arg);
    }
    if(site->dfd.fd >= 0)
      ftp_endport(site);
    for(;;) {
	s = tn_readln(&site->tvt, NULL, 0);
	if(!s)
	  return clralarm()?ERR_TO:ERR_OS;
	site->lastresp = s;
	if(isdigit(*s) && isdigit(s[1]) && isdigit(s[2]) && !isdigit(s[3]) &&
	   (!s[3] || isspace(s[3]))) {
	    rcls = *s;
	    rc = atoi(s+1);
	    if(rcls > FTP_DONE) { /* CONT/EAGAIN/ERROR */
		const struct retries *rp;
		const char *resp;

		for(rp = retries;rp < (struct retries *)((char *)retries +
							 sizeof(retries));rp++)
		  if(rc == rp->resp && strcmp(cmd, rp->item)) {
		      resp = *(char **)((char *)site+rp->tosend);
		      if(!resp)
			resp = rp->def;
		      if(!resp && !(resp = pwbuf)[0]) {
			  struct passwd *pw;
			  struct hostent *he;
			  size_t len;

			  pw = getpwuid(getuid());
			  if(pw)
			    strncpy(pwbuf, pw->pw_name, 128);
			  len = strlen(pwbuf);
			  pwbuf[len++] = '@';
			  pwbuf[255] = 1;
			  if(gethostname(pwbuf+len, 256-len) || pwbuf[255] != 1 ||
			     !(he = gethostbyname(pwbuf+len)) ||
			     !he->h_addr_list || !he->h_addr_list[0] ||
			     he->h_addrtype != AF_INET ||
			     strlen(he->h_name) > 255 - len)
			    pwbuf[len] = 0;
			  else
			    strcpy(pwbuf+len, he->h_name);
		      }
		      if(rcls == FTP_ERROR) {
			  ftp_cmd2(site, rp->item, resp);
			  rcls = site->lastresp[0];
			  tn_writeln2(&site->tvt, cmd, arg);
		      } else
			tn_writeln2(&site->tvt, rp->item, resp);
		      rp = NULL;
		      break;
		  }
		if(!rp)
		  continue;
	    }
/*	    if(rcls == FTP_EAGAIN) {
		if(cmd)
		  tn_writeln2(&site->tvt, cmd, arg);
		else
		  return clralarm()?ERR_TO:ERR_CMD;
	    } else */
	      return clralarm()?ERR_TO:rcls>=FTP_EAGAIN?ERR_CMD:ERR_OK;
	}
    }
}

#ifdef SOCKS
extern void SOCKSinit(const char *);
#endif

void ftp_init(const char *progname)
{
#ifdef SOCKS
    SOCKSinit(progname);
#endif
}

int ftp_openconn(struct ftpsite *site)
{
    int res;

    if(site->tvt.state == TS_CONNECT)
      return ERR_OK;
    site->dfd.fd = site->dport = -1;
    setalarm(site->to.connect); /* in case name service hangs */
    open_tn(site->host, site->port ? site->port: 21, &site->tvt);
    clralarm();
    if(ftp_cmd2(site, NULL, NULL) != ERR_OK)
      return ERR_CONNECT;
    res = ftp_cmd2(site, "USER ", site->user?site->user:"anonymous"); /* automatically sends PASS & ACCT */
    if(res == ERR_OK)
      ftp_cmd(site, "SYST");
    return res;
}

void ftp_closeconn(struct ftpsite *site)
{
    setalarm(site->to.quit);
    ftp_endport(site);
    if(site->dport >= 0) {
	close(site->dport);
	site->dport = -1;
    }
    clralarm();
    ftp_cmd2(site, "QUIT", NULL);
    setalarm(site->to.quit);
    close_tn(&site->tvt);
    clralarm();
}

int ftp_port(struct ftpsite *site)
{
    struct hostent *he;
    struct utsname un;
    char buf[24];
    char **p;
    unsigned char *a, *pt;
    int opt;
    size_t lopt;
    union sa addr; 

    ftp_endport(site);
    if(uname(&un) < 0)
      return ERR_OS;
    he = gethostbyname(un.nodename);
    if(!he || he->h_addrtype != AF_INET) {
	fputs("Can't find local inet addr",stderr);
	return ERR_NOHOST;
    }
    site->dport = socket(PF_INET, SOCK_STREAM, 0);
    if(site->dport < 0)
      return ERR_OS;
    opt = -1;
    setsockopt(site->dport, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
    addr.in.sin_port = 0; /* random port */
    for(p=he->h_addr_list;*p;p++) {
	memcpy(&addr.in.sin_addr.s_addr, *p, he->h_length);
	if(!bind(site->dport, &addr.sa, sizeof(addr)))
	  break;
    }
    if(!*p || listen(site->dport, 1) < 0)
      return ERR_OS;
    lopt = sizeof(addr);
    /* ARRRGGGGGHHH!  Damn Sun and the horse they rode in on! */
    if(getsockname(site->dport, &addr.sa, (void *)&lopt) < 0) {
	close(site->dport);
	site->dport = -1;
	return ERR_OS;
    }
    a = (unsigned char *)&addr.in.sin_addr;
    pt = (unsigned char *)&addr.in.sin_port;
    sprintf(buf, "%u,%u,%u,%u,%u,%u", a[0], a[1], a[2], a[3], pt[0], pt[1]);
    if(ftp_cmd2(site, "PORT ", buf) != ERR_OK) {
	close(site->dport);
	site->dport = -1;
	return ERR_OS;
    }
    return ERR_OK;
}

static int chkdport(struct ftpsite *site)
{
    union sa sa;
    size_t salen = sizeof(sa);

    if(site->dport < 0 && site->dfd.fd < 0)
      return ERR_CONNECT;
    setalarm(site->to.xfer);
    if(site->dfd.fd < 0) {
	site->dfd.bufp = site->dfd.buflen = 0;
	/* see above comment about Sun */
	site->dfd.fd = accept(site->dport, &sa.sa, (void *)&salen);
	if(site->dfd.fd < 0) {
	    clralarm();
	    return ERR_CONNECT;
	}
	site->dfd.state = TS_CONNECT;
	close(site->dport);
	site->dport = -1;
/*	fcntl(site->dfd.fd, F_SETFL, fcntl(site->dfd.fd, F_GETFL) | O_NONBLOCK); */
    }
    return ERR_OK;
}

int ftp_read(struct ftpsite *site, char *buf, int len)
{
    int res;

    res = chkdport(site);
    if(res)
      return -res;
    return tn_read(&site->dfd, buf, len);
}

int ftp_readln(struct ftpsite *site, char *buf, int len)
{
    int res;

    res = chkdport(site);
    if(res)
      return -res;
    return tn_readln(&site->dfd, buf, len)?ERR_OK:timedout?ERR_TO:ERR_OS;
}

int ftp_write(struct ftpsite *site, const char *buf, int len)
{
    int res;

    res = chkdport(site);
    if(res)
      return -res;
    res = rewrite(site->dfd.fd, buf, len);
    return res == ERR_OK?len:-res;
}

/* pasv on one site, then port on another to cross-connect */
int ftp_crossconnect(struct ftpsite *site1, struct ftpsite *site2)
{
    unsigned char addr[4];
    char *s;
    unsigned short port;
    int i;
    char buf[24];

    if(ftp_cmd(site1, "PASV") != ERR_OK || atoi(site1->lastresp) != 2 ||
       !(s = strchr(site1->lastresp, '('))) {
	struct ftpsite *tmp;

	tmp = site1;
	site1 = site2;
	site2 = tmp;
	if(ftp_cmd(site1, "PASV") != ERR_OK || atoi(site1->lastresp) != 2 ||
	   !(s = strchr(site1->lastresp, '(')))
	  return ERR_CONNECT;
    }
    for(i=0;i<4;i++) {
	s++;
	addr[i] = strtol(s, &s, 10);
    }
    s++;
    port = strtol(s, &s, 10)*256;
    s++;
    port += strtol(s, NULL, 10);
    sprintf(buf, "%d,%d,%d,%d,%d,%d", addr[0], addr[1], addr[2], addr[3],
	    port>>8,port&0xff);
    if(ftp_cmd2(site2, "PORT ", buf) != ERR_OK)
      return ERR_CONNECT;
    return ERR_OK;
}

/* general support */
/* retrieve remote working directory; assumes connection open */
int ftp_getwd(struct ftpsite *site, char *buf)
{
    const char *s;

    if(ftp_cmd(site, "PWD") != ERR_OK && ftp_cmd(site, "XPWD") != ERR_OK)
      return ERR_CMD;
    s = site->lastresp;
    if(atoi(s) != 257)
      return ERR_CMD;
    s += 4;
    if(*s == '"') {
	while(*++s != '"')
	  *buf++ = *s;
    } else while(!isspace(*s))
	  *buf++ = *s++;
    *buf++ = 0;
    return ERR_OK;
}

/* retrieve modtime of remote file; leaves connection open */
int ftp_filetm(struct ftpsite *site, const char *name, struct tm *tm)
{
    const char *s;
    int i;

    if(ftp_openconn(site) != ERR_OK)
      return(ERR_LOGIN);
    if(ftp_cmd2(site, "MDTM ", name) != ERR_OK ||
       atoi(s = site->lastresp) != 213)
      return ERR_CMD;
    i = sscanf(s,"%*d %4d%2d%2d%2d%2d%2d",&tm->tm_year,&tm->tm_mon,&tm->tm_mday,
	       &tm->tm_hour,&tm->tm_min,&tm->tm_sec);
    if(i != 6)
      return ERR_CMD;
    tm->tm_year-=1900;
    tm->tm_mon--;
    tm->tm_isdst = -1; /* unknown */
    return ERR_OK;
}
lurkftp/lsparse.pl.gz100644    700     24       15716  6027321135  13513 0ustar  darkusers]]0lsparse.pl<{_"Dzç 	 0 ݘUOlr72Lf8h糟ypeUzuk\xˮ336˹U6s:ހC:]>FB`s;,2	<sdm{߱#fǽ#2ǾuG4Mnd2hNmVbvCmF[:4"`c!c`l>w|0%QŸz~8l65B-eCxhcq@V``-&1󦚹׷ZѪmF3NxfCӳQỲllxld$
5qfѪYm1ՑcħεMo2,=/̳clbV8?@#ߒY!,DRRNSmF]7pyVm+JhaM<4dǴ^ہ	>O46Ԡ,-fm#9u	,}Yh:Vٵ‚7fNM5aSdw$`gGG3ۛ:l/Pā%,A=ߛ6vٱٻ1Ѧ٬7	D~pǀ$g<e>XA@v8	<-Dc]f(e]gE\;C)ϵ{dR9fXF`c6lD|O:C[m9Nq#4>
A&f
`.HL4m1;cI$xN0p]ǃ=:(bO1)m;#.;<4{S0`}? 0VF,	K Ӳ|r6߲<pͣf݁.3ˀV؞EcLmv(R*&f{sdQ@l`{vRẄTP	bvfQ/W*Krۡ#ANO^$}豊hT	AHNX+27}DN<P)t
a>.d>{N߉-rR&8d)bpo1[Zr(I=Y&^/ͨ]<w~
ox#K]b=5]XPd]RyiA5=྆0X6F/}V$bGKV+b;+~0%겗܁`<20
c`
M<K~_Ȣ
	eD)a;&pf;C'v}DhpDA>{6*.G̼F+RpLP[ lj-:f QIsL0Bs_70FشGPw\I;q{W`b!]Y+%@L8"[=UXM`PlK,oLPZh
}0{8%@9P\Ԍ$aakP=
|FpbId~Ȑ(u)&S߁֐É.``-jjP`O=][x2m3`)L+\;cƆcYcAd4 dbxCE4{=ו$ZG_,j%Ӂ|m}ʯ==9`'>eb(hGi{IP/"Xek&nf`.sT
&-GA'9GA001hMabnT1&Mi1=VeCHE	XW*0zw?bu^ICDRE钡)	&8eٸ\BĶ`+%d%\$dE~Zx^.Yv^8un
X2# "z!πl<M=AV|ZP(ʢXL8k{xݞ$0\	1Ȓ1Om4wA抵/iLZjpE})&$+ny;oFw\-vf􏗡r~;W2xڍYqrY!żu݀fHVVFbV*
(֐7'(g˱*Habh8P!$wȸkQ2v\9ƅGWg|#ad#)e>	Iͩ9(n"?1VMk"DJ".at'F{tXbML<٦G'n_֨GEWMKQ1&*dD(Uku-SjJ,ZhBؠV[BbU@%7LDuXd9]
֩)7?.MrUBF'UG%6T Ig"XF

8E"$@@`>GHcrI u:g+Oջ..~OL%EqiB+Էo8?Qή#Xzl	ljDE'{ǭ
Results 1 - 1
Help - FTP Sites List - Software Dir.
Searching half a billion files worldwide
© 1997-2009 MARUHN Internet Solutions