/* $Id: fmirror.c,v 1.16 2000/03/09 06:52:52 finnag Exp $
*
* fmirror - A mirror program designed to use little memory, but still
* be useful.
*/
/* Copyright © 1995-2000 Finn Arne Gangstad <finnag@fast.no> and
* Tor Egge <tegge@pvv.ntnu.no>
*
* This file is part of FMIRROR.
*
* FMIRROR is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* FMIRROR is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FMIRROR; see the file COPYING. If not, write to
* the Free Software Foundation, 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Please send bug reports and suggestions to to <finnag@fast.no>
*
* To report errors, it is usually nice if you do
*
* fmirror -d 12 -f configfile
*
* and mail the interesting parts of the output (remember to remove
* any passwords and usernames)
*
*/
/* TODO:
* - Use pcre regex library (wait for 0.9, somewhat incompatible regex syntax)
* - enable local compressing of files
* - add command-line option to enable/disable reget
* - actually implement reget
* - make better examples
* - update doc
* - save the "to-do" file to a named file instead, so work can be resumed,
* and/or let fmirror resume on its own if it detects a resumable problem
* - continuing interrupted transfers
* - reuse a port if retr fails (being nice to ftp-server)
* - send next file-get-command when 226 is received? have to use select
* and be able to read multiple files then.
* - make a configuration option to only make symlinks that point to
* an existing file/dir/whatever
* - a "talk-only" mode, just saying what it would do rather than doing it
* */
#define _GNU_SOURCE
#include "config.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <errno.h>
#include <time.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <ctype.h>
#include <regex.h>
#include <pwd.h>
#include <dirent.h>
#include <signal.h>
#include <math.h>
#define MY_MAX(a,b) ((a) > (b) ? (a) : (b))
#define LOG(level,type,arg) \
((level) <= MY_MAX(loglevel, log_ ## type) ? logit arg : -1)
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
#ifndef EXIT_FAILURE
#define EXIT_FAILURE 1
#endif
#ifndef EXIT_SUCCESS
#define EXIT_SUCCESS 0
#endif
/* Some architectures need this as well, sunos 4.1.3 for example: */
#if (!HAVE_OPTARG)
extern char *optarg;
#endif
#if (!HAVE_OPTIND)
extern int optind
#endif
#if (!HAVE_OPTERR)
extern int opterr;
#endif
#if (!HAVE_STRERROR)
static char *strerror(int);
static char *
strerror(int e)
{
extern int sys_nerr;
extern char *sys_errlist[];
static char buf[50];
if (e > 0 && e < sys_nerr)
return sys_errlist[e];
sprintf(buf, "Unknown error %d\n", e);
return buf;
}
#endif
#define MAX_LINE_LEN 512 /* Max length of a line in the config file */
static FILE *in_file, *out_file;
static int loglevel = 3; /* Default logging level is 3 */
#define FTP_CONTROL_PORT "21"
static double totalsum = 0.0;
static double totalbytes = 0.0;
static int af = PF_UNSPEC;
static char *localdir = NULL;
static char *remotedir = NULL;
static char *host = NULL;
static char *port = NULL;
static char *username = NULL;
static char *password = NULL;
static char *dircmd = NULL;
static int dirmode;
static int timefuzz = 59;
static int remotetz = -1;
static int file_and_mask = -1;
static int file_or_mask = -1;
int compressed = 0;
static char *default_decompressor = "gzip";
static char *default_decompressor_opt = "-dc";
static char *pidfile = NULL;
static char *decompressor = NULL;
static char *decompressor_opt = NULL;
static char *default_dircmd = "LIST -lRa";
static char *dircmd_tz = "LIST -ltra";
static int thisyear, thismonth, thisday;
static int Per[10][256];
static int passive = 0;
static time_t current_time;
static int nodel = 0;
static int try_reget = 0; /* default is don't try to reget */
static int keep_newer = 0; /* don't download if a newer version exists */
static int reset_times = 0; /* reset access and mod times to remote */
static int use_mdtm = 1; /* default is use MDTM if we can find remote TZ */
struct in6_addr dst6_addr; /* IPv6 destination address.
for EPSV handling */
size_t dst_addrlen;
struct sockaddr_storage localaddr; /* local IP address, used for PORT/EPRT
command construction */
static volatile sig_atomic_t alarmed = 0;
static int input_timeout = 240;
static int connect_timeout = 120;
static int connect_retries = 3;
static int reconnect_timeout = 120;
static int reconnect_retries = 15;
static char whitespace[] = " \f\t\v";
static char rcsid[] = "$Id: fmirror.c,v 1.16 2000/03/09 06:52:52 finnag Exp $";
static char usage[] =
" -4 - use only IPv4 connections\n"
" -6 - use only IPv6 connections\n"
" -A <mask> - binary and remote file permissions with <mask> [0111]\n"
" -O <mask> - binary or remote file permissions with <mask> [0444]\n"
" -C <whatever> - parse <whatever> as if it was a line in a config-file\n"
" -N - don't delete local files [not on by default]\n"
" -c dircommand (sent to ftp-server) [LIST -lRa]\n"
" -D dircommand when finding timezone [LIST -ltra]\n"
" -d debug-level (0 = fatal only, 10 = spam) [3]\n"
" -e <exclude-opts> <exclude-pattern>\n"
" -i <include-opts> <include-pattern>\n"
" -f config-file\n"
" -F <pidfile> - Save pid of fmirror in the file <pidfile>\n"
" -h This help page\n"
" -k Don't download a file if a newer version already exists\n"
" -l Local base-directory\n"
" -M <n> - use MDTM extension for modified times\n"
" -m <dirmode> - create local directories with permission <dirmode> [0755]\n"
" -P port [21]\n"
" -p Password (normally email address), [username@]\n"
" -r Remote base-directory\n"
" -R Reset the access & modification times of a newer file\n"
" -s Ftp-server address\n"
" -S Passive mode\n"
" -T <n> - remote timezone assumed to be <n> minutes off GMT\n"
" -t <n> - assume timestamps that differ with up to <n> seconds are equal\n"
" -u Username [anonymous]\n"
" -v Print version\n"
" -V verbosity level [1 suitable for cron jobs, 3 for interactive]\n"
" -x Decompressor (executable name _only_) [gzip]\n"
" -o Decompressor option [-dc]\n"
" -z Compressed dirlist (run decompressor on file-list input)\n\n";
struct mirror_info {
char *hostname;
char *path;
int af; /* force to use this address family */
char *port;
};
struct connection {
int fd;
struct sockaddr_storage sad;
struct sockaddr_storage dest; /* in network order */
unsigned short dport; /* dest port - for printing only, in HOST order */
int af;
FILE *file;
};
struct timeinfo {
int year;
int month;
int day;
int hour;
int minute;
int second;
int gmtoff; /* offset from gmt, in seconds */
};
struct parse_info {
int perm;
uint size;
time_t date;
struct timeinfo t;
char *filename;
char *fileonlyname; /* points into filename somewhere */
char *linkname;
};
enum action {
A_INCL = (1 << 0),
A_EXCL = (1 << 1),
A_NODEL = (1 << 2)
};
struct reg_list {
int fullpath;
int normal;
int act;
char *regex_text;
regex_t *regex;
struct reg_list *next;
};
static struct reg_list *first_regex = NULL;
static struct reg_list *last_regex = NULL;
#ifndef __GNUC__
#define __attribute__(x)
#endif
static int parse_args(int argc, char *const *argv);
static void do_mirror(struct mirror_info *);
static int do_connect(const char *, const char *);
static void clog(const char *);
static int logit(const char *, ...) __attribute__((format(printf, 1, 2)));
static int handle_input(const char **msg);
static int cmd(const char *, ...);
static void get_filelist(void);
static const char *make_active_args(struct sockaddr *);
static const char *make_eprt_args(struct sockaddr *);
static const char *make_port_args(struct sockaddr_in *);
static const char *make_port_verb(int portaf);
static struct connection *new_connection(void);
static void handle_dir_listing(struct connection *c);
static int parse_filename(char *, char *, struct parse_info *);
static struct timeinfo *Date2Min(const char *);
static void get_file(struct parse_info *);
static void do_getfiles(void);
static int do_get_file(char *, time_t, uint, uint);
static int handle_ftp_transfer(struct connection *, char *, time_t, uint, uint);
static int Perm2Mod(char *);
static void init_Perm2Mod(void);
static double time_since_last(void);
static double time_since_start(void);
static void read_config_file(const char *);
static char *smatch(const char *, char *);
static int add_regex(enum action, char *);
static int skip_file(const char *filename, const char *fileonlyname);
static int time_dif(time_t, time_t);
static int size_dif(unsigned, unsigned);
static int mode_dif(int, int);
static int recursive_unlink(const char *filename, struct stat *st);
static void maybe_delete_dir(const char *pathname);
static void delete_delayed_dirs(void);
static char *strip_newline(char *s);
static int supermkdir(char *);
static int mkdir_mode(const char *, int mode);
static int make_new_dir(const char *name);
static time_t make_ctime_from_ti(struct timeinfo *ti);
static int is_newer(time_t, time_t);
static void set_time(const char *name, time_t);
static time_t utc_mktime(struct tm *tm);
static int sig_permanent(int sig, RETSIGTYPE (*handler)(int));
static int make_connected_socket(struct sockaddr *sad, size_t addrlen);
static void init_active(struct connection *c);
static int init_passive(struct connection *c);
static int init_passive_ipv6(struct connection *c);
static int init_passive_ipv4(struct connection *c);
static int make_passive_connection(struct connection *c);
static int accept_connection(int fd, struct sockaddr *sad);
static int find_timezone(char *);
void log_conn_from(struct sockaddr *sa);
static FILE *tempfile;
static FILE *delayed_delfile;
static double time_since_begin_of_file = 0.0;
static double kb_sec_overall = 2.5; /* self corrects after 1st file */
static double kb_sec_lastfile = 2.5;
static int log_addrinfo = 1; /* where to connect for next file */
static int log_bug = 3;
static int log_cmd = 1;
static int log_cmdfail = 1;
static int log_connect = 0; /* data session connect */
static int log_ctrl_connect = 0; /* control connection connect */
static int log_delsymlink = 1;
static int log_dlexcuse = 1;
static int log_eta = -1;
static int log_exec = 1; /* log start of decompressor */
static int log_failure = 3;
static int log_findtz = 0; /* find remote timezone */
static int log_finished = 0;
static int log_getfile = 1;
static int log_hashes = -1;
static int log_inerror = 1; /* error in input (config-file etc) */
static int log_mkdir = 1; /* log dirs that are created */
static int log_mksymlink = 1;
static int log_other = 0;
static int log_protoerr = 1; /* protocol errors */
static int log_regmatch = 0; /* log regex matches */
static int log_reply = 1; /* control link traffic FROM ftp server */
static int log_rmdir = 1;
static int log_rmfile = 1;
static int log_setopt = 0; /* log options as they are set */
static int log_timestamp = 1; /* print timestamp in front og log */
static int log_totsize = 1; /* print how many bytes to mirror */
static int log_weird = 1; /* files with weird permissions remotely */
static int log_willget = 1;
static int log_willmakedir = 1;
static int verbosity = 3; /* 1 suitable for cron jobs, 3 for interactive */
static int
time_dif(time_t tcurrent, time_t tnew)
{
/* Return 1 if the time stamps are different enough to matter, 0
* if not. */
double d = difftime(tcurrent, tnew);
double ad = fabs(d);
if (ad <= timefuzz ||
/* one hour within minute accuracy */
(((int)ad > 3540) && ((int)ad < 3660))) {
return 0;
} else {
time_t now = current_time;
double nd = difftime(now, tnew);
/* If date is older than 60 days, allow up to 1 day + DST - 1 second
* difference between the timestamps, since "ls -l" only has
* day resolution for old files */
return (nd < (60 * 24 * 3600) || (ad >= 25 * 3600));
}
}
static int
size_dif(unsigned a, unsigned b)
{
return a != b;
}
static int
mode_dif(int a, int b)
{
/* Return true if the file masks a and b differ according to the
* current configuration. */
return
((a & file_and_mask) | file_or_mask) !=
((b & file_and_mask) | file_or_mask);
}
static struct reg_list *
get_file_reg_ent(const char *filename, const char *fileonlyname)
{
struct reg_list *r = first_regex;
while (r) {
if (!r->normal ^
!regexec(r->regex, r->fullpath ? filename : fileonlyname,
0, NULL, 0)) {
LOG(12, other, ("<%s> matches %s regex <%s>",
r->fullpath ? filename : fileonlyname,
r->normal ? "normal" : "inverted",
r->regex_text));
return r;
}
LOG(12, other, ("<%s> no match for %s regex %s",
r->fullpath ? filename : fileonlyname,
r->normal ? "normal" : "inverted",
r->regex_text));
r = r->next;
}
return r;
}
static int
get_file_act(const char *filename, const char *fileonlyname)
{
struct reg_list *r = get_file_reg_ent(filename, fileonlyname);
return r ? r->act : 0;
}
static int
skip_file(const char *filename, const char *fileonlyname)
{
/* Matches the filename in parse_info against the global regex
* include and exlude-lists, to see if this file should be
* skipped. Returns 1 if the file should be skipped, 0 otherwise. */
struct reg_list *r = get_file_reg_ent(filename, fileonlyname);
const char *invtext = (r && r->normal) ? "" : "inversion of ";
if (!r) {
LOG(7, other, ("<%s> does not match any regex", filename));
return 0;
}
if (r->act & A_INCL) {
LOG(5, regmatch, ("include: <%s> (mathced %s<%s>)", filename,
invtext, r->regex_text));
return 0;
} else if (r->act & A_EXCL) {
LOG(5, regmatch,("exclude: <%s> (matched %s<%s>)", filename,
invtext, r->regex_text));
return 1;
} else {
LOG(0, bug, ("BUG: Bogus regex act-val = %d.", r->act));
}
return 0;
}
static RETSIGTYPE
alarm_handler(int sig)
{
(void)sig;
alarmed = 1;
#ifdef SIGHANDLERS_RETURN_SOMETHING
return (RETSIGTYPE)0;
#endif
}
#define SLO_IFUNSET(x,y) do { if (log_ ## x < 0) log_ ## x = y; } while(0)
static void set_def_log_opt(void) {
if (loglevel == 3 && verbosity == 3) {
SLO_IFUNSET(eta, 1);
SLO_IFUNSET(hashes, 0);
return;
}
if (loglevel == 3 && verbosity > 1) {
SLO_IFUNSET(eta, 0);
SLO_IFUNSET(hashes, 1);
return;
}
if (loglevel > 5 && loglevel < 18) {
SLO_IFUNSET(eta, 0);
SLO_IFUNSET(hashes, 1024);
return;
}
SLO_IFUNSET(eta, 0);
SLO_IFUNSET(hashes, 0);
if (loglevel < 0) loglevel = 0;
}
int
main(int argc, char *const *argv)
{
struct mirror_info mi;
time_t timestamp;
struct tm *tm;
(void) rcsid; /* One warning less */
current_time = time(0);
umask(022);
if (!(tempfile = tmpfile())) {
LOG(0, failure, ("-- ERROR -- Failed to create tempfile, aborting: %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
if (!(delayed_delfile = tmpfile())) {
LOG(0, failure,
("-- ERROR -- Failed to create delayed delfile, aborting: %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
sig_permanent(SIGALRM, alarm_handler);
sig_permanent(SIGPIPE, SIG_IGN);
time_since_start(); /* Initialize time_since_start */
init_Perm2Mod(); /* Initialize Perm2Mod */
timestamp = time(0);
tm = gmtime(×tamp);
thisyear = tm->tm_year + 1900;
thismonth = tm->tm_mon;
thisday = tm->tm_mday;
if (!parse_args(argc, argv)) {
printf("Try %s -h for more information.\n", argv[0]);
exit(EXIT_FAILURE);
}
if (!localdir)
fprintf(stderr, "%s: localdir not set\n", argv[0]);
if (!remotedir)
fprintf(stderr, "%s: remotedir not set\n", argv[0]);
if (!host)
fprintf(stderr, "%s: host not set\n", argv[0]);
if (!(localdir && remotedir && host)) {
fprintf(stderr, "\n%s -h for more information.\n\n", argv[0]);
exit(EXIT_FAILURE);
}
if (!dirmode)
dirmode = 0755;
if (file_and_mask == -1)
file_and_mask = 0111; /* default: only bother about executable bit */
if (file_or_mask == -1)
file_or_mask = 0444; /* default: always make files readable */
if (chdir(localdir) && (make_new_dir(localdir), chdir(localdir))) {
LOG(0, failure, ("-- ERROR -- Problems changing to localdir (%s): %s",
localdir, strerror(errno)));
exit(EXIT_FAILURE);
}
if (pidfile) {
FILE *f = fopen(pidfile, "w");
if (f) {
fprintf(f, "%d\n", (int)getpid());
fclose(f);
} else {
LOG(0, failure, ("Error creating pidfile %s: %s", pidfile,
strerror(errno)));
}
}
set_def_log_opt();
mi.hostname = host;
mi.path = remotedir;
mi.port = port ? port : FTP_CONTROL_PORT;
mi.af = af;
LOG(1, finished, ("%s @ %s -> %s", remotedir,
mi.hostname, localdir));
do_mirror(&mi);
LOG(1, finished, ("Mirror finished."));
exit(EXIT_SUCCESS);
}
static double
time_since_last(void)
{
/* Returns the number of seconds since the last time this function
* was called. */
static struct timeval t1 = {0, 0};
struct timeval t;
double diff;
if (t1.tv_sec == 0) {
gettimeofday(&t1, NULL);
return 0.01;
}
gettimeofday(&t, NULL);
diff = (t.tv_sec - t1.tv_sec) + (t.tv_usec - t1.tv_usec) * 0.000001;
t1 = t;
if (diff < 0.0000001)
diff = 0.0000001;
return diff;
}
static double
time_since_start(void)
{
/* Returns the number of seconds since the first time this
* function was called */
static struct timeval t1 = {0, 0};
struct timeval t;
double ret;
if (t1.tv_sec == 0) {
gettimeofday(&t1, NULL);
return 0.000001;
}
gettimeofday(&t, NULL);
ret = (t.tv_sec - t1.tv_sec) + (t.tv_usec - t1.tv_usec) * 0.000001;
if (ret <= 0)
ret = 0.000001; /* should not happen */
return ret;
}
static void
get_file(struct parse_info *p)
{
LOG(7, other, ("(GETFILE) %s", p->filename));
totalbytes += (double) p->size;
fprintf(tempfile, "%u %u %u RETR %s\n", (uint)p->date,
p->perm & 0777, p->size, p->filename);
}
static int
cmd(const char *format, ...)
{
/* Sends a command to the ftp server */
int l;
char buffer[3000];
va_list args;
va_start(args, format);
vsprintf(buffer, format, args);
l = fprintf(out_file, "%s\r\n", buffer);
fflush(out_file);
buffer[l - 1] = 0;
LOG(6, cmd, ("---> %s", buffer));
va_end(args);
return l;
}
static int
get_input(char *line, int len)
{
char c;
int val;
do {
char *addr;
alarm(input_timeout);
alarmed = 0;
addr = fgets(line, len, in_file);
alarm(0);
if (!addr) {
if (errno == EINTR || alarmed ) {
LOG(0, failure, ("Timeout waiting for reply. Aborting."));
exit(EXIT_FAILURE);
}
LOG(0, failure, ("Lost link to server on control connection: %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
strip_newline(line);
LOG(6, reply, (": %s", line));
} while (line[3] == '-' || isspace((unsigned char)line[0]));
c = line[3];
line[3] = 0;
val = strtol(line, (char **)NULL, 0);
line[3] = c;
return val;
}
static int
handle_input(const char **msg)
{
/* Reads the answer from the ftp-server, and returns the error
* code as an integer */
static char line[PATH_MAX + 80];
if (msg)
*msg = line;
return get_input(line, sizeof(line));
}
static int
do_connect(const char *addr, const char *dport)
{
/* Connects to a specified host and port, and returns a
* filedescriptor for the connection. */
struct addrinfo hints, *res, *res0;
int error;
int sockfd = 0;
char msg[32];
char numaddr[64];
int protocol = 0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = af; /* use global variable af */
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(addr, dport, &hints, &res0);
if (error) {
LOG(0, failure,
("-- ERROR -- getaddrinfo : %s", gai_strerror(errno)));
exit(1);
/*NOTREACHED*/
}
for (res = res0; res; res = res->ai_next) {
switch(res->ai_family) {
case AF_INET:
inet_ntop(res->ai_family,
&((struct sockaddr_in *)res->ai_addr)->sin_addr,
numaddr, sizeof(numaddr));
break;
case AF_INET6:
inet_ntop(res->ai_family,
&((struct sockaddr_in6 *)res->ai_addr)->sin6_addr,
numaddr, sizeof(numaddr));
break;
}
LOG(4, ctrl_connect, ("Attempting to connect to %s", numaddr));
sockfd = make_connected_socket(res->ai_addr, res->ai_addrlen);
if (sockfd == -1) {
LOG(0, failure,
("Too many connect attempts. Giving up connecting to %s", numaddr));
continue;
}
protocol = res->ai_protocol;
/* use global variable to export addrlen
this variable will be used by make_passive_connection() */
dst_addrlen = res->ai_addrlen;
/* set global variable af to acquired address family.
this variable will be used by new_connection() */
af = res->ai_family;
switch (af) {
case AF_INET:
snprintf(msg, sizeof(msg), "Connected [IPv4]");
break;
case AF_INET6:
/* set global variable dst_addr to res0 if af is IPv6
we will need it in EPSV handling */
dst6_addr = ((struct sockaddr_in6 *)(res->ai_addr))->sin6_addr;
snprintf(msg, sizeof(msg), "Connected [IPv6]");
break;
default:
LOG(0, failure,
("do_connect: wrong AF specification"));
break;
}
LOG(3, ctrl_connect, (msg));
/* sucessfully connected, break the loop */
break;
} /* for */
if (sockfd == -1) {
LOG(0, failure, ("Was not able to connect to any address. Giving up."));
return -1;
}
#if (defined(IPTOS_LOWDELAY) && defined(IP_TOS))
{
int opt = IPTOS_LOWDELAY;
if (af == AF_INET) /* use IP_TOS only for IPv4 */
if (setsockopt(sockfd, protocol, IP_TOS, (char *)&opt, sizeof(opt)))
LOG(0, failure,
("-- ERROR -- setsockopt LOWDELAY : %s", strerror(errno)));
}
#endif
freeaddrinfo(res0);
return sockfd;
}
static int
success(void)
{
const char *msg;
int result = handle_input(&msg);
if (result < 200 || result > 299) {
LOG(1, cmdfail,
("Operation failed: %s", msg));
return 0;
}
return 1;
}
static void
do_mirror(struct mirror_info *mi)
{
int control_fd;
const char *msg;
int err;
goto first;
do {
LOG(4, ctrl_connect, ("Reconnect timeout - sleeping %d secs",
reconnect_timeout));
sleep(reconnect_timeout);
first:
LOG(3, ctrl_connect, ("Connecting to %s...", mi->hostname));
control_fd = do_connect(mi->hostname, mi->port);
if (control_fd >= 0) {
LOG(3, ctrl_connect, ("Acquired local IP address."));
if (!(in_file = fdopen(control_fd, "r"))) {
LOG(0, failure, ("-- ERROR -- fdopen on in_file failed : %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
if (!(out_file = fdopen(control_fd, "w"))) {
LOG(0, failure, ("-- ERROR -- fdopen on out_file failed : %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
handle_input(NULL);
if (!username) {
username = "anonymous";
}
cmd("USER %s", username);
err = handle_input(&msg);
if (err >= 400) {
close(control_fd);
LOG(0, failure, ("Could not log in as '%s': %s [%s]",
username, msg, reconnect_retries > 0
? "retrying" : "giving up"));
}
} else {
err = 401;
}
} while (err >= 400 && reconnect_retries-- > 0);
if (err >= 400)
exit(EXIT_FAILURE);
if (password || err >= 300) {
/* Don't send password if the ftp server didn't ask for it and
* it isn't set. */
char buffer[128];
if (!password) {
struct passwd *p = getpwuid(getuid());
sprintf(buffer, "%s@", p->pw_name);
}
cmd("PASS %s", password ? password : buffer);
if (!success())
exit(EXIT_FAILURE);
}
if (MY_MAX(loglevel, log_reply) >= 6) {
/* For debugging purposes - only send if answer is logged */
cmd("SYST");
handle_input(NULL);
}
if (use_mdtm) {
int x;
cmd("%s", "MDTM foo");
x = handle_input(NULL);
if (x == 500 || x == 502) {
use_mdtm = 0;
LOG(2, findtz, ("Remote does not support MDTM, turned off."));
} else {
/* only try to guess timezone if no -T option */
if ((remotetz == -1) && !find_timezone(mi->path)) {
/*
* if use_mdtm > 1; assume remote TZ = GMT
* and force use of MDTM.
*/
if (use_mdtm == 1) {
use_mdtm = 0;
LOG(2, findtz, ("No remote TZ, turning off MDTM."));
} else {
LOG(2, findtz, ("No remote TZ, assuming GMT"));
}
}
}
}
if (remotetz == -1) remotetz = 0;
cmd("CWD %s", mi->path);
if (!success()) {
LOG(0, failure, ("Could not CWD to %s, aborting", mi->path));
exit(EXIT_FAILURE);
}
get_filelist();
if (fseek(tempfile, 0L, SEEK_SET)) {
LOG(0, failure, ("-- ERROR -- Problems with fseek tempfile : %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
cmd("TYPE I");
if (!success()) {
LOG(0, cmdfail, ("TYPE I failed, aborting"));
exit(EXIT_FAILURE);
}
time_since_last(); /* Initialize time_since_last */
if (verbosity >= 2 && totalbytes > 0.5)
LOG(1, totsize, ("%.0f total bytes targeted for mirror", totalbytes));
do_getfiles();
cmd("QUIT");
handle_input(NULL);
fclose(in_file);
fclose(out_file);
close(control_fd);
delete_delayed_dirs();
}
static int
parse_mdtm(const char *msg, struct timeinfo *mdtime)
{
struct timeinfo d;
char buf[5];
/* this code is not Y10K compliant. */
if (strlen(msg) != 18) return 0;
memset(&d, 0, sizeof(d));
strncpy(buf, msg+4, 4); buf[4] = 0;
d.year = strtol(buf, NULL, 10);
strncpy(buf, msg+8, 2); buf[2] = 0;
d.month = strtol(buf, NULL, 10) - 1;
strncpy(buf, msg+10, 2); buf[2] = 0;
d.day = strtol(buf, NULL, 10);
strncpy(buf, msg+12, 2); buf[2] = 0;
d.hour = strtol(buf, NULL, 10);
strncpy(buf, msg+14, 2); buf[2] = 0;
d.minute = strtol(buf, NULL, 10);
strncpy(buf, msg+16, 2); buf[2] = 0;
d.second = strtol(buf, NULL, 10);
LOG(12, other, ("mdtm parsed %4d-%02d-%02d %02d:%02d:%02d",
d.year, d.month + 1, d.day,
d.hour, d.minute, d.second));
*mdtime = d;
return 1;
}
static int
find_timezone(char *gotodir)
{
struct connection *c;
int x;
const char *msg;
struct sockaddr_storage sad;
FILE *stream;
char line[PATH_MAX + 80];
int fd;
struct sample_dirs_list {
struct sample_dirs_list *next;
struct parse_info *pinfo;
} sample_dirs, *nextsdl;
struct parse_info p, sample_fil, *pp;
int foundtz = 0;
char numaddr[64];
sample_fil.filename=0;
memset(&sample_dirs, 0, sizeof(sample_dirs));
if (gotodir) {
cmd("CWD %s", gotodir);
if (!success() && strcmp(gotodir, "/") != 0) {
LOG(4, findtz, ("Cannot CWD %s for timezone", gotodir));
gotodir = 0;
goto dirdone;
}
}
cmd("TYPE A");
if (!success())
exit(EXIT_FAILURE);
c = new_connection();
if (passive) {
if (init_passive(c)) {
LOG(0, failure, ("Passive initialisation failed, aborting."));
exit(EXIT_FAILURE);
}
} else {
init_active(c);
cmd("%s %s", make_port_verb(c->af),
make_active_args((struct sockaddr *)&c->sad));
if(!success()) {
close(c->fd);
free(c);
LOG(0, cmdfail, ("Port command failed, aborting"));
exit(EXIT_FAILURE);
}
}
cmd("%s", dircmd_tz);
if (passive) {
if (make_passive_connection(c)) {
LOG(0, failure, ("Passive conection failed, aborting."));
exit(EXIT_FAILURE);
}
switch (c->af) {
case AF_INET:
inet_ntop(c->af, &((struct sockaddr_in *)&c->dest)->sin_addr,
numaddr, sizeof(numaddr));
break;
case AF_INET6:
inet_ntop(c->af, &((struct sockaddr_in6 *)&c->dest)->sin6_addr,
numaddr, sizeof(numaddr));
break;
}
LOG(5, connect, ("hdl: Connected passively to %s port %d",
numaddr,
c->dport));
}
x = handle_input(&msg);
if (x >= 400) {
LOG(0, cmdfail, ("Dir listing failed, exiting. (%s)", msg));
close(c->fd);
free(c);
exit(EXIT_FAILURE);
}
if (passive) {
fd = c->fd;
} else {
LOG(5, other, ("Dir list: Waiting for connection..."));
fd = accept_connection(c->fd, (struct sockaddr *)&sad);
if (fd == -1) {
LOG(0, failure, ("accept() : %s", errno == EINTR
? "Operation timed out" : strerror(errno)));
exit(EXIT_FAILURE);
}
log_conn_from((struct sockaddr *)&sad);
}
if (!(stream = fdopen(fd, "r"))) {
LOG(0, failure, ("fdopen(%d, \"r\"): %s", fd, strerror(errno)));
exit(EXIT_FAILURE);
}
alarmed = 0;
alarm(input_timeout);
while (fgets(line, sizeof(line), stream)) {
/* found one? skip parsing rest of dir */
if (sample_fil.filename) continue;
strip_newline(line);
if (parse_filename(".", line, &p)) {
if (S_ISDIR(p.perm)) {
nextsdl = malloc(sizeof(*nextsdl));
pp = malloc(sizeof(*pp));
if (!nextsdl || !pp) {
LOG(0, failure, ("malloc failed")); exit(EXIT_FAILURE);
}
*nextsdl = sample_dirs;
*pp = p;
sample_dirs.next = nextsdl;
sample_dirs.pinfo = pp;
}
if (S_ISREG(p.perm) && (p.t.hour || p.t.minute)) {
sample_fil = p;
LOG(5, findtz, ("Find timezone from %s time %d-%d-%d %02d:%02d",
p.filename, p.t.year, p.t.month+1, p.t.day,
p.t.hour, p.t.minute));
}
}
}
if (alarmed) {
LOG(0, failure, ("Timeout reading directoriy in find_timezone. aborting."));
exit(EXIT_FAILURE);
}
fclose(stream);
close(fd);
close(c->fd);
free(c);
if (!success()) {
LOG(0, cmdfail,
("Unexpected problem after getting dir listing. exiting."));
exit(EXIT_FAILURE);
}
if (!sample_fil.filename && !sample_dirs.pinfo) {
LOG(5, findtz, ("No appropriate file or dir found for timezone"));
goto dirdone;
}
if (!sample_fil.filename) {
int anyok = 0;
LOG(6, findtz, ("No appropriate file found for timezone"));
/* try any directory from here */
nextsdl = &sample_dirs;
while (nextsdl) {
if (!nextsdl->pinfo) break;
LOG(3, findtz, ("find timezone: try dir %s/%s/",
gotodir ? gotodir : "",
nextsdl->pinfo->filename));
anyok = find_timezone(nextsdl->pinfo->filename);
if (anyok) break;
nextsdl = nextsdl->next;
}
/* free the memory used */
nextsdl = &sample_dirs;
while (sample_dirs.pinfo) {
free(sample_dirs.pinfo);
nextsdl = sample_dirs.next;
if (nextsdl) {
sample_dirs = *nextsdl;
free(nextsdl);
} else {
break;
}
}
foundtz = anyok;
goto dirdone;
} else {
cmd("MDTM %s", sample_fil.filename);
x = handle_input(&msg);
if (x >= 200 && x <= 299) {
struct timeinfo d;
time_t tltz, tgmt;
LOG(4, findtz, ("mdtm %s successful (%s)",
sample_fil.filename, msg));
if (!parse_mdtm(msg, &d)) goto dirdone;
p = sample_fil;
LOG(7, findtz, ("Timezone diff: compare "
"%02d-%02d %02d:%02d and %02d-%02d %02d:%02d",
d.month+1, d.day, d.hour, d.minute,
p.t.month+1, p.t.day, p.t.hour, p.t.minute));
d.second = 0;
tltz = make_ctime_from_ti(&p.t);
tgmt = make_ctime_from_ti(&d);
LOG(3, findtz, ("Remote timezone: %d minutes from GMT",
(int)((tgmt - tltz) / 60)));
remotetz = tgmt - tltz;
/* sometimes things just fails and we get crazy values,
* as can happen with files from the future. Also,
* I have seen a remote ls return randomly wrong dates. */
if (fabs((double)remotetz) > 24 * 3600) {
remotetz = 0;
goto dirdone;
}
foundtz = 1;
}
}
dirdone:
if (gotodir) {
cmd("CWD %s", "..");
if (!success()) {
LOG(0, failure, ("Could not CWD to '..', aborting"));
exit(EXIT_FAILURE);
}
}
return foundtz;
}
static void
do_getfiles(void)
{
char line[PATH_MAX + 80];
while (fgets(line, sizeof(line), tempfile)) {
time_t mtime = strtoul(strtok(line, " "), NULL, 10);
uint perm = strtoul(strtok(NULL, " "), NULL, 10);
uint msize = strtoul(strtok(NULL, " "), NULL, 10);
do_get_file(strtok(NULL, "\n"), mtime, perm, msize);
}
}
static uint
rdig(unsigned char **s)
{
/* Read number from *s, skip leading and trailing space, and
* update *s. Positive ints only. */
unsigned char *r = *s;
uint x;
while (*r && !isdigit(*r))
r++;
x = (uint)atoi((const char *)r);
while (*r && isdigit(*r))
r++;
*s = r;
return x;
}
static int
parse_epsv_reply(char *buffer, struct connection *c)
{
/* parses EPSV command response (for IPv6) stored in buffer
and fills connection structure with address/port it has acquired */
unsigned char *s = (unsigned char *)buffer + 4;
unsigned short cport;
struct sockaddr_in6 *si6;
char numaddr[64];
while (*s && !isdigit(*s))
s++;
if (!*s)
return -1;
memset(&c->dest, 0, sizeof(c->dest));
si6 = (struct sockaddr_in6 *)&c->dest;
/* set destination address to the same address to which control
connection is established (according to RFC 2428,
"The response to this command includes only the TCP port number
of the listening connection."
*/
si6->sin6_addr = dst6_addr;
si6->sin6_family = AF_INET6;
cport = rdig(&s);
c->dport = cport;
si6->sin6_port = htons(cport);
inet_ntop(si6->sin6_family, &(si6->sin6_addr), numaddr, sizeof(numaddr));
LOG(5, addrinfo ,("passive reply is %d (using addr %s)",
c->dport, numaddr));
return 0;
}
static int
parse_pasv_reply(char *buffer, struct connection *c)
{
/* parses PASV command (for IPv4) */
unsigned char *s = (unsigned char *)buffer + 4;
unsigned long addr;
unsigned short cport;
struct sockaddr_in *si;
while (*s && !isdigit(*s))
s++;
if (!*s)
return -1;
memset(&c->dest, 0, sizeof(c->dest));
/* Written like this to ensure order of execution */
addr = rdig(&s) << 24;
addr += rdig(&s) << 16;
addr += rdig(&s) << 8;
addr += rdig(&s);
si = (struct sockaddr_in *)&c->dest;
si->sin_addr.s_addr = htonl(addr);
cport = rdig(&s) << 8;
cport += rdig(&s);
c->dport = cport;
si->sin_port = htons(cport);
si->sin_family = AF_INET;
c->af = AF_INET;
LOG(5, addrinfo ,("passive reply is %s.%d", inet_ntoa(si->sin_addr),
ntohs(si->sin_port)));
return 0;
}
static int
init_passive(struct connection *c) {
/* wrapper for _ipv4/_ipv6 functions */
switch (c->af) {
case AF_INET:
return(init_passive_ipv4(c));
break;
case AF_INET6:
return(init_passive_ipv6(c));
break;
default:
return(0);
}
}
static int
init_passive_ipv6(struct connection *c)
{
char buffer[PATH_MAX + 80];
int err;
cmd("EPSV");
err = get_input(buffer, sizeof(buffer));
if (err != 229) { /* RFC 2428, section 3 */
LOG(0, cmdfail, ("EPSV command failed, skipping: %s", buffer));
return -1;
}
if (parse_epsv_reply(buffer, c)) {
LOG(0, protoerr, ("EPSV reply not understood: %s", buffer));
return -1;
}
return 0;
}
static int
init_passive_ipv4(struct connection *c)
{
char buffer[PATH_MAX + 80];
int err;
cmd("PASV");
err = get_input(buffer, sizeof(buffer));
if (err != 227) {
LOG(0, cmdfail, ("PASV command failed, skipping: %s", buffer));
return -1;
}
if (parse_pasv_reply(buffer, c)) {
LOG(0, protoerr, ("PASV reply not understood: %s", buffer));
return -1;
}
return 0;
}
static int
do_get_file(char *arg_cmd, time_t mtime, uint perm, uint msize)
{
struct connection *c;
int x;
int retries = 4;
int err;
unsigned short dport = 0;
char numaddr[64];
LOG(7, other, ("do_get_file called with arg `%s'", arg_cmd));
do {
const char *msg;
c = new_connection();
if (passive) {
if (init_passive(c)) {
LOG(0, failure, ("Passive initialisation failed. skipping."));
free(c);
return 1;
}
} else {
init_active(c);
cmd("%s %s", make_port_verb(c->af),
make_active_args((struct sockaddr *)&c->sad));
if(!success()) {
LOG(1, cmdfail, ("Port command failed, skipping file"));
close(c->fd);
free(c);
return 1;
}
}
cmd("%s", arg_cmd);
if (passive) {
if (make_passive_connection(c)) {
LOG(0, failure, ("Passive initialisation failed. skipping."));
free(c);
return 1;
}
switch (c->af) {
case AF_INET:
inet_ntop(c->af, &((struct sockaddr_in *)&c->dest)->sin_addr,
numaddr, sizeof(numaddr));
dport = ((struct sockaddr_in *)&c->dest)->sin_port;
break;
case AF_INET6:
inet_ntop(c->af, &((struct sockaddr_in6 *)&c->dest)->sin6_addr,
numaddr, sizeof(numaddr));
dport = ((struct sockaddr_in *)&c->dest)->sin_port;
break;
}
LOG(5, connect, ("hdl: Connected passively to %s port %d",
numaddr, ntohs(dport)));
}
x = handle_input(&msg);
/* ### Should handle transient errors better here */
if (x >= 400) {
LOG(1, cmdfail,
("Cannot get file `%s', skipping. (%s)", arg_cmd + 5, msg));
close(c->fd);
free(c);
return 1;
}
err = handle_ftp_transfer(c, arg_cmd + 5, mtime, perm, msize);
close(c->fd);
free(c);
if (err < 0) {
retries--;
} else if (err < 200 || err > 299) {
LOG(0, failure, ("Retrieving file failed. Exiting."));
exit(EXIT_FAILURE);
}
} while (err < 0 && retries > 0);
return retries <= 0; /* 0 on success, 1 on failure */
}
static void
print_hashes(int totlen, int *hashedlen, uint msize)
{
static time_t last_time = 0;
const char hashsigns[] = "####################";
double total_eta_sec = 0.0;
uint eta_hours = 0;
uint eta_mins = 0;
uint eta_sec = 0;
double total_eta_tsec = 0.0;
uint eta_tdays = 0;
uint eta_thours = 0;
uint eta_tmins = 0;
uint eta_tsec = 0;
if (log_eta) {
time_t t = time(NULL); /* Only print once per second */
if (t == last_time)
return;
last_time = t;
total_eta_sec = (msize - totlen) / (kb_sec_overall * 1024.0);
eta_hours = total_eta_sec / 3600;
eta_mins = (total_eta_sec - eta_hours * 3600) / 60;
eta_sec = total_eta_sec - eta_hours * 3600 - eta_mins * 60 + 0.5;
total_eta_tsec = (totalbytes - totlen) / (kb_sec_overall * 1024.0);
eta_tdays = total_eta_tsec / 86400;
eta_thours = (total_eta_tsec - eta_tdays * 86400) / 3600;
eta_tmins = (total_eta_tsec - eta_tdays * 86400
- eta_thours * 3600) / 60;
eta_tsec = total_eta_tsec - eta_tdays * 86400
- eta_thours * 3600 - eta_tmins * 60 + 0.5;
printf ("[%9dk] ETA %d:%.2d:%.2d remaining. [%9dk] "
"ETA %d:%.2d:%.2d:%.2d overall. \r",
(uint) ((msize-totlen)/1024),
eta_hours,
eta_mins,
eta_sec,
(uint) ((totalbytes-totlen)/1024),
eta_tdays,
eta_thours,
eta_tmins,
eta_tsec);
fflush(stdout);
return;
}
if (log_hashes > 1) {
int hashes = (totlen - *hashedlen) / log_hashes;
if (hashes) {
int maxh = sizeof(hashsigns) - 1;
*hashedlen += hashes*log_hashes;
while (hashes > maxh) {
printf("%s", hashsigns);
hashes -= maxh;
}
printf("%s", &hashsigns[maxh - hashes]);
fflush(stdout);
}
return;
}
/* like hashes, but less intrusive */
if (log_hashes == 1) {
printf("[%8d]\r", totlen);
fflush(stdout);
return;
}
}
static void
handle_write_error(int l, int ffd, const char *tmp_name)
{
const int saved_errno = errno;
close(ffd);
unlink(tmp_name);
if (l == -1 && saved_errno == ENOSPC) {
LOG(0, failure, ("#### OUT OF SPACE WHILE WRITING '%s'. ####",
tmp_name));
exit(EXIT_SUCCESS); /* So mirror doesn't run again */
} else {
LOG(0, failure, ("-- ERROR -- Problems while writing '%s' : %s",
tmp_name, strerror(saved_errno)));
return;
}
}
static int
supermkdir(char *d)
{
if (mkdir_mode(d, dirmode)) {
if (errno == EEXIST)
return 0;
if (errno == ENOENT) {
char *rslash = strrchr(d, '/');
if (rslash) {
*rslash = 0;
if (!supermkdir(d)) {
*rslash = '/';
return mkdir_mode(d, dirmode);
}
}
}
return -1;
}
return 0;
}
void
log_conn_from(struct sockaddr *sa)
{
char numaddr[64];
unsigned short cport = 0;
switch(sa->sa_family) {
case AF_INET:
inet_ntop(sa->sa_family, &((struct sockaddr_in *)sa)->sin_addr,
numaddr, sizeof(numaddr));
cport = ((struct sockaddr_in *)sa)->sin_port;
break;
case AF_INET6:
inet_ntop(sa->sa_family, &((struct sockaddr_in6 *)sa)->sin6_addr,
numaddr, sizeof(numaddr));
cport = ((struct sockaddr_in6 *)sa)->sin6_port;
break;
}
LOG(5, connect, ("hdl: Connection from %s port %d", numaddr, cport));
}
static int
handle_ftp_transfer(struct connection *c, char *filename, time_t mtime,
uint perm, uint msize)
{
int ffd;
char buffer[10240];
int len;
struct sockaddr_storage sad;
int fd = -1;
int totlen = 0;
int hashedlen = 0;
char tmp_name[PATH_MAX + 8];
char *fname = strrchr(filename, '/');
int retnum;
struct utimbuf ut;
if (fname) {
*fname = 0; /* split filename into path and filename */
sprintf(tmp_name, "%s/.in.%s", filename, fname + 1);
*fname = '/';
} else {
sprintf(tmp_name, ".in.%s", filename);
}
if (passive) {
fd = c->fd;
} else {
LOG(5, connect, ("ftp trans: Waiting for connection..."));
alarm(connect_timeout);
fd = accept_connection(c->fd, (struct sockaddr *)&sad);
alarm(0);
if (fd == -1) {
if (errno == EINTR) {
LOG(1, connect, ("Accept timed out."));
/* Expired alarm */
return -1;
}
LOG(0, connect, ("-- ERROR -- accept() : %s", strerror(errno)));
exit(EXIT_FAILURE);
}
log_conn_from((struct sockaddr *)&sad);
}
if ((ffd = creat(tmp_name, 0200)) == -1) { /* create unreadble tempfile */
if (errno == ENOENT && fname) {
*fname = 0;
make_new_dir(filename);
*fname = '/';
if ((ffd = creat(tmp_name, 0200)) != -1)
goto dir_ok;
}
LOG(0, failure, ("-- ERROR -- Creating temp file '%s' : %s", tmp_name,
strerror(errno)));
close(fd);
return 0;
}
dir_ok:
if (verbosity >= 2) {
LOG(2, getfile, ("Getting file '%s' size = %u", filename, msize));
}
do {
alarm(input_timeout);
len = read(fd, buffer, sizeof(buffer));
alarm(0);
if (len > 0) {
int idx = 0;
int rest = len;
totlen += len;
print_hashes(totlen, &hashedlen, msize);
LOG(18, other, ("got %d bytes..", len));
while (rest > 0) {
int l;
if ((l = write(ffd, buffer + idx, rest)) > 0) {
idx += l;
rest -= l;
} else {
handle_write_error(l, ffd, tmp_name);
close(ffd);
close(fd);
return 0;
}
}
} else if (len == -1) {
LOG(0, failure, ("Timed out getting '%s', skipping.", filename));
unlink(tmp_name);
close(ffd);
close(fd);
return 0;
}
} while (len > 0);
if (log_hashes || log_eta)
printf("\n");
totalsum += totlen;
totalbytes -= msize;
time_since_begin_of_file = time_since_last();
kb_sec_lastfile = totlen / 1024.0 / time_since_begin_of_file;
kb_sec_overall = totalsum / 1024.0 / time_since_start ();
if (verbosity != 3 && verbosity >= 2) {
LOG(1, getfile, ("[got %9d bytes, %.0f bytes remaining. ETA = %.0f minutes\n %8.2f kb/sec lastfile, %8.2f kb/sec overall] %s",
totlen, totalbytes, (totalbytes * time_since_start()) / ((totalsum?totalsum:1) * 60.0),
kb_sec_lastfile,
kb_sec_overall,
filename));
}
if (verbosity == 3) {
LOG(3, getfile, ("Transfer rate: %8.2f kb/sec lastfile, %8.2f kb/sec overall",
kb_sec_lastfile,
kb_sec_overall));
}
perm &= file_and_mask;
perm |= file_or_mask;
if (fchmod(ffd, perm)) {
LOG(0, failure, ("-- ERROR -- could not fchmod '%s' to 0%o: %s",
tmp_name, perm, strerror(errno)));
}
close(ffd);
close(fd);
retnum = handle_input(NULL);
if (use_mdtm) {
const char *msg;
int x;
cmd("MDTM %s", filename);
x = handle_input(&msg);
if (x>=200 && x<=299) {
struct timeinfo d;
LOG(8, other, ("mdtm %s returned status %d string '%s'",
filename, x, msg+4));
if (parse_mdtm(msg, &d)) {
mtime = make_ctime_from_ti(&d);
LOG(8, other, ("Translates to %d (%s)", (int)mtime,
strip_newline(ctime(&mtime))));
}
}
}
ut.actime = mtime;
ut.modtime = mtime;
LOG(5, other, ("Setting time of %s to %u, perm to 0%o", tmp_name,
(uint)mtime, perm));
if (utime(tmp_name, &ut)) {
LOG(0, failure, ("-- ERROR -- [1] utime(\"%s\",%d) : %s", tmp_name,
(uint)mtime, strerror(errno)));
}
if (rename(tmp_name, filename)) {
LOG(0, failure, ("-- ERROR -- Could not rename '%s' to '%s' : %s",
tmp_name, filename, strerror(errno)));
} else {
LOG(7, other, ("Renamed %s -> %s", tmp_name, filename));
}
return retnum;
}
static void
get_filelist()
{
struct connection *c;
char numaddr[64];
int x;
const char *msg;
if (!compressed) {
cmd("TYPE A");
if (!success())
exit(EXIT_FAILURE);
} else {
cmd("TYPE I");
if (!success())
exit(EXIT_FAILURE);
}
c = new_connection();
if (passive) {
if (init_passive(c)) {
LOG(0, failure, ("Passive initialisation failed, aborting."));
exit(EXIT_FAILURE);
}
} else {
init_active(c);
cmd("%s %s", make_port_verb(c->af),
make_active_args((struct sockaddr *)(&c->sad)));
if(!success()) {
close(c->fd);
free(c);
LOG(1, cmdfail, ("Port command failed, aborting"));
exit(EXIT_FAILURE);
}
}
if (!dircmd)
dircmd = default_dircmd;
if (!decompressor)
decompressor = default_decompressor;
if (!decompressor_opt)
decompressor_opt = default_decompressor_opt;
cmd("%s", dircmd);
if (passive) {
if (make_passive_connection(c)) {
LOG(0, failure, ("Passive conection failed, aborting."));
exit(EXIT_FAILURE);
}
switch (c->af) {
case AF_INET:
inet_ntop(c->af, &((struct sockaddr_in *)&c->dest)->sin_addr,
numaddr, sizeof(numaddr));
break;
case AF_INET6:
inet_ntop(c->af, &((struct sockaddr_in6 *)&c->dest)->sin6_addr,
numaddr, sizeof(numaddr));
break;
}
LOG(5, connect, ("hdl: Connected passively to %s port %d",
numaddr, c->dport));
}
x = handle_input(&msg);
if (x >= 400) {
LOG(1, cmdfail, ("Dir listing failed, exiting. (%s)", msg));
close(c->fd);
free(c);
exit(EXIT_FAILURE);
}
handle_dir_listing(c);
close(c->fd);
free(c);
if (!success()) {
LOG(0, cmdfail,
("Unexpected problem after getting dir listing. exiting."));
exit(EXIT_FAILURE);
}
}
static int
make_new_dir(const char *name)
{
int err;
char dname[2048];
strncpy(dname, name, sizeof(dname));
dname[sizeof(dname) - 1] = 0;
if (verbosity < 2) {
/* cron job mode */
LOG(1, willmakedir, ("+ %s/", dname));
} else {
LOG(1, willmakedir, ("create dir: %s/", dname));
}
if ((err = supermkdir(dname)))
LOG(0, failure, ("-- ERROR -- supermkdir(\"%s\") : %s", dname,
strerror(errno)));
return err;
}
static int
unlink_and_mkdir(const char *name)
{
if (verbosity < 2) {
LOG(1, rmfile, ("! %s/", name));
} else {
LOG(1, rmfile, ("`%s' is not a dir, unlinking..", name));
}
unlink(name);
return make_new_dir(name);
}
static int
lstat_log(const char *name, struct stat *st)
{
int err = lstat(name, st);
if (err == -1 && errno != ENOENT) {
LOG(0, failure, ("in lstat_log: lstat(\"%s\") : %s", name,
strerror(errno)));
}
return err;
}
static void
set_new_directory(char *prefix, char *l)
{
struct stat sbuf;
int len;
int action;
int skipit;
int delit;
const char *t;
if (!strncmp(l, "./", 2)) {
l += 2;
}
LOG(7, other, ("Changing prefix to: `%s'", l));
strcpy(prefix, l); /* NOTE! Changes prefix argument */
len = strlen(prefix);
t = strrchr(prefix, '/');
strcat(prefix, "/");
action = get_file_act(prefix, t ? t + 1 : prefix);
skipit = !!(action & A_EXCL);
delit = !(action & A_NODEL);
prefix[len] = 0;
if (lstat_log(prefix, &sbuf)) {
if (!skipit) {
if (errno == ENOENT) {
make_new_dir(prefix);
} else {
/* It's a bit weird if you get here, means you can't
* stat the file and it's not because it isn't there */
unlink_and_mkdir(prefix);
}
}
} else {
if (!S_ISDIR(sbuf.st_mode)) {
if (skipit) {
/* Something that is not a directory (locally) has
* been replaced by a remote directory, and it's
* (potentially) excluded.
*
* ### should we test for delit && !nodel here? */
if (delit && !nodel) {
LOG(1, rmfile,
("`%s' is now a (possibly) excluded dir. deleting",
prefix));
if (unlink(prefix))
LOG(1, failure,
("set_new_directory: unlink(\"%s\") failed: %s",
prefix, strerror(errno)));
}
} else {
unlink_and_mkdir(prefix);
}
} else if (skipit && delit) {
/* A local dir _might_ be excluded */
maybe_delete_dir(prefix);
}
}
prefix[len] = 0;
}
static int
string_match_len(const char *s1, const char *s2)
{
/* Returns how many chars from the start of s1 and s2 that are
* identical */
int mlen = 0;
while (*s1 && *s1++ == *s2++)
mlen++;
return mlen;
}
static void
delayed_dir_del(const char *d)
{
LOG(7, other, ("[delayed del] %s", d));
fprintf(delayed_delfile, "%s\n", d);
}
static void
maybe_delete_dir(const char *pathname)
{
/* Registers a directory that should maybe be deleted later. It
* will be deleted if it is empty after the mirroring is finished.
* ### todo fix bug: will delete empty dirs all the way down to / */
static char last_path[2048];
int last_len = strlen(last_path);
int match_len;
if (nodel)
return;
if (*pathname)
LOG(7, other, ("maybe_delete_dir: %s", pathname));
match_len = string_match_len(pathname, last_path);
if (!last_len || match_len >= last_len) {
strcpy(last_path, pathname);
strcat(last_path, "/");
} else {
char *x;
while ((x = strrchr(last_path, '/')) && (x - last_path) >= match_len) {
*x = 0;
delayed_dir_del(last_path);
}
strcpy(last_path, pathname);
strcat(last_path, "/");
}
}
static void
delete_delayed_dirs(void)
{
char prev[2048];
char line[2048];
int prev_failed = 0;
maybe_delete_dir(""); /* flush pending dirs that maybe should be deleted */
/* fflush(delayed_delfile); */
if (fseek(delayed_delfile, 0L, SEEK_SET)) {
LOG(0, failure, ("-- ERROR -- Problems with fseek del-file : %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
prev[0] = 0;
while (fgets(line, sizeof(line), delayed_delfile)) {
int l;
strip_newline(line);
l = strlen(line);
line[l] = '/';
line[l + 1] = 0;
if (prev_failed && !strncmp(line, prev, l + 1)) {
line[l] = 0;
LOG(7, other, ("[delayed del] SKIP %s", line));
} else {
line[l] = 0;
prev_failed = rmdir(line);
if (prev_failed) {
LOG(7, other, ("[delayed del] FAILED(ok) %s [%s]", line,
strerror(errno)));
} else {
if (verbosity < 2) {
LOG(1, rmdir, ("- %s/", line));
} else {
LOG(1, rmdir, ("delete dir: %s/", line));
}
}
}
memcpy(prev, line, l + 1);
}
}
static void
get_new_file(struct parse_info *p)
{
if (S_ISREG(p->perm)) {
if (verbosity < 2) {
/* Ideal output for cron jobs etc: */
LOG(1, willget, ("+ %s", p->filename));
} else {
LOG(3, willget, ("`%s' size = %u is missing, getting.",
p->filename, p->size));
}
get_file(p);
} else if (S_ISLNK(p->perm)) {
if (verbosity < 2) {
LOG(1, mksymlink, ("+ %s -> %s", p->filename, p->linkname));
} else {
LOG(1, mksymlink, ("create link: %s -> %s", p->filename, p->linkname));
}
if (symlink(p->linkname, p->filename)) {
if (errno == ENOENT) {
p->fileonlyname[-1] = 0;
if (make_new_dir(p->filename)) {
LOG(0, failure, ("make_new_dir(\"%s\") failed: %s",
p->filename, strerror(errno)));
} else {
p->fileonlyname[-1] = '/';
if (symlink(p->linkname, p->filename) == 0)
return;
}
}
LOG(0, failure, ("-- ERROR -- symlink(\"%s\", \"%s\"): %s",
p->linkname, p->filename, strerror(errno)));
}
} else if (S_ISBLK(p->perm) || S_ISCHR(p->perm) || S_ISFIFO(p->perm) ||
S_ISSOCK(p->perm)) {
LOG(5, other, ("Ignoring remote device/fifo/socket '%s'(0x%x)",
p->filename, p->perm));
} else if (!S_ISDIR(p->perm)){
LOG(1, weird, ("* Strange perm for '%s' (0x%x)", p->filename,
p->perm));
}
}
static void
print_download_excuse(struct parse_info *p, struct stat *st)
{
int tdif = time_dif(st->st_mtime, p->date);
const char *t = tdif ? "time" : "";
int sdif = size_dif(st->st_size, p->size);
const char *s = sdif ? "size" : "";
int mdif = mode_dif(st->st_mode, p->perm);
const char *m = mdif ? "mode" : "";
LOG(3, dlexcuse,
("`%s' size = %u has new %s%s%s%s%s, getting", p->filename, p->size,
t, tdif && (sdif || mdif) ? (sdif && mdif ? ", " : " and ") : "",
s, sdif && mdif ? " and " : "",
m));
}
static void
verify_symlink(struct parse_info *p)
{
char buffer[PATH_MAX];
int l;
buffer[0] = 0;
l = readlink(p->filename, buffer, sizeof(buffer));
buffer[l >= 0 ? l : 0] = 0;
if (!*buffer) {
if(verbosity < 2) {
LOG(1, mksymlink, ("! %s -> %s", p->filename, p->linkname));
} else {
LOG(1, mksymlink,
("`%s' is now a symlink to `%s'", p->filename, buffer));
}
} else if (strcmp(buffer, p->linkname)) {
if (verbosity < 2) {
LOG(1, mksymlink,
("! %s -> %s (%s)",
p->filename, p->linkname, buffer));
} else {
LOG(1, mksymlink,
("symlink `%s' points elsewhere (`%s' changed to `%s')",
p->filename, buffer, p->linkname));
}
unlink(p->filename);
if (symlink(p->linkname, p->filename)) {
LOG(0, failure,
("symlink(\"%s\", \"%s\") failed: %s", p->linkname,
p->filename, strerror(errno)));
}
} else {
LOG(7, other, ("symlink `%s' unchanged", p->filename));
}
}
static int
clean_dir(DIR *d)
{
/* Delete all files and directories in directory pointed to by d.
* Return 1 if the dir contains more to be deleted, 0 if not, -1
* on error. The function can return 1 when the dir is empty if it
* eventually returns 0 when being called again (currently happens
* if the last entry in the dir is a directory) The dir <d> must
* be closed before returning. */
struct dirent *dent;
while (d && (dent = readdir(d))) {
if (strcmp(dent->d_name, ".") &&
strcmp(dent->d_name, "..")) {
struct stat s;
lstat(dent->d_name, &s);
if (S_ISDIR(s.st_mode)) {
char *nam = strdup(dent->d_name);
closedir(d); /* Prevent too many open files */
d = NULL;
if (recursive_unlink(nam, &s)) {
free(nam);
return -1;
}
free(nam);
} else {
/* Files are deleted directly */
if (unlink(dent->d_name)) {
LOG(0, failure,
("Problems unlinking '%s': %s", dent->d_name,
strerror(errno)));
closedir(d);
return -1;
}
}
}
}
if (d)
closedir(d);
return !d; /* Return 1 if dir is not empty, 0 if it is empty */
}
static int
clean_current_dir(void)
{
int err = 0;
DIR *d;
do {
d = opendir(".");
if (!d) {
LOG(0, failure, ("opendir(\".\") failed: %s", strerror(errno)));
break;
}
} while ((err = clean_dir(d)) > 0); /* clean_dir does close_dir(d) */
return err; /* contains error code if clean_dir fails */
}
static int
recursive_unlink(const char *filename, struct stat *st)
{
/* This function is written in a somewhat special way to handle
* directory trees of any depths. Directories are deleted in
* depth first order. */
if (!strncmp("./", filename, 2)) {
filename += 2;
while (*filename == '/')
filename++;
}
if (S_ISDIR(st->st_mode)) {
char *old_dir;
if (!strchr(filename, '/')) {
/* If the filename has no /, it is in the current dir. It
* is therefore sufficient to do a chdir("..") after
* chdir(filename) to get back where we are. */
old_dir = "..";
} else {
char *dir_buf = (char *)malloc(PATH_MAX);
old_dir = getcwd(dir_buf, PATH_MAX);
if (!old_dir) {
LOG(0, failure, ("OOPS: getcwd failed, aborting rec. unlink"));
free(dir_buf);
return -1;
}
}
if (chdir(filename)) {
LOG(0, failure, ("rec. unlink: chdir(\"%s\") failed: %s:",
filename, strerror(errno)));
} else {
int err = clean_current_dir();
/* Change working directory back to what it was on entry */
chdir(old_dir);
if (err) {
return err;
}
}
if (strcmp(old_dir, ".."))
free(old_dir); /* old_dir was malloced */
return rmdir(filename);
}
return unlink(filename);
}
static void
get_old_file(struct parse_info *p, struct stat *st)
{
if (S_ISREG(p->perm)) {
if (S_ISLNK(st->st_mode)) {
if (verbosity > 2) {
LOG(1, delsymlink,
("symlink '%s' replaced with file, unlink.", p->filename));
} else {
LOG(1, delsymlink, ("- %s ->", p->filename));
}
if (unlink(p->filename)) {
LOG(0, failure, ("-- ERROR -- unlink '%s': %s", p->filename,
strerror(errno)));
} else {
get_file(p);
}
} else {
if (S_ISDIR(st->st_mode)) {
if (verbosity > 2) {
LOG(1, rmdir, ("delete dir: %s/", p->filename));
} else {
LOG(1, rmdir, ("- %s/", p->filename));
}
recursive_unlink(p->filename, st);
}
if (verbosity >= 2)
print_download_excuse(p, st);
get_file(p);
}
if (verbosity < 2) {
LOG(1, willget, ("! %s", p->filename));
}
} else if (S_ISLNK(p->perm)) {
if (!S_ISLNK(st->st_mode)) {
if (verbosity >= 2) {
LOG(1, rmdir, ("delete dir: %s/", p->filename));
} else {
LOG(1, rmdir, ("- %s/", p->filename));
}
recursive_unlink(p->filename, st);
}
verify_symlink(p);
} else if (S_ISBLK(p->perm) || S_ISCHR(p->perm) || S_ISFIFO(p->perm) ||
S_ISSOCK(p->perm)) {
int delit = !(get_file_act(p->filename, p->fileonlyname) & A_NODEL);
if (delit && !nodel) {
/* ### - uhm is it correct not to delete this if we're nodel ? */
if (verbosity > 2) {
LOG(1, rmfile, ("- %s (replaced by dev/fifo/socket)",
p->filename));
} else{
LOG(1, rmfile,
("'%s' has been replaced by device/fifo/socket (0x%x),"
"deleting.", p->filename, p->perm));
}
recursive_unlink(p->filename, st);
}
} else if (!S_ISDIR(p->perm)) {
LOG(0, weird, ("HUH? get_old_file called for something not link, "
"dir or file (name = '%s', perm = 0%o)", p->filename,
p->perm));
}
}
static char *
strip_newline(char *s)
{
/* Strips CR, LF, CR LF or LF CR from the end of a string. */
int len = strlen(s);
char c;
if (len == 0)
return s;
c = s[len - 1];
if (c == 10 || c == 13) {
if (--len && (c ^ s[len - 1]) == (10 ^ 13))
len--;
s[len] = 0;
}
return s;
}
static uint
make_hash_value(const char *src, uint fht_len)
{
uint h1 = 0;
uint c;
while ((c = (uint)((unsigned char)*src++))) {
h1 ^= ((h1 << 7) | (h1 >> (32 - 7))) ^ c;
}
h1 %= fht_len;
return h1;
}
typedef char *fhashentry_t;
static void
add_to_hash(char *name, fhashentry_t *fht, int fht_len)
{
uint h1 = make_hash_value(name, fht_len);
LOG(10, other, ("add_to_hash(\"%s\", %p, %d)", name, (void *)fht,
fht_len));
while (fht[h1]) {
if (++h1 >= (uint)fht_len)
h1 = 0;
}
fht[h1] = name;
LOG(10, other, ("fht[%d] = %p", h1, name));
}
static void
del_from_hash(const char *name, fhashentry_t *fht, int fht_len)
{
uint h1 = make_hash_value(name, fht_len);
int x = fht_len;
while (--x) {
char *s = fht[h1];
if (s) {
if (!strcmp(s, name)) {
*s = 0; /* zero the string but keep hash-value! */
LOG(10, other, ("del_from_hash(\"%s\", %p, %d): OK", name,
(void *)fht, fht_len));
return;
}
} else {
break;
}
if (++h1 >= (uint)fht_len)
h1 = 0;
}
LOG(10, other, ("del_from_hash(\"%s\", %p, %d): NOT FOUND", name,
(void *)fht, fht_len));
}
static void
parse_ls_entry(char *prefix, char *line)
{
/* Parses one line of the ls-output. */
/* ### this function is too big, split up */
struct stat st;
struct parse_info p;
static fhashentry_t *fht = 0;
static int fht_maxlen = 0;
static int fht_len = 0;
static char *fnames = 0;
static int fnames_len = 0;
static int newdir = 1;
static int hash_entries = 0;
int len = 0;
if (!line)
goto last_time;
len = strlen(strip_newline(line)) - 1;
LOG(6, other, ("LS> %s", line));
if (newdir) {
/* 1. Read the new directory and store the names */
int fn_idx = 0, idx = 0;
int n;
DIR *d = opendir(prefix);
struct dirent *de;
newdir = 0;
hash_entries = 0;
if (!d)
goto no_such_dir;
while ((de = readdir(d))) {
int l = strlen(de->d_name) + 1;
while (l + fn_idx > fnames_len) {
char *tmp;
fnames_len += 100 + fnames_len;
tmp = realloc(fnames, fnames_len);
LOG(10, other,
("realloc(%p, %d) = %p", fnames, fnames_len, tmp));
if (!tmp) {
LOG(0, failure,
("realloc(%p, %d) fails!", fnames, fnames_len));
exit(EXIT_FAILURE);
}
fnames = tmp;
}
memcpy(fnames + fn_idx, de->d_name, l);
fn_idx += l;
hash_entries++;
}
closedir(d);
LOG(10, other,
("[debug] %d hash-entries, %d bytes", hash_entries, fn_idx));
/* build a hash-table from this (we now know how many entries
* there are, and can make a suitably sized hash-table. */
fht_len = ((hash_entries + 1) * 75) / 32;
if (fht_len > fht_maxlen) {
fht = realloc(fht, fht_len * sizeof(fhashentry_t));
if (!fht) {
LOG(0, other,
("FATAL ERROR: realloc(%p, %d) failed: %s",
fht, fht_len * sizeof(fhashentry_t), strerror(errno)));
exit(EXIT_FAILURE);
}
fht_maxlen = fht_len;
}
for (n = 0; n < fht_len; n++)
fht[n] = (char *)NULL;
for (n = 0; n < hash_entries; n++) {
if (strcmp(".", &fnames[idx]) && strcmp("..", &fnames[idx])) {
add_to_hash(&fnames[idx], fht, fht_len);
}
idx += (strlen(&fnames[idx]) + 1);
}
}
no_such_dir:
if (parse_filename(prefix, line, &p)) {
if (S_ISDIR(p.perm)) {
/* ### TODO: */
/* push dir on in-this-dir dir-stack */
/* that stack will later be reversed onto the expected-dir stack */
del_from_hash(p.fileonlyname, fht, fht_len);
} else if (!skip_file(p.filename, p.fileonlyname)) {
/* reget means act like the REGET command in ftp, which can
* resume a broken transfer in the middle of a file */
int should_reget = try_reget;
if (lstat_log(p.filename, &st)) {
if (errno == ENOENT) {
get_new_file(&p);
}
} else if (!time_dif(st.st_mtime, p.date) &&
!size_dif(st.st_size, p.size) &&
!mode_dif(st.st_mode, p.perm)) {
LOG(5, other, ("`%s' is unchanged", p.filename));
del_from_hash(p.fileonlyname, fht, fht_len);
should_reget = 0; /* don't reget if we have it */
} else if (keep_newer && is_newer(st.st_mtime, p.date)) {
/* The local file is newer than the remote one */
LOG(5, other, ("local `%s' is newer, not getting", p.filename));
del_from_hash(p.fileonlyname, fht, fht_len);
} else if (size_dif(st.st_size, p.size) ||
mode_dif(st.st_mode, p.perm)) {
get_old_file(&p, &st);
del_from_hash(p.fileonlyname, fht, fht_len);
} else if (reset_times && !S_ISLNK(p.perm)) {
LOG(2, other, ("Setting time of %s to %u",
p.filename, (unsigned int)p.date));
set_time(p.filename, p.date);
del_from_hash(p.fileonlyname, fht, fht_len);
} else {
get_old_file(&p, &st);
del_from_hash(p.fileonlyname, fht, fht_len);
}
if (should_reget) {
char reget_name[PATH_MAX + 100];
sprintf(reget_name, ".in.%u.%u.%s",
(uint)p.date, (uint)p.size, p.fileonlyname);
del_from_hash(reget_name, fht, fht_len);
}
}
free(p.linkname);
free(p.filename);
} else {
if (line[len] == ':' && strcmp("./:", line) && strcmp(".:", line)) {
/* entering new directory, delete all files that are still
* in the hash-table */
int n;
last_time:
for (n = 0; n < fht_len; n++) {
const char *s = fht[n];
if (s && *s && !nodel) {
char path[10240];
struct stat stb;
int delit;
if (prefix[0] == '.' && !prefix[1])
strcpy(path, s);
else
sprintf(path, "%s/%s", prefix, s);
delit = !(get_file_act(path, s) & A_NODEL);
if (delit) {
if (lstat(path, &stb)) {
LOG(1, failure,
("Problems with tbdeled lstat(\"%s\"): %s",
s, strerror(errno)));
} else {
int err = recursive_unlink(path, &stb);
if (verbosity < 2) {
LOG(1, rmfile,
("- %s%s%s%s", path,
S_ISDIR(stb.st_mode) ? "/" : "",
err ? " : " : "",
err ? strerror(errno) : ""));
} else {
LOG(1, rmfile,
("deleted: %s%s%s%s", path,
S_ISDIR(stb.st_mode) ? "/" : "",
err ? " : " : "",
err ? strerror(errno) : ""));
}
}
}
}
fht[n] = NULL;
}
if (line) {
line[len] = 0; /* Strip the trailing ':' */
set_new_directory(prefix, line);
newdir = 1;
hash_entries = 0;
}
}
}
}
static void
handle_dir_listing(struct connection *c)
{
struct sockaddr_storage sad;
FILE *stream;
char line[PATH_MAX + 80];
char prefix[PATH_MAX] = ".";
int fd;
if (passive) {
fd = c->fd;
} else {
LOG(5, other, ("Dir list: Waiting for connection..."));
fd = accept_connection(c->fd, (struct sockaddr *)&sad);
if (fd == -1) {
LOG(0, failure, ("accept() : %s", errno == EINTR
? "Operation timed out" : strerror(errno)));
exit(EXIT_FAILURE);
}
log_conn_from((struct sockaddr *)&sad);
}
if (compressed) {
/* fork off the decompressor and give it the net-conection as stdin */
int p[2];
pid_t pid;
LOG(5, exec,
("Running decompressor %s %s", decompressor, decompressor_opt));
if (pipe(p)) {
LOG(0, failure, ("pipe() failed: %s", strerror(errno)));
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
LOG(0, failure, ("fork() failed: %s", strerror(errno)));
exit(EXIT_FAILURE);
} else if (pid == 0) {
alarm(0); /* Alarms aren't killed by exec */
close(0);
close(1);
if (close(p[0]) || dup(fd) != 0 || dup(p[1]) != 1) {
LOG(0, failure, ("dup or close failed: %s", strerror(errno)));
} else {
execlp(decompressor, decompressor, decompressor_opt, NULL);
LOG(0, failure, ("execlp failed: %s", strerror(errno)));
}
{
char buffer[1024];
sprintf(buffer, "fmirror exec failed: %s\n", strerror(errno));
write(1, buffer, strlen(buffer) + 1);
}
_exit(EXIT_FAILURE);
} else {
close(fd);
close(p[1]);
fd = p[0];
}
}
if (!(stream = fdopen(fd, "r"))) {
LOG(0, failure, ("fdopen(%d, \"r\"): %s", fd, strerror(errno)));
exit(EXIT_FAILURE);
}
while (1) {
char *addr;
alarmed = 0;
alarm(input_timeout);
addr = fgets(line, sizeof(line), stream);
alarm(0);
if (!addr) {
if (errno == EINTR || alarmed) {
LOG(0, failure, ("Timeout waiting for reply. Aborting."));
exit(EXIT_FAILURE);
}
break;
}
parse_ls_entry(prefix, line); /* sometimes changes prefix */
}
parse_ls_entry(prefix, 0); /* finish up */
fclose(stream);
close(fd);
}
static const char *
make_port_verb(int portaf)
{
/* use PORT for IPv4, EPORT for IPv6 */
switch(portaf) {
case AF_INET:
return("PORT");
break;
case AF_INET6:
return("EPRT");
break;
default:
LOG(0, failure,
("make_port_verb: wrong AF specification"));
return(NULL);
break;
}
}
static const char *
make_active_args(struct sockaddr *sad)
{
switch (sad->sa_family) {
case AF_INET:
return(make_port_args((struct sockaddr_in *)sad));
break;
case AF_INET6:
return(make_eprt_args((struct sockaddr *)sad));
break;
default:
LOG(0, failure,
("make_active_args: wrong AF specification"));
return(NULL);
break;
}
}
static const char *
make_eprt_args(struct sockaddr *sad)
{
/* construct arguments of EPRT verb according to RFC 2428, section 2.
this function is currently used only for IPv6 connections,
it can be however used for both IPv4 and IPv6 */
static char buf[128];
char numaddr[64];
struct sockaddr_in *si;
struct sockaddr_in6 *si6;
uint p;
switch(sad->sa_family) {
case AF_INET:
si = (struct sockaddr_in *)sad;
inet_ntop(sad->sa_family,
&(((struct sockaddr_in *)&localaddr)->sin_addr),
numaddr, sizeof(numaddr));
p = ntohs(si->sin_port);
break;
case AF_INET6:
si6 = (struct sockaddr_in6 *)sad;
inet_ntop(sad->sa_family,
&(((struct sockaddr_in6 *)&localaddr)->sin6_addr),
numaddr, sizeof(numaddr));
p = ntohs(si6->sin6_port);
break;
}
snprintf(buf, sizeof(buf), "|%s|%s|%d|",
sad->sa_family == AF_INET ? "1" : "2",
numaddr, p);
return buf;
}
static const char *
make_port_args(struct sockaddr_in *sad)
{
/* Translates an ip address and a port number from a sockaddr_in
* struct to something that can be used by the PORT command in
* ftp. The returned pointer is to STATIC data. */
static char buf[64]; /* more than enough to hold x,x,x,x,x,x */
uint ip;
uint p;
/* get the cached ip-address */
ip = ntohl(((struct sockaddr_in *)&localaddr)->sin_addr.s_addr);
p = ntohs(sad->sin_port);
snprintf(buf, sizeof(buf), "%d,%d,%d,%d,%d,%d",
ip >> 24, (ip >> 16) & 0xff,
(ip >> 8) & 0xff, ip & 0xff, p >> 8, p & 0xff);
LOG(3, ctrl_connect, ("PORT args: %s", buf));
return buf;
}
static struct connection *
new_connection(void)
{
struct connection *c = malloc(sizeof(struct connection));
memset(c, 0, sizeof(*c));
c->fd = -1;
c->file = NULL;
c->af = af;
return c;
}
static void
init_active(struct connection *c)
{
int opt, len;
int sock = socket(c->af, SOCK_STREAM, 0);
unsigned int addrlen = 0;
struct sockaddr_storage sad;
struct sockaddr_in *si;
struct sockaddr_in6 *si6;
char numaddr[64];
const struct in6_addr my_in6addr_any = IN6ADDR_ANY_INIT;
if (sock < 0) {
LOG(0, failure, ("ERROR: socket() : %s", strerror(errno)));
exit(EXIT_FAILURE);
}
#if (defined(IP_TOS) && defined(IPTOS_THROUGHPUT))
opt = IPTOS_THROUGHPUT;
if (c->af == AF_INET) /* set IP_TOS for AF_INET only */
setsockopt(sock, IPPROTO_IP, IP_TOS, (char *)&opt, sizeof(opt));
#endif
switch (c->af) {
case AF_INET:
si = (struct sockaddr_in *)&sad;
si->sin_family = c->af;
si->sin_addr.s_addr = INADDR_ANY;
si->sin_port = 0;
addrlen = sizeof(*si);
inet_ntop(c->af, &(si->sin_addr), numaddr, sizeof(numaddr));
break;
case AF_INET6:
si6 = (struct sockaddr_in6 *)&sad;
si6->sin6_family = c->af;
si6->sin6_addr = my_in6addr_any;
si6->sin6_port = 0;
addrlen = sizeof(*si6);
inet_ntop(c->af, &(si6->sin6_addr), numaddr, sizeof(numaddr));
break;
}
if (bind(sock, (struct sockaddr *)&sad, addrlen)) {
LOG(0, failure, ("ERROR: bind(%s,%d): %s",
numaddr,
c->af == AF_INET ?
ntohs(((struct sockaddr_in *)&sad)->sin_port)
: ntohs(((struct sockaddr_in6 *)&sad)->sin6_port),
strerror(errno)));
close(sock);
exit(EXIT_FAILURE);
}
if (listen(sock, 5)) {
LOG(0, failure, ("ERROR: listen(%d, 5): %s", sock,
strerror(errno)));
close(sock);
exit(EXIT_FAILURE);
}
len = sizeof(sad);
if (getsockname(sock, (struct sockaddr *)&sad, &len)) {
LOG(0, failure, ("ERROR: getsockname(): %s", strerror(errno)));
exit(EXIT_FAILURE);
}
LOG(3, ctrl_connect, ("listening on ANY:%d",
c->af == AF_INET ?
ntohs(((struct sockaddr_in *)&sad)->sin_port)
: ntohs(((struct sockaddr_in6 *)&sad)->sin6_port)));
c->fd = sock;
c->sad = sad;
c->file = NULL;
}
static char *
smatch(const char *s1, char *s2)
{
int len = strlen(s1);
if (!strncasecmp(s1, s2, len)) {
char *x = s2 + len;
while (isspace((unsigned char)*x))
x++;
return x;
}
return NULL;
}
static int
opt_set_string(const char *varname, const char *pattern, char *line,
char **var)
{
char *opt;
if ((opt = smatch(pattern, line))) {
if (!*var) {
*var = strdup(opt);
LOG(7, setopt, ("(parse) - %s set to `%s'", varname, *var));
} else {
LOG(12, other, ("(ignored) %s", line));
}
return 1;
}
return 0;
}
static int
opt_set_int(const char *varname, const char *pattern, char *line,
int *var)
{
char *opt;
if ((opt = smatch(pattern, line))) {
*var = strtol(opt, (char **)NULL, 0);
LOG(7, setopt, ("(parse) - %s set to `%d'", varname, *var));
return 1;
}
return 0;
}
#define OPT_SET_STRING(x) opt_set_string(#x, #x ":", line, &x)
#define OPT_SET_INT(x) opt_set_int(#x, #x ":", line, &x)
#define OPT_SET_LOG(x) opt_set_int("log_" #x, "log_" #x ":", line, &log_ ## x)
static void
read_config_line(char *line)
{
char *opt;
if (!isalpha((unsigned char)line[0])) {
LOG(12, other, ("(ign) `%s'", line));
return;
}
LOG(12, other, ("(parse) `%s'", line));
if (!(OPT_SET_STRING(username) ||
OPT_SET_STRING(password) ||
OPT_SET_STRING(host) ||
OPT_SET_STRING(remotedir) ||
OPT_SET_STRING(localdir) ||
OPT_SET_STRING(dircmd) ||
OPT_SET_STRING(dircmd_tz) ||
OPT_SET_STRING(decompressor) ||
OPT_SET_STRING(decompressor_opt) ||
OPT_SET_STRING(port) ||
OPT_SET_STRING(pidfile) ||
OPT_SET_LOG(addrinfo) ||
OPT_SET_LOG(bug) ||
OPT_SET_LOG(cmd) ||
OPT_SET_LOG(cmdfail) ||
OPT_SET_LOG(connect) ||
OPT_SET_LOG(ctrl_connect) ||
OPT_SET_LOG(delsymlink) ||
OPT_SET_LOG(dlexcuse) ||
OPT_SET_LOG(exec) ||
OPT_SET_LOG(eta) ||
OPT_SET_LOG(failure) ||
OPT_SET_LOG(findtz) ||
OPT_SET_LOG(finished) ||
OPT_SET_LOG(getfile) ||
OPT_SET_LOG(hashes) ||
OPT_SET_LOG(inerror) ||
OPT_SET_LOG(mkdir) ||
OPT_SET_LOG(mksymlink) ||
OPT_SET_LOG(other) ||
OPT_SET_LOG(protoerr) ||
OPT_SET_LOG(regmatch) ||
OPT_SET_LOG(reply) ||
OPT_SET_LOG(rmdir) ||
OPT_SET_LOG(rmfile) ||
OPT_SET_LOG(setopt) ||
OPT_SET_LOG(timestamp) ||
OPT_SET_LOG(totsize) ||
OPT_SET_LOG(weird) ||
OPT_SET_LOG(willget) ||
OPT_SET_LOG(willmakedir) ||
OPT_SET_INT(dirmode) ||
OPT_SET_INT(loglevel) ||
OPT_SET_INT(compressed) ||
OPT_SET_INT(timefuzz) ||
OPT_SET_INT(file_and_mask) ||
OPT_SET_INT(file_or_mask) ||
OPT_SET_INT(passive) ||
OPT_SET_INT(nodel) ||
OPT_SET_INT(try_reget) ||
OPT_SET_INT(use_mdtm) ||
OPT_SET_INT(remotetz) ||
OPT_SET_INT(verbosity) ||
OPT_SET_INT(keep_newer) ||
OPT_SET_INT(reset_times) ||
OPT_SET_INT(input_timeout) ||
OPT_SET_INT(connect_timeout) ||
OPT_SET_INT(connect_retries) ||
OPT_SET_INT(reconnect_timeout) ||
OPT_SET_INT(reconnect_retries) ||
((opt = smatch("exclude:", line)) && add_regex(A_EXCL, opt)) ||
((opt = smatch("include:", line)) && add_regex(A_INCL, opt))))
LOG(1, inerror, ("Junk in config-line: '%s'", line));
}
static void
read_config_file(const char *filename)
{
char line[MAX_LINE_LEN];
FILE *f = fopen(filename, "r");
if (!f) {
fprintf(stderr, "Could not open config-file `%s': %s\n",
filename, strerror(errno));
return;
}
LOG(4, other, ("Reading config-file `%s'", filename));
while (fgets(line, sizeof(line), f)) {
strip_newline(line);
read_config_line(line);
}
fclose(f);
}
static int
add_regex(enum action act, char *opt)
{
char *flags, *reg;
int err;
struct reg_list *re = malloc(sizeof(struct reg_list));
flags = strtok(opt, "\t ");
reg = strtok(NULL, "\000");
if (!flags || !reg) {
LOG(0, inerror, ("add_regex: illegal argument, missing word"));
return -1;
}
/* strtok didn't neccesarily remove all spaces and tabs */
while (isspace((unsigned char)*reg))
reg++;
LOG(8, other, ("add_regex: %d, flags:`%s', reg:`%s'", act, flags, reg));
re->act = act;
if (strchr(flags, 'p')) {
re->fullpath = 1;
} else if (strchr(flags, 'f')) {
re->fullpath = 0;
} else {
LOG(0, inerror,
("add_regex: ILLEGAL flags `%s' - needs f or p", flags));
free(re);
return -1;
}
re->normal = !strchr(flags, 'x');
re->act |= (A_NODEL * !!strchr(flags, 'n'));
re->regex = malloc(sizeof(regex_t));
err = regcomp(re->regex, reg, REG_EXTENDED |
(REG_ICASE * (!!(strchr(flags, 'i')))) | REG_NOSUB);
if (err) {
LOG(0, failure, ("regcomp failed on `%s' : %d", reg, err));
free(re->regex);
free(re);
return -1;
} else {
LOG(8, other, ("regcomp successful on `%s'", reg));
}
re->regex_text = strdup(reg);
re->next = NULL;
if (!last_regex) {
first_regex = last_regex = re;
} else {
last_regex->next = re;
last_regex = re;
}
return 1;
}
static int
parse_args(int argc, char *const *argv)
{
while (1) {
int c = getopt(argc, argv,
"46A:C:c:D:d:e:f:F:hi:kl:M:m:NO:o:P:p:Rr:s:ST:t:u:vV:x:z:");
if (c == -1)
break;
switch (c) {
case '4':
af = AF_INET;
break;
case '6':
af = AF_INET6;
break;
case 'A':
file_and_mask = strtoul(optarg, NULL, 0);
break;
case 'C':
read_config_line(optarg);
break;
case 'N':
nodel = 1;
break;
case 'D':
dircmd_tz = strdup(optarg);
break;
case 'c':
if (!dircmd)
dircmd = strdup(optarg);
else
fprintf(stderr, "Option -c %s ignored\n", optarg);
break;
case 'd':
loglevel = strtol(optarg, (char **)NULL, 0);
break;
case 'e':
add_regex(A_EXCL, optarg);
break;
case 'f':
read_config_file(optarg);
break;
case 'F':
if (!pidfile)
pidfile = strdup(optarg);
else
fprintf(stderr, "Option -F %s ifnored\n", pidfile);
break;
case 'h':
printf("Usage: %s [OPTIONS]\n\n%s", argv[0], usage);
exit(EXIT_SUCCESS);
break;
case 'i':
add_regex(A_INCL, optarg);
break;
case 'l':
if (!localdir)
localdir = strdup(optarg);
else
fprintf(stderr, "Option -l %s ignored\n", optarg);
break;
case 'm':
if (!dirmode)
dirmode = strtoul(optarg, NULL, 0);
else
fprintf(stderr, "Option -m %s ignored\n", optarg);
break;
case 'O':
file_or_mask = strtoul(optarg, NULL, 0);
break;
case 'o':
if (!decompressor_opt)
decompressor_opt = strdup(optarg);
else
fprintf(stderr, "Option -o %s ignored\n", optarg);
break;
case 'P':
if (!port)
port = strdup(optarg);
else
fprintf(stderr, "option -P %s ignored\n", optarg);
break;
case 'p':
if (!password)
password = strdup(optarg);
else
fprintf(stderr, "Option -p %s ignored\n", optarg);
break;
case 'r':
if (!remotedir)
remotedir = strdup(optarg);
else
fprintf(stderr, "Option -r %s ignored\n", optarg);
break;
case 's':
if (!host)
host = strdup(optarg);
else
fprintf(stderr, "Option -s %s ignored\n", optarg);
break;
case 'S':
passive = 1;
break;
case 'T':
remotetz = 60 * strtol(optarg, NULL, 0);
break;
case 't':
timefuzz = strtol(optarg, NULL, 0);
break;
case 'u':
if (!username)
username = strdup(optarg);
else
fprintf(stderr, "Option -u %s ignored\n", optarg);
break;
case 'V':
verbosity = strtoul(optarg, NULL, 0);
break;
case 'M':
use_mdtm = strtoul(optarg, NULL, 0);
break;
case 'x':
if (!decompressor)
decompressor = strdup(optarg);
else
fprintf(stderr, "Option -x %s ignored\n", optarg);
break;
case 'z':
compressed = 1;
break;
case 'k':
keep_newer = 1;
break;
case 'R':
reset_times = 1;
break;
case 'v':
fprintf(stderr, "Fmirror version " VER ".\n");
/* fallthrough */
default:
return 0;
}
}
if (optind < argc) {
fprintf(stderr, "Abort: Stray command line params '%s'...\n",
argv[optind]);
exit(EXIT_FAILURE);
}
return 1;
}
static int
is_group(const char *s)
{
/* return 1 if <s> contains group (in ls listing), 0 if not.
* string can look like
* "finnag 5047 Jan 17 21:26 fmirror.log" (in which case we return 0) or
* "finnag foogroup 5047 Jan 17 21:26 fmirror.log" */
if (!isdigit((unsigned char)*s))
return 1; /* size is always a number, so this is group */
/* skip to next word */
s += strcspn(s, whitespace);
s += strspn(s, whitespace);
if (isdigit((unsigned char)*s))
return 1; /* second word is a number, so string includes group */
return 0;
}
static time_t
make_ctime_from_ti(struct timeinfo *ti)
{
struct tm tm;
time_t t;
tm.tm_sec = ti->second;
tm.tm_min = ti->minute;
tm.tm_hour = ti->hour;
tm.tm_mday = ti->day;
tm.tm_mon = ti->month;
tm.tm_year = ti->year - 1900;
tm.tm_isdst = 0;
t = utc_mktime(&tm);
if (t == (time_t)-1) {
LOG(0, failure,
("Cannot run mktime on %04d/%02d/%02d %02d:%02d:%02d: %s",
(int)tm.tm_year + 1900, (int)tm.tm_mon + 1, (int)tm.tm_mday,
(int)tm.tm_hour, (int)tm.tm_min, (int)tm.tm_sec,
strerror(errno)));
}
return t + ti->gmtoff;
}
/* parse_filename expects one line of output from a LIST command, and
* returns 1 if an entry was found (with parse_info filled in), or 0
* if not. "." and ".." are not considered good entries, so
* parse_filename will return 0 for those. */
static int
parse_filename(char *pre, char *name, struct parse_info *p)
{
static int last_was_blank = 0;
int len;
char *buf;
LOG(11, other, ("parse_filename(\"%s\", \"%s\")", pre, name));
len = strlen(name);
if (len < 25) {
if (!len) {
LOG(11, other, ("\"%s\" is blank", name));
last_was_blank = 1;
}
return 0;
}
p->filename = NULL;
p->fileonlyname = NULL;
p->linkname = NULL;
p->size = 0;
p->perm = 0;
buf = name; /* buf is now permissions */
p->perm = Perm2Mod(buf);
if (p->perm == 0xffff || (last_was_blank && name[len - 1] == ':'))
return 0; /* Very likely that this is a new directory */
last_was_blank = 0;
LOG(9, other, ("Permission %s = 0%o", buf, p->perm));
buf = name + 10; /* Skip permission fields */
buf = strtok(buf, whitespace); /* buf is now i-node count */
buf = strtok(NULL, whitespace); /* buf is now owner */
buf = strtok(NULL, ""); /* group or size ++ */
if (!buf)
return 0;
while (isspace((unsigned char)*buf))
buf++;
/* Try to detect whether we are now looking at size or group. */
if (is_group(buf)) {
LOG(8, other, ("<%s> includes group-info", buf));
buf += strcspn(buf, whitespace);
buf += strspn(buf, whitespace); /* skip the group */
} else {
LOG(8, other, ("<%s> does not include group-info", buf));
}
buf = strtok(buf, whitespace); /* buf is now size */
if (!buf) {
return 0;
}
p->size = strtol(buf, (char **)NULL, 10);
buf = strtok(NULL, "");
if (!buf) {
return 0;
}
/* memcpy(datestr, buf, 12); */
buf[12] = 0;
/* datestr[12] = 0; */
{
struct timeinfo *ti;
if ((ti = Date2Min(buf))) {
time_t t;
LOG(7, other, ("Parsed date: y%d m%d d%d h%d m%d", ti->year,
ti->month, ti->day, ti->hour, ti->minute));
t = make_ctime_from_ti(ti);
p->date = t;
p->t = *ti;
LOG(7, other, ("Which translates to %d (%s)", (int)t,
strip_newline(ctime(&t))));
} else {
LOG(4, weird, ("Failed to parse datestring `%s'", buf));
}
}
buf += 13;
if (!*buf)
return 0;
if (strcmp(".", buf) && (strcmp("..", buf))) {
int fonly_pos = 0;
char buffer[3072];
if (strcmp(".", pre) == 0) {
sprintf(buffer, "%s", buf);
} else {
sprintf(buffer, "%s/%s", pre, buf);
fonly_pos = strlen(pre) + 1;
}
if (S_ISLNK(p->perm)) {
/* There doesn't seem to be a sane way to do this if the
* linkname or whatever it points to includes " -> ". */
char *s = strstr(buffer, " -> ");
if (!s) {
LOG(4, weird, ("Could not make link-name of `%s'", buffer));
return 0;
} else {
*s = 0;
p->filename = strdup(buffer);
p->fileonlyname = &p->filename[fonly_pos];
p->linkname = strdup(s + 4);
LOG(9, other, ("Sym-link resolved: `%s' to `%s'", p->filename,
p->linkname));
}
} else {
p->filename = strdup(buffer);
p->fileonlyname = &p->filename[fonly_pos];
}
LOG(7, other, ("filename: `%s'", buf));
return 1;
} else {
LOG(9, other, ("Ignoring `.' or `..' (%s)", buf));
return 0;
}
}
static void
clog(const char *s)
{
time_t it = time(0);
struct tm *t = localtime(&it);
if (log_timestamp) {
fprintf(stderr, "%02d:%02d:%02d %s\n",
t->tm_hour, t->tm_min, t->tm_sec, s);
} else {
fprintf(stderr, "%s\n", s);
}
}
static int
logit(const char *s, ...)
{
int len;
char buffer[1024];
va_list args;
va_start(args, s);
len = vsprintf(buffer, s, args);
va_end(args);
clog(buffer);
return len;
}
static void
init_Perm2Mod(void)
{
int m, n;
for(m = 0; m < 10; m++) {
for(n = 0; n < 256; n++) {
Per[m][n] = 0xFFFF;
}
}
Per[9]['-'] = 0x0000, Per[9]['x'] = 0x0001, Per[9]['T'] = 0x0200;
Per[9]['t'] = 0x201; Per[8]['-'] = 0x0000, Per[8]['w'] = 0x0002;
Per[7]['-'] = 0x0000, Per[7]['r'] = 0x0004;
Per[6]['-'] = 0x0000, Per[6]['x'] = 0x0008, Per[6]['S'] = 0x0400,
Per[6]['s'] = 0x0408;
Per[5]['-'] = 0x0000, Per[5]['w'] = 0x0010;
Per[4]['-'] = 0x0000, Per[4]['r'] = 0x0020;
Per[3]['-'] = 0x0000, Per[3]['x'] = 0x0040, Per[3]['S'] = 0x0800,
Per[3]['s'] = 0x0840;
Per[2]['-'] = 0x0000, Per[2]['w'] = 0x0080;
Per[1]['-'] = 0x0000, Per[1]['r'] = 0x0100;
Per[0]['-'] = 0x8000, Per[0]['d'] = 0x4000, Per[0]['l'] = 0xa000;
/* Cray compatibility */
Per[0]['m'] = 0x8000; /* migrated file */
/* ??? Compatibility */
Per[0]['F'] = 0x8000; /* regular file */
Per[0]['D'] = 0x4000; /* directory */
Per[0]['L'] = 0xa000; /* symbolic link */
Per[0]['c'] = 0x2000; /* character device */
Per[0]['b'] = 0x6000; /* block device */
Per[0]['p'] = 0x1000; /* named pipe */
Per[0]['s'] = 0xc000; /* unix domain socket */
/* Mandatory file locking compatibility */
Per[6]['l'] = 0x400; /* i.e. same as S_ISGID */
}
static int
Perm2Mod(char *perm)
{
int a=0;
int n;
for(n = 0; n < 10; n++) a = a | (int)Per[n][(unsigned char)perm[n]];
return a;
}
static struct timeinfo *
Date2Min(const char *date)
{
static struct timeinfo ti;
int error = 0;
int year;
int day;
int month;
int hour;
int minute;
if (date[9] == ':') {
if (date[7] != ' ')
hour = (date[7] - '0') * 10 + (date[8] - '0');
else
hour = date[8] - '0';
if (hour >= 24 || hour < 0 ||
(date[7] != ' ' && (date[7] < '0' || date[7] > '2')) ||
date[8] < '0' || date[8] > '9')
error = 1;
if (date[10] != ' ')
minute = (date[10] - '0') * 10 + (date[11] - '0');
else
minute= date[11] - '0';
if (minute >= 60 || minute < 0 ||
(date[10] != ' ' && (date[10] < '0' || date[10] > '5')) ||
date[11] < '0' || date[11] > '9')
error = 1;
year = 0;
} else {
hour = 0;
minute = 0;
year = strtoul(((const char *)&date[7]), (char **)NULL, 10);
if (year < 1900 || year > 2094)
error = 1;
}
if (date[4] != ' ')
day = (date[4] - '0') * 10 + (date[5] - '0');
else
day = date[5] - '0';
if (day <= 0 || day > 31 ||
(date[4] != ' ' && (date[4] < '0' || date[4] > '3')) ||
date[5] < '0' || date[5] > '9')
error = 1;
month = -1;
switch(date[0]) {
case 'J':
switch(date[1]) {
case 'a':
if (date[2] == 'n') month = 0;
break;
case 'u':
switch(date[2]) {
case 'n':
month = 5;
break;
case 'l':
month = 6;
break;
}
break;
}
break;
case 'F':
if ((date[1] == 'e') && (date[2] == 'b')) month = 1;
break;
case 'M':
switch(date[2]) {
case 'r':
if (date[1] == 'a') month = 2;
break;
case 'y':
if (date[1] == 'a') month = 4;
break;
}
break;
case 'A':
switch(date[1]) {
case 'p':
if (date[2] == 'r') month = 3;
break;
case 'u':
if (date[2] == 'g') month = 7;
break;
}
break;
case 'S':
if ((date[1] == 'e') && (date[2] == 'p')) month = 8;
break;
case 'O':
if ((date[1] == 'c') && (date[2] == 't')) month = 9;
break;
case 'N':
if ((date[1] == 'o') && (date[2] == 'v')) month = 10;
break;
case 'D':
if ((date[1] == 'e') && (date[2] == 'c')) month = 11;
break;
}
if (month == -1)
error = 1;
if (error)
return NULL;
ti.gmtoff = remotetz;
ti.month = month;
ti.day = day;
ti.hour = hour;
ti.minute = minute;
ti.year = year;
if (!ti.year) {
/* The ls listing did not contain year, this means the file
* cannot (should not) be more than a year old. Try to guess
* whether it's the current year or last year.. */
ti.year = thisyear;
/* First do 2 simple heuristics that work with _most_ ftp servers.
* (some will not print year unless the file is ~a year old,
* and need the 3rd check) */
if (month > thismonth + 1) {
--ti.year;
} else if (month < thismonth - 1) {
/* do nothing, we know it's this year */
} else {
/* Do a more thorough check to see if it is less than one
* day into the future if we assume the year is thisyear */
time_t t = make_ctime_from_ti(&ti);
if (t != (time_t)-1) {
double d = difftime(t, time(0));
LOG(8, other, ("Tdiff for ls-entry without year: %d seconds",
(int)d));
if (d >= (24 * 3600)) {
/* Don't allow dates more than one day into the future
* to be specified without year in ls-listings */
--ti.year;
}
}
}
}
return &ti;
}
static int
mkdir_mode(const char *name, int mode)
{
int ret = mkdir(name, mode);
if (ret == 0)
chmod(name, mode);
return ret;
}
static int is_newer(time_t local, time_t remote)
{
return local > remote;
}
static void set_time(const char *name, time_t mtime)
{
struct utimbuf ut;
ut.actime = mtime;
ut.modtime = mtime;
if (utime(name, &ut)) {
LOG(0, failure, ("-- ERROR -- [1] utime(\"%s\", %u) : %s", name,
(uint)mtime, strerror(errno)));
}
}
static unsigned long mdtot[12] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
static time_t
utc_mktime(struct tm *tm)
{
time_t t;
long year = tm->tm_year - 70;
long month = tm->tm_mon; /* 0..11 */
long day = tm->tm_mday - 1; /* 0..30 */
long hour = tm->tm_hour;
long min = tm->tm_min;
long sec = tm->tm_sec; /* 0..59 (+ leap second on bad days) */
/* Calculate number of added leap days from 1970 and up to now */
long lcyear = year + (month > 1);
long lyears = (lcyear + 1) / 4 - (lcyear + 69) / 100
+ (lcyear + 369) / 400;
day += year * 365;
day += mdtot[month];
day += lyears;
hour += day * 24;
min += hour * 60;
sec += min * 60;
t = sec;
return t;
}
static int
sig_permanent(int sig, RETSIGTYPE (*handler)(int))
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = handler;
return sigaction(sig, &sa, NULL);
}
static int
make_connected_socket(struct sockaddr *sad, size_t addrlen)
{
int n = connect_retries;
char numaddr[64];
unsigned short cport = 0;
while (n--) {
int err;
int sockfd = socket(sad->sa_family, SOCK_STREAM, 0);
if(sockfd < 0) {
LOG(0, failure, ("-- ERROR -- creating socket: %s",
strerror(errno)));
exit(EXIT_FAILURE);
}
alarm(connect_timeout);
err = connect(sockfd, (struct sockaddr *)sad, addrlen);
alarm(0);
if (err == -1) {
switch(sad->sa_family) {
case AF_INET:
inet_ntop(sad->sa_family,
&((struct sockaddr_in *)sad)->sin_addr,
numaddr, sizeof(numaddr));
cport = ((struct sockaddr_in *)sad)->sin_port;
break;
case AF_INET6:
inet_ntop(sad->sa_family,
&((struct sockaddr_in6 *)sad)->sin6_addr,
numaddr, sizeof(numaddr));
cport = ((struct sockaddr_in6 *)sad)->sin6_port;
break;
default:
LOG(0, failure,
("make_connected_socket: wrong AF specification"));
break;
}
LOG(0, failure,
("Connection to %s port %d failed: %s [%s]",
numaddr,
cport,
errno == EINTR ? "Connection timed out" : strerror(errno),
n ? "retrying" : "giving up"));
close(sockfd);
} else {
if (getsockname(sockfd, (struct sockaddr *)&localaddr, &addrlen)) {
LOG(0, failure, ("getsockname fails, expect PORT to fail..: %s",
strerror(errno)));
}
switch(sad->sa_family) {
case AF_INET:
inet_ntop(sad->sa_family,
&((struct sockaddr_in *)&localaddr)->sin_addr,
numaddr, sizeof(numaddr));
break;
case AF_INET6:
inet_ntop(sad->sa_family,
&((struct sockaddr_in6 *)&localaddr)->sin6_addr,
numaddr, sizeof(numaddr));
break;
}
LOG(0, failure, ("make_connected_socket: local address: %s",
numaddr));
return sockfd;
}
}
return -1;
}
static int
accept_connection(int fd, struct sockaddr *sad)
{
int l = sizeof(*sad);
alarm(connect_timeout); /* die if no connection in 70 seconds */
fd = accept(fd, (struct sockaddr *)sad, &l);
alarm(0);
return fd;
}
static int
make_passive_connection(struct connection *c)
{
size_t addrlen = dst_addrlen;
char numaddr[64];
c->fd = make_connected_socket((struct sockaddr *)(&c->dest), addrlen);
if (c->fd == -1) {
inet_ntop(c->af, &(c->dest), numaddr, sizeof(numaddr));
LOG(0, failure, ("Could not open passive connection to %s port %d",
numaddr,
c->dport));
return -1;
}
#if (defined(IP_TOS) && defined(IPTOS_THROUGHPUT))
{
int opt = IPTOS_THROUGHPUT;
setsockopt(c->fd, IPPROTO_IP, IP_TOS, (char *)&opt, sizeof(opt));
}
#endif
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1