/*
 * Copyright (C) 2005  Roy Keene
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Author Information
 *      Roy Keene
 *      Planning Systems Inc
 *      Slidell, LA
 *      backuppcd-bugs@psislidell.com
 */

#include "compat.h"
#include "backuppcd.h"
#include "backuppcd-common.h"
#include "net.h"
#include "sha1.h"

#define DAEMON_RET_SUCCESS 0
#define DAEMON_RET_FAILURE 1

#define LOOP_RUN 0
#define LOOP_STOP 1
#define LOOP_DONE 2

#ifndef SKIPSTARTFILE
#define SKIPSTARTFILE SYSCONFDIR "/backuppcd-disable"
#endif

#ifndef O_BINARY
#define O_BINARY 0
#endif
#ifndef O_LARGEFILE
#define O_LARGEFILE 0
#endif
#ifndef SIGPIPE
#define SIGPIPE 13
#endif

#ifndef BPC_MAXUSERNAME_LEN
#define BPC_MAXUSERNAME_LEN 128
#endif
#ifndef BPC_MAXPASSWORD_LEN
#define BPC_MAXPASSWORD_LEN 256
#endif
#ifndef BPC_MAXPATH_LEN
#define BPC_MAXPATH_LEN 4096
#endif
#ifndef BPC_MAXOUTBUF_LEN
#define BPC_MAXOUTBUF_LEN 819200
#endif
#ifndef BPC_OUTBUF_LEN
#define BPC_OUTBUF_LEN 32768
#endif
#ifndef BPC_BUF_LEN
#define BPC_BUF_LEN 32768
#endif
#ifndef BPC_UPDATE_INTERVAL
#define BPC_UPDATE_INTERVAL 300
#endif

/*
 * Default master password:  This value will never match any SHA1 hash.
 */
#ifndef MASTER_PASSWORD
#define MASTER_PASSWORD "7505d64a54e061b7acd54ccd58b49dc43500b63x"
#endif

/*
 * This global variable is used to coordinate the shutdown of the main work
 * loop (backuppc_loop()).
 */
static int WorkLoopStatus = LOOP_RUN;

/*
 * These variables are global because the configuration is done by a seperate
 * function.
 */
static int backuppc_port = 874;
static uint32_t backuppc_writeblock_size = 32000;
static char *backuppc_updateurl = NULL;
static char *backuppc_binfile = NULL;

#ifdef _USE_WIN32_
/*
 * Win32 service stuff.
 */
static SC_HANDLE manager = NULL;
static SC_HANDLE service = NULL;
static SERVICE_STATUS backuppcServiceStat;
static SERVICE_STATUS_HANDLE backuppcServiceStat_handle = (SERVICE_STATUS_HANDLE) NULL;
static char svcName[] = "BackupPC";
#endif

/*
 * These define the different privilege levels.
 */
typedef enum {
	BPC_PRIV_NONE,
	BPC_PRIV_READ,
	BPC_PRIV_WRITE,
	BPC_PRIV_RDWR
} backuppc_privs_t;

/*
 * These are symbolic names to use to mark when a message datum has been sent.
 */
typedef enum {
	BPC_SM_NONE,
	BPC_SM_HEADER,
	BPC_SM_PKT_HEADER,
	BPC_SM_CLEANUP,
	BPC_SM_PATHNAME,
	BPC_SM_DATA_CLEANUP,
	BPC_SM_AUTHSTATUS,
	_BPC_SM_SIZE /* Must be the last item*/
} backuppc_send_mark_t;

/*
 * These are symbolic names to use to mark when a message datum has been
 * recieved.
 */
typedef enum {
	BPC_RM_NONE,
	BPC_RM_HEADER,
	BPC_RM_PKT_HEADER,
	BPC_RM_PATHNAME,
	BPC_RM_AUTHDATA,
	_BPC_RM_SIZE /* Must be the last item. */
} backuppc_recv_mark_t;

/*
 * These define the different data types that backuppc_readvalues() and
 * backuppc_writevalues() understand.
 */
typedef enum {
	BPC_DT_END,
	BPC_DT_UINT8,
	BPC_DT_UINT16,
	BPC_DT_UINT32,
	BPC_DT_UINT64,
	BPC_DT_BYTEARRAY,
	BPC_DT_BYTEARRAY_PTR,
	BPC_DT_STRING,
	BPC_DT_STRING_PTR,
} backuppc_datatypes_t;

/*
 * Every client that connects is given a node of this type to handle its
 * buffering.
 *
 * The (void *) process_handle entry is for the backuppc_process_client()
 * call to store data in.  It should be filled out by
 * backuppc_process_client_init() and cleaned by
 * backuppc_process_client_fini().
 */
struct backuppc_client_info;
struct backuppc_client_info {
	struct in_addr addr;
	unsigned char *buf;
	unsigned char *buf_s;
	unsigned long bufsize;
	unsigned long bufsize_s;
	unsigned long bufused;
	unsigned char *outbuf;
	unsigned char *outbuf_s;
	unsigned long outbufsize;
	unsigned long outbufsize_s;
	unsigned long outbufused;
	void *process_handle;
	int outdata_waiting;
	int tx_error_count;
	int rx_error_count;
	int invalid_data;
	int fd;
	backuppc_privs_t privs;
	struct backuppc_client_info *_next;
};

/*
 * backuppc_process_client()'s private data storage, maintains things like
 * state of the current command and various other items across calls.
 */
struct backuppc_client_prochandle {
	struct backuppc_dirent *dent;
	backuppc_cmd_t cmd;
	unsigned char *tmpbuf;
	uint16_t username_len;
	uint16_t password_len;
	uint32_t pathname_len;
	uint32_t excl_sect_len;
	uint32_t incl_sect_len;
	uint32_t block_num;
	uint8_t options;
	size_t tmpbufsize;
	size_t tmpbufused;
	size_t bytes_written;
	size_t attrlen;
	char *username;
	char *password;
	char *pathname;
	char *data;
	char attrdata[4096];
	void *dh[256];
	int dhidx;
	int sent_sect[_BPC_SM_SIZE];
	int recv_sect[_BPC_RM_SIZE];
	int wv_idx;
	int rv_idx;
};

/*
 * Structure pointed to by the backuppc_readdir()
 *
 * Holds information about the file, as well as a file descriptor if opened by
 * backuppc_openfile().
 */
struct backuppc_dirent {
	backuppc_dirent_type_t type;
	unsigned char md5[16];
	const char *name;
	char fullpathname[BPC_MAXPATH_LEN];
	char *symlinkdest;
	char *hrdlinkdest;
	size_t size;
 	dev_t dev;
	mode_t mode;
#ifndef _USE_WIN32_
	nlink_t nlink;
	uid_t uid;
	gid_t gid;
#else
	uint32_t uid;
	uint32_t gid;
#endif
	uint64_t blocks;
	uint32_t blksize;
	dev_t rdev;
	time_t mtime;
	time_t ctime;
	int md5_set;
	int issparse;
	int fd;
};

struct backuppc_inode_list;
struct backuppc_inode_list {
	ino_t inode_num;
	char *fullpathname;
	struct backuppc_inode_list *_next;
};

/*
 * Private structure handle used by backuppc_opendir() and backuppc_closedir()
 */
typedef struct backuppc_dirhandle {
	struct backuppc_inode_list *ilist[4097];
	struct backuppc_dirent ret;
	char *pat_exclude;
	char *pat_include;
	char *pathname;
	DIR *handle;
	int lastdrivecheck;
	int isfile;
} BPC_DIR;



char *sha1sum(char *string) {
	unsigned char digest[20];
	static char ret[(sizeof(digest) * 2) + 1] = {0};
	static char hexabet[] = "0123456789abcdef";
	SHA1_CTX ctx;
	int retcnt = 0, i;

	SHA1Init(&ctx);

	SHA1Update(&ctx, string, strlen(string));

	SHA1Final(digest, &ctx);

	for (i = 0; i < sizeof(digest); i++) {
		ret[retcnt++] = hexabet[(digest[i] & 0xf0) >> 4];
		ret[retcnt++] = hexabet[digest[i] & 0xf];
	}

	ret[retcnt] = '\0';

	return(ret);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function compiles to a no-op on platforms that already have a single
 *   unified namespace.
 *
 *   On Win32 this function changes pathnames containing a drive letter style
 *   pathname to a more sane name including the leading slash.
 *
 */
static void backuppc_pathmangle(char *pathname) {
#ifdef _USE_WIN32_
	char drive_letter;
	
	if (strlen(pathname) < 2) {
		return;
	}

	if (pathname[0] == '/' && isalpha(pathname[1])) {
		return;
	}

	if (pathname[1] != ':' || !isalpha(pathname[0])) {
		return;
	}

	drive_letter = pathname[0];
	pathname[0] = '/';
	pathname[1] = drive_letter;

#endif
	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathunmangle(
 *                                     char *pathname
 *                                    );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to unmangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function compiles to a no-op on platforms that already have a single
 *   unified namespace.
 *
 *   On Win32 this function changes pathnames containing a sane pathname to
 *   a pathname that contains a drive letter and can be used to open or
 *   otherwise access the file.
 *
 */
static void backuppc_pathunmangle(char *pathname) {
#ifdef _USE_WIN32_
	char drive_letter;
	
	if (strlen(pathname) < 2) {
		return;
	}

	if (pathname[1] == ':' && isalpha(pathname[0])) {
		return;
	}

	if (pathname[0] != '/' || !isalpha(pathname[1])) {
		return;
	}

	drive_letter = pathname[1];
	pathname[0] = drive_letter;
	pathname[1] = ':';

#endif
	return;
}

/*
 * SYNOPSIS:
 *   static BPC_DIR *backuppc_opendir(
 *                                    char *pathname
 *                                   );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to the directory to open
 *
 * RETURN VALUE:
 *   This function returns a pointer to a handle that can be used with
 *   backuppc_readdir() and backuppc_closedir().  It returns NULL on
 *   error.
 *
 * NOTES:
 *   If you backuppc_opendir() a file, you get a handle that when you call
 *   backuppc_readdir() gives you exactly one entry, the pathname specified.
 *   Thus you should not assume that the result of backuppc_readdir() gives
 *   a child-node in the directory heirarchy of the pathname specified when
 *   this function is called.  the (struct backuppc_dirent).fullpathname
 *   value should be used instead to get the full path to the relevant node.
 *
 */
static BPC_DIR *backuppc_opendir(char *pathname) {
	BPC_DIR *dh;
	struct stat stbuf;
	int stat_ret;
	int i;

	if (pathname[0] != '/') {
		return(NULL);
	}

	dh = malloc(sizeof(*dh));

	if (!dh) {
		return(NULL);
	}

	dh->pat_exclude = NULL;
	dh->pat_include = NULL;
	dh->isfile = 0;

	for (i = 0; i < (sizeof(dh->ilist) / sizeof(dh->ilist[0])); i++) {
		dh->ilist[i] = NULL;
	}

#ifdef _USE_WIN32_
	if (strlen(pathname) < 2) {
		dh->pathname = strdup("/");
		dh->handle = NULL;
		dh->lastdrivecheck = 0;
		return(dh);
	}
#endif

	backuppc_pathunmangle(pathname);
	dh->handle = opendir(pathname);
	backuppc_pathmangle(pathname);

	if (!dh->handle) {
		backuppc_pathunmangle(pathname);
#ifdef HAVE_LSTAT
		stat_ret = lstat(pathname, &stbuf);
#else
		stat_ret = stat(pathname, &stbuf);
#endif
		backuppc_pathmangle(pathname);

		if (stat_ret < 0) {
			free(dh);
			dh = NULL;
			return(dh);
		}

		if (S_ISDIR(stbuf.st_mode)) {
			free(dh);
			dh = NULL;
			return(dh);
		}

		dh->isfile = 1;
	}

	dh->lastdrivecheck = 0;
	dh->pathname = strdup(pathname);

	dh->ret.symlinkdest = NULL;
	dh->ret.hrdlinkdest = NULL;

	return(dh);
}

/*
 * SYNOPSIS:
 *   static struct backuppc_dirent *backuppc_readdir(
 *                                                   BPC_DIR *dh
 *                                                  );
 *
 * ARGUMENTS:
 *   BPC_DIR *dh                  Directory stream handle, from backuppc_opendir()
 *
 * RETURN VALUE:
 *   This function returns a pointer to a (struct backuppc_dirent).  Every
 *   call will return the same pointer for the specified directory handle.
 *   This function returns NULL when there are no more nodes that can be
 *   processed available in the stream or an error occurs.
 *
 * NOTES:
 *   Under Win32, this function presents the children of "/" as the letters
 *   of all drives that are not "removable", "remote", or "cdrom".
 *
 *   The size for non-regular file nodes will always be zero, regardless of
 *   how many blocks they actually take up on the disk.
 *
 */
static struct backuppc_dirent *backuppc_readdir(BPC_DIR *dh) {
	struct backuppc_inode_list *cur_inode;
	struct dirent *dent;
	struct stat stbuf;
	size_t pathnamelen;
#ifdef _USE_WIN32_
	DWORD drives;
	UINT drive_type;
	char drive_letter;
	char drive_letter_str[32];
	int i, drive_exists;
#endif
	int stat_ret, readlink_ret;
	int hash_idx;

	if (!dh) {
		return(NULL);
	}

	if (!dh->handle && !dh->isfile) {
#ifdef _USE_WIN32_
		/* On Win32, dh->handle being NULL indicates the fake root
		   directory we are to construct. */
		drives = GetLogicalDrives();

		for (i = dh->lastdrivecheck; i < (sizeof(drives) * 8); i++) {
			drive_exists = drives & (1 << i);

			if (drive_exists) {
				drive_letter = 'A' + i;

				/* The trailing slashes are required here,
				   otherwise Windows thinks we mean the
				   current directory on that drive instead 
				   of the root directory. */
				sprintf(drive_letter_str, "%c:\\", drive_letter);
				snprintf(dh->ret.fullpathname, sizeof(dh->ret.fullpathname), "/%c/", drive_letter);

				drive_type = GetDriveType(drive_letter_str);

				if (drive_type == DRIVE_REMOVABLE || drive_type == DRIVE_REMOTE || drive_type == DRIVE_CDROM) {
					continue;
				}

				dh->lastdrivecheck = i + 1;
				drive_letter_str[1] = '\0';
				dh->ret.name = strdup(drive_letter_str);
				dh->ret.type = BPC_DIRENT_DIR;
				dh->ret.size = 0;
//				dh->ret.inode = ((0xf << ((sizeof(dh->ret.inode) * 8) - 4)) | ((1 << ((sizeof(dh->ret.inode) * 8) - 1)) - 1)) - i;
				dh->ret.md5_set = 0;
				dh->ret.fd = -1;
				dh->ret.dev = 0;
				dh->ret.rdev = 0;
				dh->ret.mode = 0;
				dh->ret.nlink = 0;
				dh->ret.uid = 0;
				dh->ret.gid = 0;
				dh->ret.blocks = 0;
				dh->ret.mtime = 0;
				dh->ret.ctime = 0;
				return(&dh->ret);
			}
		}
#endif
		return(NULL);
	}

	pathnamelen = strlen(dh->pathname);

	/*
	 * A zero-length pathname never makes sense, and it will break some
	 * calculations below.
	 */
	if (pathnamelen < 1) {
		return(NULL);
	}

	while (1) {
		if (!dh->isfile) {
			dent = readdir(dh->handle);

			if (!dent) {
				break;
			}

			if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) {
				continue;
			}

			if (dh->pathname[pathnamelen - 1] == '/') {
				snprintf(dh->ret.fullpathname, sizeof(dh->ret.fullpathname), "%s%s", dh->pathname, dent->d_name);
			} else {
				snprintf(dh->ret.fullpathname, sizeof(dh->ret.fullpathname), "%s/%s", dh->pathname, dent->d_name);
			}
		} else {
			if (dh->isfile == 2) {
				break;
			}

			snprintf(dh->ret.fullpathname, sizeof(dh->ret.fullpathname), "%s", dh->pathname);

			dh->isfile = 2;
		}

		backuppc_pathunmangle(dh->ret.fullpathname);

#ifdef HAVE_LSTAT
		stat_ret = lstat(dh->ret.fullpathname, &stbuf);
#else
		stat_ret = stat(dh->ret.fullpathname, &stbuf);
#endif

		backuppc_pathmangle(dh->ret.fullpathname);

		if (stat_ret < 0) {
			continue;
		}

		dh->ret.name = strrchr(dh->ret.fullpathname, '/');
		if (!dh->ret.name) {
			continue;
		}
		dh->ret.dev = stbuf.st_dev;
		dh->ret.rdev = stbuf.st_rdev;
		dh->ret.mode = stbuf.st_mode;
		dh->ret.uid = stbuf.st_uid;
		dh->ret.gid = stbuf.st_gid;
#ifdef _USE_WIN32_
		dh->ret.blksize = 1024;
		dh->ret.blocks = (stbuf.st_size + dh->ret.blksize - 1) / dh->ret.blksize;
#else
		dh->ret.blksize = stbuf.st_blksize;
		dh->ret.blocks = stbuf.st_blocks;
#endif
		dh->ret.mtime = stbuf.st_mtime;
		dh->ret.ctime = stbuf.st_ctime;
		dh->ret.md5_set = 0;
		dh->ret.fd = -1;

		switch (stbuf.st_mode & S_IFMT) {
			case S_IFREG:
				dh->ret.type = BPC_DIRENT_FILE;
				break;
#ifdef S_IFLNK
			case S_IFLNK:
				dh->ret.type = BPC_DIRENT_SYMLINK;
#ifdef HAVE_READLINK
				if (!dh->ret.symlinkdest) {
					dh->ret.symlinkdest = malloc(BPC_MAXPATH_LEN);
				}
				readlink_ret = readlink(dh->ret.fullpathname, dh->ret.symlinkdest, BPC_MAXPATH_LEN);
				if (readlink_ret >= 0 && readlink_ret < BPC_MAXPATH_LEN) {
					dh->ret.symlinkdest[readlink_ret] = '\0';
				}
#endif
				break;
#endif
			case S_IFCHR:
				dh->ret.type = BPC_DIRENT_CDEV;
				break;
			case S_IFBLK:
				dh->ret.type = BPC_DIRENT_BDEV;
				break;
			case S_IFDIR:
				dh->ret.type = BPC_DIRENT_DIR;
				break;
			case S_IFIFO:
				dh->ret.type = BPC_DIRENT_FIFO;
				break;
#ifdef S_IFSOCK
			case S_IFSOCK:
				dh->ret.type = BPC_DIRENT_SOCKET;
				break;
#endif
			default:
				dh->ret.type = BPC_DIRENT_UNKNOWN;
				break;
		}

		/*
		 * If a file has a link count greater than one, determine if
		 * we have seen it before, if not note that we've seen it now
		 * so we can look for it later.
		 */
		if (stbuf.st_nlink > 1 && dh->ret.type != BPC_DIRENT_DIR && dh->ret.type != BPC_DIRENT_UNKNOWN) {
			hash_idx = stbuf.st_ino % (sizeof(dh->ilist) / sizeof(dh->ilist[0]));
			for (cur_inode = dh->ilist[hash_idx]; cur_inode; cur_inode = cur_inode->_next) {
				if (cur_inode->inode_num == stbuf.st_ino) {
					dh->ret.type = BPC_DIRENT_HRDLINK;
					dh->ret.hrdlinkdest = cur_inode->fullpathname;
					break;
				}
			}

			if (dh->ret.type != BPC_DIRENT_HRDLINK) {
				cur_inode = malloc(sizeof(*cur_inode));
				cur_inode->inode_num = stbuf.st_ino;
				cur_inode->fullpathname = strdup(dh->ret.fullpathname);
				cur_inode->_next = dh->ilist[hash_idx];
				dh->ilist[hash_idx] = cur_inode;
			}
		}


		if (dh->ret.type == BPC_DIRENT_FILE) {
			dh->ret.size = stbuf.st_size;
		} else {
			dh->ret.size = 0;
		}

		dh->ret.issparse = 0;

		return(&dh->ret);
	}

	return(NULL);
}

/*
 * SYNOPSIS:
 *   static void backuppc_closedir(
 *                                 BPC_DIR *dh);
 *                                );
 *
 * ARGUMENTS:
 *   BPC_DIR *dh                  Directory stream handle, from backuppc_opendir()
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function closes an opened directory stream.  It cleans up any
 *   resources related to that stream.  The handle specified should not be
 *   used once this function has been called.
 *
 */
static void backuppc_closedir(BPC_DIR *dh) {
	struct backuppc_inode_list *cur_inode, *next_inode;
	int hash_idx;

	if (!dh) {
		return;
	}

	if (dh->pathname) {
		free(dh->pathname);
	}

	if (dh->handle) {
		closedir(dh->handle);
	}

	if (dh->pat_exclude) {
		free(dh->pat_exclude);
	}

	if (dh->pat_include) {
		free(dh->pat_include);
	}

	for (hash_idx = 0; hash_idx < (sizeof(dh->ilist) / sizeof(dh->ilist[0])); hash_idx++) {
		cur_inode = dh->ilist[hash_idx];
		while (cur_inode) {
			if (cur_inode->fullpathname) {
				free(cur_inode->fullpathname);
			}

			next_inode = cur_inode->_next;

			free(cur_inode);

			cur_inode = next_inode;
		}
	}


	free(dh);

	return;
}

/*
 * SYNOPSIS:
 *   static int backuppc_openfile(
 *                                struct backuppc_dirent *dent,
 *                                int flags,
 *                                mode_t mode
 *                               );
 *
 * ARGUMENTS:
 *   struct backuppc_dirent *dent Pointer to structure containing information
 *                                about file to open.
 *   int flags                    Flags to open file with, should include one
 *                                of O_RDONLY, O_RDWR, or O_WRONLY.
 *   mode_t mode                  Permissions to set on file if it is to be
 *                                created.
 *
 * RETURN VALUE:
 *   This function returns the file descriptor associated with the open file
 *   on success, or -1 on failure.
 *
 * NOTES:
 *   This function sets the "fd" member of the structure pointed to by the
 *   "dent" parameter to the file descriptor.
 *
 *   If the file "fd" member is already set to a positive value, that value
 *   is returned and the file is not opened nor is its state changed.
 *
 */
static int backuppc_openfile(struct backuppc_dirent *dent, int flags, mode_t mode) {
	char *pathname;
	int fd;

	if (dent->fd >= 0) {
		return(dent->fd);
	}

	if (dent->type != BPC_DIRENT_FILE) {
		return(-1);
	}

	pathname = dent->fullpathname;

	backuppc_pathunmangle(pathname);

	fd = open(pathname, flags | O_LARGEFILE | O_BINARY, mode);

	backuppc_pathmangle(pathname);

	if (fd < 0) {
		return(-1);
	}

	dent->fd = fd;

	return(fd);
}

/*
 * SYNOPSIS:
 *   static int backuppc_closefile(
 *                                 struct backuppc_dirent *dent
 *                                );
 *
 * ARGUMENTS:
 *   struct backuppc_dirent *dent Pointer to structure containing information
 *                                about file to close.
 *
 * RETURN VALUE:
 *   Zero is returned on success, or -1 if an error occurs.  It is not
 *   considered an error to close an already closed file.
 *
 * NOTES:
 *
 */
static int backuppc_closefile(struct backuppc_dirent *dent) {
	int fd;

	if (dent->fd < 0) {
		return(0);
	}

	fd = dent->fd;
	dent->fd = -1;

	return(close(fd));
}

/*
 * SYNOPSIS:
 *   static void backuppc_get_attr(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function needs to be written
 *
 */
static ssize_t backuppc_get_attr(struct backuppc_dirent *dent, char *outbuf, size_t outbuflen) {
	char *buf = outbuf;
	uint16_t id;
	uint16_t modeval;
	uint32_t len, timeval, idval;

	if (dent->mtime > 0 && (outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + sizeof(timeval))) {
		id = htons(BPC_ATTRID_MTIME);
		len = htonl(sizeof(timeval)); 
		timeval = htonl(dent->mtime);

		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);

		memcpy(buf, &len, sizeof(len));
		buf += sizeof(len);

		memcpy(buf, &timeval, sizeof(timeval));
		buf += sizeof(timeval);
	}

	if (dent->ctime > 0 && (outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + sizeof(timeval))) {
		id = htons(BPC_ATTRID_CTIME);
		len = htonl(sizeof(timeval)); 
		timeval = htonl(dent->ctime);

		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);

		memcpy(buf, &len, sizeof(len));
		buf += sizeof(len);

		memcpy(buf, &timeval, sizeof(timeval));
		buf += sizeof(timeval);
	}

	/* BPC_ATTRID_USER */
	/* BPC_ATTRID_GROUP */

	if (dent->uid >= 0 && (outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + sizeof(idval))) {
		id = htons(BPC_ATTRID_UID);
		len = htonl(sizeof(idval));
		idval = htonl(dent->uid);

		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);

		memcpy(buf, &len, sizeof(len));
		buf += sizeof(len);

		memcpy(buf, &idval, sizeof(idval));
		buf += sizeof(idval);
	}

	if (dent->gid >= 0 && (outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + sizeof(idval))) {
		id = htons(BPC_ATTRID_GID);
		len = htonl(sizeof(idval));
		idval = htonl(dent->gid);

		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);

		memcpy(buf, &len, sizeof(len));
		buf += sizeof(len);

		memcpy(buf, &idval, sizeof(idval));
		buf += sizeof(idval);
	}

	if (dent->mode != 0 && (outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + sizeof(modeval))) {
		id = htons(BPC_ATTRID_ACL);
		len = htonl(sizeof(modeval));

		modeval = 0;

		if ((dent->mode & S_IRUSR) == S_IRUSR) {
			modeval |= BPC_ACL_RUSR;
		}
		if ((dent->mode & S_IWUSR) == S_IWUSR) {
			modeval |= BPC_ACL_WUSR;
		}
		if ((dent->mode & S_IXUSR) == S_IXUSR) {
			modeval |= BPC_ACL_XUSR;
		}
#ifndef _USE_WIN32_
		if ((dent->mode & S_IRGRP) == S_IRGRP) {
			modeval |= BPC_ACL_RGRP;
		}
		if ((dent->mode & S_IWGRP) == S_IWGRP) {
			modeval |= BPC_ACL_WGRP;
		}
		if ((dent->mode & S_IXGRP) == S_IXGRP) {
			modeval |= BPC_ACL_XGRP;
		}
		if ((dent->mode & S_IROTH) == S_IROTH) {
			modeval |= BPC_ACL_ROTH;
		}
		if ((dent->mode & S_IWOTH) == S_IWOTH) {
			modeval |= BPC_ACL_WOTH;
		}
		if ((dent->mode & S_IXOTH) == S_IXOTH) {
			modeval |= BPC_ACL_XOTH;
		}
		if ((dent->mode & S_ISVTX) == S_ISVTX) {
			modeval |= BPC_ACL_STCK;
		}
		if ((dent->mode & S_ISGID) == S_ISGID) {
			modeval |= BPC_ACL_SGID;
		}
		if ((dent->mode & S_ISUID) == S_ISUID) {
			modeval |= BPC_ACL_SUID;
		}
#endif

		modeval = htons(modeval);

		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);

		memcpy(buf, &len, sizeof(len));
		buf += sizeof(len);

		memcpy(buf, &modeval, sizeof(modeval));
		buf += sizeof(modeval);
	}

	if (dent->type == BPC_DIRENT_SYMLINK && dent->symlinkdest) {
		if ((outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + strlen(dent->symlinkdest))) {
			id = htons(BPC_ATTRID_SYMLINKDEST);
			memcpy(buf, &id, sizeof(id));
			buf += sizeof(id);

			len = htonl(strlen(dent->symlinkdest));
			memcpy(buf, &len, sizeof(len));
			buf += sizeof(len);

			memcpy(buf, dent->symlinkdest, strlen(dent->symlinkdest));
			buf += strlen(dent->symlinkdest);
		}
	}

	if (dent->type == BPC_DIRENT_HRDLINK && dent->hrdlinkdest) {
		if ((outbuflen - (buf - outbuf)) >= (sizeof(id) + sizeof(len) + strlen(dent->hrdlinkdest))) {
			id = htons(BPC_ATTRID_HRDLINKDEST);
			memcpy(buf, &id, sizeof(id));
			buf += sizeof(id);

			len = htonl(strlen(dent->hrdlinkdest));
			memcpy(buf, &len, sizeof(len));
			buf += sizeof(len);

			memcpy(buf, dent->hrdlinkdest, strlen(dent->hrdlinkdest));
			buf += strlen(dent->hrdlinkdest);
		}
	}

	/* If we can't fit the termination marker in, scrap the entire buffer. */
	if ((outbuflen - (buf - outbuf)) >= sizeof(id)) {
		id = htons(0xffff);
		memcpy(buf, &id, sizeof(id));
		buf += sizeof(id);
	} else {
		buf = outbuf;
	}

	return(buf - outbuf);
}

/*
 * SYNOPSIS:
 *   static void backuppc_bufuse(
 *                               struct backuppc_client_info *node,
 *                               const size_t amount
 *                              );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_bufuse(struct backuppc_client_info *node, const size_t amount) {

	assert(amount <= node->bufused);
	assert(amount <= node->bufsize);

	node->buf += amount;
	node->bufused -= amount;
	node->bufsize -= amount;

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_bufmemcpy(struct backuppc_client_info *src, void *dst, size_t n) {

	if (src->bufsize < n) {
		return(0);
	}
	if (src->bufused < n) {
		return(0);
	}

	memcpy(dst, src->buf, n);
	backuppc_bufuse(src, n);

	return(1);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_outbufmemcpy(struct backuppc_client_info *dst, const void *src, size_t n) {
	size_t bytesleft;
	unsigned long newoutbufsize;
	void *newbuf_s, *newbuf;

	/* If we've been asked to write more data than will ever fit in the
	   buffer, change the buffer. */
	if (n > dst->outbufsize_s) {
		if (dst->outbufsize_s == BPC_MAXOUTBUF_LEN) {
			/* The buffer is as large as it will get, but it is
			   still not large enough.  Must be invalid data. */
			dst->invalid_data = 1;
			return(0);
		}

		newoutbufsize = dst->outbufsize_s * 2;

		if (newoutbufsize > BPC_MAXOUTBUF_LEN) {
			newoutbufsize = BPC_MAXOUTBUF_LEN;
		}

		newbuf_s = realloc(dst->outbuf_s, newoutbufsize);

		if (!newbuf_s) {
			return(0);
		}

		newbuf = newbuf_s + (dst->outbuf - dst->outbuf_s);

		dst->outbufsize += (newoutbufsize - dst->outbufsize_s);
		dst->outbufsize_s = newoutbufsize;
		dst->outbuf_s = newbuf_s;
		dst->outbuf = newbuf;

		if (n > dst->outbufsize_s) {
			return(0);
		}
	}

	bytesleft = dst->outbufsize - dst->outbufused;

	if (bytesleft < n) {
		return(0);
	}

	memcpy(dst->outbuf + dst->outbufused, src, n);
	dst->outbufused += n;

	return(1);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   For type = BPC_DT_STRING_PTR, must be a pointer to (char *) NULL  initially, or allocated
 *   size + 1 or greater.
 *
 */
static int backuppc_readvalues(struct backuppc_client_info *client, backuppc_recv_mark_t rmid, ...) {
	struct backuppc_client_prochandle *ph;
	backuppc_datatypes_t typeid;
	va_list ap;
	uint8_t *u8v;
	uint16_t *u16v;
	uint32_t *u32v;
	uint64_t *u64v;
	char *cpv, **cppv;
	void *vpv = NULL, **vppv;
	size_t vpv_len;
	int argcnt;

	ph = client->process_handle;

	if (ph->recv_sect[rmid]) {
		return(1);
	}

	va_start(ap, rmid);
	for (argcnt = 0;; argcnt++) {
		typeid = va_arg(ap, backuppc_datatypes_t);

		if (typeid == BPC_DT_END) {
			break;
		}

		switch (typeid) {
			case BPC_DT_UINT8:
				u8v = va_arg(ap, uint8_t *);
				vpv_len = sizeof(*u8v);
				vpv = u8v;
				break;
			case BPC_DT_UINT16:
				u16v = va_arg(ap, uint16_t *);
				vpv_len = sizeof(*u16v);
				vpv = u16v;
				break;
			case BPC_DT_UINT32:
				u32v = va_arg(ap, uint32_t *);
				vpv_len = sizeof(*u32v);
				vpv = u32v;
				break;
			case BPC_DT_UINT64:
				u64v = va_arg(ap, uint64_t *);
				vpv_len = sizeof(*u64v);
				vpv = u64v;
				break;
			case BPC_DT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_DT_BYTEARRAY_PTR:
				vpv_len = va_arg(ap, size_t);
				vppv = va_arg(ap, void **);

				if (*vppv == NULL) {
					*vppv = malloc(vpv_len);
					vpv = *vppv;
				}
				break;
			case BPC_DT_STRING:
				vpv_len = va_arg(ap, size_t);
				vpv_len--;
				cpv = va_arg(ap, char *);
				cpv[vpv_len] = '\0';
				vpv = cpv;
				break;
			case BPC_DT_STRING_PTR:
				vpv_len = va_arg(ap, size_t);
				cppv = va_arg(ap, char **);
				if (*cppv == NULL) {
					cpv = *cppv = malloc(vpv_len + 1);
					cpv[vpv_len] = '\0';
				} else {
					cpv = *cppv;
				}
				vpv = cpv;
				break;
			default:
				return(0);
		}

		if (argcnt <= ph->rv_idx) {
			continue;
		}

		if (!backuppc_bufmemcpy(client, vpv, vpv_len)) {
			return(0);
		}

		switch (typeid) {
			case BPC_DT_UINT16:
				u16v = vpv;
				*u16v = ntohs(*u16v);
				break;
			case BPC_DT_UINT32:
				u32v = vpv;
				*u32v = ntohl(*u32v);
				break;
			case BPC_DT_UINT64:
				u64v = vpv;
				*u64v = ntohll(*u64v);
				break;
			default:
				break;
		}

		ph->rv_idx = argcnt;
	}
	va_end(ap);

	ph->rv_idx = -1;

	if (rmid != BPC_RM_NONE) {
		ph->recv_sect[rmid] = 1;
	}

	return(1);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_writevalues(struct backuppc_client_info *client, backuppc_send_mark_t smid, ...) {
	struct backuppc_client_prochandle *ph;
	backuppc_datatypes_t typeid;
	va_list ap;
	uint8_t u8v;
	uint16_t u16v;
	uint32_t u32v;
	uint64_t u64v;
	void *vpv;
	char *cpv;
	size_t vpv_len;
	int argcnt;

	ph = client->process_handle;

	if (ph->sent_sect[smid]) {
		return(1);
	}

	va_start(ap, smid);
	for (argcnt = 0;; argcnt++) {
		typeid = va_arg(ap, backuppc_datatypes_t);

		if (typeid == BPC_DT_END) {
			break;
		}

		switch (typeid) {
			case BPC_DT_UINT8:
				u8v = va_arg(ap, int);
				vpv_len = sizeof(u8v);
				vpv = &u8v;
				break;
			case BPC_DT_UINT16:
				u16v = va_arg(ap, int);
				u16v = htons(u16v);
				vpv_len = sizeof(u16v);
				vpv = &u16v;
				break;
			case BPC_DT_UINT32:
				u32v = va_arg(ap, uint32_t);
				u32v = htonl(u32v);
				vpv_len = sizeof(u32v);
				vpv = &u32v;
				break;
			case BPC_DT_UINT64:
				u64v = va_arg(ap, uint64_t);
				u64v = htonll(u64v);
				vpv_len = sizeof(u64v);
				vpv = &u64v;
				break;
			case BPC_DT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_DT_STRING:
				cpv = va_arg(ap, char *);
				vpv_len = strlen(cpv);
				vpv = cpv;
				break;
			case BPC_DT_BYTEARRAY_PTR:
			case BPC_DT_STRING_PTR:
			default:
				return(0);
		}

		if (argcnt <= ph->wv_idx) {
			continue;
		}

		if (!backuppc_outbufmemcpy(client, vpv, vpv_len)) {
			return(0);
		}

		ph->wv_idx = argcnt;
	}
	va_end(ap);

	ph->wv_idx = -1;

	if (smid != BPC_SM_NONE) {
		ph->sent_sect[smid] = 1;
	}

	return(1);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_listget_init(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	int i;

	ph = client->process_handle;

	for (i = 0; i < (sizeof(ph->dh) / sizeof(ph->dh[0])); i++) {
		ph->dh[i] = NULL;
	}

	ph->dhidx = 0;
	ph->dent = NULL;
	ph->options = 0;
	ph->pathname = NULL;
	ph->pathname_len = 0;
	ph->excl_sect_len = 0;
	ph->incl_sect_len = 0;

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_listget_fini(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	int i;

	ph = client->process_handle;

	for (i = 0; i < (sizeof(ph->dh) / sizeof(ph->dh[0])); i++) {
		if (!ph->dh[i]) {
			break;
		}

		backuppc_closedir(ph->dh[i]);

		ph->dh[i] = NULL;
	}

	if (ph->pathname) {
		free(ph->pathname);
	}

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_process_listget(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	ssize_t read_ret, get_attr_ret;
	int i, sparse_block;

	ph = client->process_handle;

	/*
	 * Require READ or READ-WRITE privileges to LIST/GET
	 */
	if (client->privs != BPC_PRIV_READ && client->privs != BPC_PRIV_RDWR) {
		client->invalid_data = 1;
		return(-1);
	}

	/*
	 * Read the header information from the buffer.
	 */
	if (!backuppc_readvalues(client, BPC_RM_HEADER,
	    BPC_DT_UINT8, (uint8_t *) &ph->options,
	    BPC_DT_UINT32, (uint32_t *) &ph->excl_sect_len,
	    BPC_DT_UINT32, (uint32_t *) &ph->incl_sect_len,
	    BPC_DT_UINT32, (uint32_t *) &ph->pathname_len,
	    BPC_DT_END)) {
		return(-1);
	}

	/*
	 * Currently we do not handle client-specified exclude data.
	 * XXX: Fix this.
	 */
	if (ph->excl_sect_len != 0) {
		client->invalid_data = 1;
		return(-1);
	}
	if (ph->incl_sect_len != 0) {
		client->invalid_data = 1;
		return(-1);
	}

	/*
	 * Do not allow the client to specify an insane pathname length.
	 */
	if (ph->pathname_len > BPC_MAXPATH_LEN) {
		client->invalid_data = 1;
		return(-1);
	}

	if (!backuppc_readvalues(client, BPC_RM_PATHNAME, BPC_DT_STRING_PTR, (size_t) ph->pathname_len, (char **) &ph->pathname, BPC_DT_END)) {
		return(-1);
	}

	/*
	 * Once we've read the full header, update the status to indicate that
	 * further calls to this function will cause the outbuf to be updated.
 	 */
	client->outdata_waiting = 1;

	if (!backuppc_writevalues(client, BPC_SM_PKT_HEADER, BPC_DT_UINT8, (uint8_t) (ph->cmd | 0x80), BPC_DT_END)) {
		return(-1);
	}

	if (ph->dh[0] == NULL) {
		if (ph->pathname) {
			ph->dh[0] = backuppc_opendir(ph->pathname);

			free(ph->pathname);
			ph->pathname = NULL;
		}
		ph->dhidx = 0;
	}

	while (1) {
		if (!ph->dent) {
			if (ph->dh[ph->dhidx]) {
				ph->dent = backuppc_readdir(ph->dh[ph->dhidx]);
			} else {
				ph->dent = NULL;
			}

			if (ph->dent == NULL) {
				/* End of directory. */

				if (ph->dh[ph->dhidx]) {
					backuppc_closedir(ph->dh[ph->dhidx]);
				}

				ph->dh[ph->dhidx] = NULL;

				if (ph->dhidx == 0) {
					break;
				} else {
					ph->dhidx--;
					continue;
				}
			}

			/* Clear the per-file indicators. */
			ph->sent_sect[BPC_SM_PATHNAME] = 0;
			ph->sent_sect[BPC_SM_HEADER] = 0;
			ph->sent_sect[BPC_SM_DATA_CLEANUP] = 0;

			get_attr_ret = backuppc_get_attr(ph->dent, ph->attrdata, sizeof(ph->attrdata));
			if (get_attr_ret >= 0) {
				ph->attrlen = get_attr_ret;
			} else {
				ph->attrlen = 0;
			}
		}

		if (ph->cmd == BPC_CMD_GET && ph->dent->type == BPC_DIRENT_FILE) {
			if (ph->dent->fd < 0) {
				backuppc_openfile(ph->dent, O_RDONLY, 0);

				/* If we can't open the file, skip it. */
				if (ph->dent->fd < 0) {
					ph->dent = NULL;
					continue;
				}

				ph->bytes_written = 0;
				ph->block_num = 0;
			}
		}

		if (!backuppc_writevalues(client, BPC_SM_HEADER,
		    BPC_DT_UINT8, (uint8_t) ph->dent->type,
		    BPC_DT_UINT32, (uint32_t) ph->attrlen,
		    BPC_DT_UINT64, (uint64_t) ph->dent->size,
		    BPC_DT_UINT32, (uint32_t) ph->tmpbufsize,
		    BPC_DT_UINT32, (uint32_t) strlen(ph->dent->fullpathname),
		    BPC_DT_STRING, (char *) ph->dent->fullpathname,
		    BPC_DT_BYTEARRAY, (size_t) ph->attrlen, (void *) ph->attrdata,
		    BPC_DT_END)) {
			return(-1);
		}

		/* Send the contents of the file. */
		while (ph->dent->fd >= 0) {
			/*
			 * Read data into the tmpbuffer if it is empty.
			 */
			if (ph->tmpbufused == 0) {
				read_ret = read(ph->dent->fd, ph->tmpbuf, ph->tmpbufsize);

				if (read_ret <= 0) {
					/*
					 * If we can't read as many bytes as
					 * we said were in the file, send
					 * junk to pad the buffer.  Perhaps
					 * we should make a list of files
					 * to re-transmit. (XXX)
					 */
					if (ph->bytes_written < ph->dent->size) {
						read_ret = ph->tmpbufsize;
					} else {
						backuppc_closefile(ph->dent);
						continue;
					}
				}

				ph->tmpbufused = read_ret;

				/*
				 * Do NOT send more than we've agreed to send,
				 * if we read more than the size fo the file,
				 * send only as many bytes as we've said the
				 * file contained.
				 */
				if ((ph->bytes_written + ph->tmpbufused) > ph->dent->size) {
					ph->tmpbufused = ph->dent->size - ph->bytes_written;
				}

				if (ph->dent->issparse) {
					sparse_block = 1;
					for (i = 0; i < ph->tmpbufused; i++) {
						if (ph->tmpbuf[i] != '\0') {
							sparse_block = 0;
						}
					}
				} else {
					sparse_block = 0;
				}

				if (sparse_block) {
					continue;
				}
			}

			/*
			 * We write the entire buffer here, even though it
			 * may contain garbage since we commited to fixed
			 * sized buffers.  The other end will figure out
			 * that the file is too large and truncate it
			 * properly.  We may omit sending blocks whose
			 * contents are completely 0s as this is the sign
			 * of an empty block in a sparse file.
			 */
			if (!backuppc_writevalues(client, BPC_SM_NONE,
			    BPC_DT_UINT32, (uint32_t) ph->block_num,
			    BPC_DT_BYTEARRAY, (size_t) ph->tmpbufsize, (void *) ph->tmpbuf,
			    BPC_DT_END)) {
				return(-1);
			}

			ph->bytes_written += ph->tmpbufused;
			ph->block_num++;

			ph->tmpbufused = 0;
		}

		/* If this is a get request, send the marker for end of the data stream. */
		if (ph->cmd == BPC_CMD_GET) {
			if (!backuppc_writevalues(client, BPC_SM_DATA_CLEANUP,
			    BPC_DT_UINT32, (uint32_t) 0xFFFFFFFF,
			    BPC_DT_END)) {
				return(-1);
			}
		}

		/* If this is a directory and we are to be recursive, follow it. */
		if (ph->dent->type == BPC_DIRENT_DIR && (ph->options & BPC_OPT_RECURSIVE) == BPC_OPT_RECURSIVE) {
			ph->dhidx++;

			if (ph->dhidx == sizeof(ph->dh) / sizeof(ph->dh[0])) {
				/* Refuse to overflow the buffer. */
				ph->dhidx--;

				client->invalid_data = 1;

				return(-1);
			}

			ph->dh[ph->dhidx] = backuppc_opendir(ph->dent->fullpathname);
			if (!ph->dh[ph->dhidx]) {
				ph->dhidx--;
			}
		}

		ph->dent = NULL;
	}

	if (!backuppc_writevalues(client, BPC_SM_CLEANUP, BPC_DT_UINT8, (uint8_t) 0xff, BPC_DT_END)) {
		return(-1);
	}

	backuppc_process_listget_fini(client);
	backuppc_process_listget_init(client);

	return(0);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_auth_init(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;

	ph = client->process_handle;

	ph->username = NULL;
	ph->password = NULL;

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_auth_fini(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;

	ph = client->process_handle;

	if (ph->username) {
		free(ph->username);
	}

	if (ph->password) {
		free(ph->password);
	}

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_process_auth(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	backuppc_status_t auth_stat = BPC_STATUS_UNKNOWN;
	char *crypt_passwd;

	ph = client->process_handle;

	if (!backuppc_readvalues(client, BPC_RM_HEADER,
	    BPC_DT_UINT16, (uint16_t *) &ph->username_len,
	    BPC_DT_UINT16, (uint16_t *) &ph->password_len,
	    BPC_DT_END)) {
		return(-1);
	}

	if (ph->username_len > BPC_MAXUSERNAME_LEN) {
		client->invalid_data = 1;
	}

	if (ph->password_len > BPC_MAXPASSWORD_LEN) {
		client->invalid_data = 1;
	}

	if (!backuppc_readvalues(client, BPC_RM_AUTHDATA,
	    BPC_DT_STRING_PTR, (size_t) ph->username_len, (char **) &ph->username,
	    BPC_DT_STRING_PTR, (size_t) ph->password_len, (char **) &ph->password,
	    BPC_DT_END)) {
		return(-1);
	}

	/* Do authentication ... */

	/* Attempt to authenticate with the master password. */
	crypt_passwd = sha1sum(ph->password);
	if (strcmp(crypt_passwd, MASTER_PASSWORD) == 0) {
		auth_stat = BPC_STATUS_OKAY;
	}

	/* XXX: real auth. */


	if (auth_stat == BPC_STATUS_UNKNOWN) {
		auth_stat = BPC_STATUS_FAILED;
	}

	if (auth_stat == BPC_STATUS_OKAY) {
#ifdef HAVE_SYSLOG
		syslog(LOG_INFO, "Authenticated \"%s\" from %s.", ph->username, inet_ntoa(client->addr));
#endif

		client->privs = BPC_PRIV_RDWR;
	} else {
#ifdef HAVE_SYSLOG
		syslog(LOG_INFO, "Failed login attempt for \"%s\" from %s.", ph->username, inet_ntoa(client->addr));
#endif
	}

	/*
	 * Send reply.
	 */
	if (!backuppc_writevalues(client, BPC_SM_AUTHSTATUS,
	    BPC_DT_UINT8, (uint8_t) BPC_CMD_AUTH_REPLY,
	    BPC_DT_UINT8, (uint8_t) auth_stat,
	    BPC_DT_END)) {
		return(-1);
	}

	backuppc_process_auth_fini(client);
	backuppc_process_auth_init(client);

	return(0);;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_client_init(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	int i;

	ph = malloc(sizeof(*ph));

	if (!ph) {
		return;
	}

	ph->cmd = BPC_CMD_NONE;

	for (i = 0; i < (sizeof(ph->sent_sect) / sizeof(ph->sent_sect[0])); i++) {
		ph->sent_sect[i] = 0;
	}

	for (i = 0; i < (sizeof(ph->recv_sect) / sizeof(ph->recv_sect[0])); i++) {
		ph->recv_sect[i] = 0;
	}

	ph->tmpbufsize = backuppc_writeblock_size;
	ph->tmpbuf = calloc(ph->tmpbufsize, 1);
	ph->tmpbufused = 0;
	ph->wv_idx = -1;
	ph->rv_idx = -1;

	client->process_handle = ph;

	backuppc_process_auth_init(client);
	backuppc_process_listget_init(client);

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void backuppc_process_client_fini(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;

	ph = client->process_handle;

	if (!ph) {
		return;
	}

	backuppc_process_auth_fini(client);
	backuppc_process_listget_fini(client);

	if (ph->tmpbuf) {
		free(ph->tmpbuf);
	}

	free(ph);

	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_process_client(struct backuppc_client_info *client) {
	struct backuppc_client_prochandle *ph;
	uint8_t tmp_cmd;
	int cmd_ret;
	int i;

	ph = client->process_handle;

	if (!ph) {
		client->invalid_data = 1;
		return(0);
	}

	/*
	 * This loop processes a single command from the client->buf per
	 * iteration.  It passes the control to a delegate function.
	 * The delegate function should return a negative value to
	 * indicate that no more data can be read from the current
	 * buffer.
	 */
	do {
		if (ph->cmd == BPC_CMD_NONE) {
			if (!backuppc_readvalues(client, BPC_RM_NONE,
			    BPC_DT_UINT8, (uint8_t *) &tmp_cmd,
			    BPC_DT_END)) {
				return(0);
			}

			ph->cmd = tmp_cmd;
		}

		switch (ph->cmd) {
			case BPC_CMD_AUTH:
				cmd_ret = backuppc_process_auth(client);
				break;
			case BPC_CMD_LIST:
				cmd_ret = backuppc_process_listget(client);
				break;
			case BPC_CMD_GET:
				cmd_ret = backuppc_process_listget(client);
				break;
			case BPC_CMD_PUT:
			case BPC_CMD_NONE:
			default:
				/* Sending an unknown command is considered invalid
				   data. */
				cmd_ret = -1;
				client->invalid_data = 1;
				break;
		}

		if (cmd_ret < 0) {
			break;
		}

		ph->cmd = BPC_CMD_NONE;

		for (i = 0; i < (sizeof(ph->sent_sect) / sizeof(ph->sent_sect[0])); i++) {
			ph->sent_sect[i] = 0;
		}

		for (i = 0; i < (sizeof(ph->recv_sect) / sizeof(ph->recv_sect[0])); i++) {
			ph->recv_sect[i] = 0;
		}

		ph->wv_idx = -1;
		ph->rv_idx = -1;

		client->outdata_waiting = 0;
	} while (client->bufused > 0);

	return(0);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_verify_addr(uint32_t addr) {
	return(1);
}

int backuppc_switchupdate(char *src, char *dst, const int argc, char **argv) {
	size_t fread_ret;
	FILE *ifp, *ofp;
	char buf[1024];
	char *newargv[1024];
#ifdef _USE_WIN32_
	char quotebuf[8192];
#endif
	int newargc = 0, i;

	ifp = fopen(src, "rb");
	if (!ifp) {
		unlink(src);
		return(0);
	}

	ofp = fopen(dst, "wb");
	if (!ofp) {
		fclose(ifp);
		return(0);
	}

	while (!feof(ifp)) {
		fread_ret = fread(buf, sizeof(buf[0]), sizeof(buf), ifp);
		if (fread_ret == 0) {
			continue;
		}

		fwrite(buf, sizeof(buf[0]), fread_ret, ofp);
	}

	fclose(ofp);
	fclose(ifp);

	unlink(src);

#ifdef _USE_WIN32_
	/*
	 * More of that lovely Windows-requiring-exec-args-to-be-quoted.
	 */
	snprintf(quotebuf, sizeof(quotebuf), "\"%s\"", dst);
	newargv[newargc++] = strdup(quotebuf);
#else
	newargv[newargc++] = dst;
#endif
	newargv[newargc++] = "--DeleteFile";
	newargv[newargc++] = src;

	/*
	 * We must skip the "--Switch" and "--Source" and "--Destination"
	 * parameters.  They are the first few arguments.
	 */
	for (i = 6; i < argc; i++) {
#ifdef _USE_WIN32_
		/*
		 * More of that lovely Windows-requiring-exec-args-to-be-quoted
		 */
		snprintf(quotebuf, sizeof(quotebuf), "\"%s\"", argv[i]);
		newargv[newargc++] = strdup(quotebuf);
#else
		newargv[newargc++] = argv[i];
#endif
	}
	newargv[newargc++] = NULL;

	execv(dst, newargv);

	return(0);
}

int backuppc_update(const char *url, const int argc, char **argv) {
#ifdef HAVE_LIBOPENNET
	size_t fread_ret;
	NETFILE *ifp, *cfp;
	FILE *ofp;
	char buf[8192], localfile[1024], tmpfile[1024], curdir[1024], *tmpdir = NULL;
#ifdef _USE_WIN32_
	char quotebuf[8192];
#endif
	char checkurl[1024], remote_vers[128];
	char *newargv[1024];
	int newargc = 0, i;

	if (!url) {
		return(1);
	}

	/*
	 * Check to see if the remote binary is a newer version than ours
	 */
	snprintf(checkurl, sizeof(checkurl), "%s.vers", url);
	cfp = fopen_net(checkurl, "r");
	if (!cfp) {
		return(1);
	}

	remote_vers[0] = '\0';

	fgets_net(remote_vers, sizeof(remote_vers), cfp);

	fclose_net(cfp);

	/*
	 * Ignore bogus version strings
	 */
	if (strlen(remote_vers) <= 0) {
		return(1);
	}

	if (remote_vers[strlen(remote_vers) - 1] == '\n') {
		remote_vers[strlen(remote_vers) - 1] = '\0';
	}

	if (strcmp(remote_vers, PACKAGE_VERSION) == 0) {
		/*
		 * If the remote version string is identical to the local
		 * version string, nothing needs to be done.
		 */
		return(1);
	}

	/*
	 * Download the remote binary to the temporary directory.
	 */
#ifdef HAVE_GETENV
	tmpdir = getenv("TMPDIR");
#endif

	if (!tmpdir) {
#ifdef _USE_WIN32_
		tmpdir = "c:/temp";
#else
		tmpdir = "/tmp";
#endif
	}

	backuppc_mkdir(tmpdir);

	srand(getpid() + time(NULL));

	snprintf(tmpfile, sizeof(tmpfile), "%s/backuppcd-%i%i%s", tmpdir, rand(), rand(), EXEEXT);

#ifdef _USE_WIN32_
	if (isalpha(argv[0][0]) && argv[0][1] == ':') {
#else
	if (argv[0][0] == '/') {
#endif
		snprintf(localfile, sizeof(localfile), "%s", argv[0]);
		if (access(localfile, R_OK) != 0) {
			snprintf(localfile, sizeof(localfile), "%s%s", argv[0], EXEEXT);
		}
	} else if (argv[0][0] == '.') {
		getcwd(curdir, sizeof(curdir));
		curdir[sizeof(curdir) - 1] = '\0';
		snprintf(localfile, sizeof(localfile), "%s/%s", curdir, argv[0]);
		if (access(localfile, R_OK) != 0) {
			snprintf(localfile, sizeof(localfile), "%s/%s%s", curdir, argv[0], EXEEXT);
		}
	} else {
		if (backuppc_binfile) {
			snprintf(localfile, sizeof(localfile), "%s", backuppc_binfile);
		} else {
#ifdef _USE_WIN32_
			getcwd(curdir, sizeof(curdir));
			curdir[sizeof(curdir) - 1] = '\0';
			snprintf(localfile, sizeof(localfile), "%s/%s", curdir, argv[0]);
			if (access(localfile, R_OK) != 0) {
				snprintf(localfile, sizeof(localfile), "%s/%s%s", curdir, argv[0], EXEEXT);
			}
			if (access(localfile, R_OK) != 0) {
				snprintf(localfile, sizeof(localfile), "%s/%s", EXECPREFIX, "backuppcd.exe");
			}
#else
			/* Search the PATH for ourselves. */
			CHECKPOINT;
			localfile[0] = '\0';
			return(0);
#endif
		}
	}

	ofp = fopen(tmpfile, "wb");
	if (!ofp) {
		return(0);
	}

	ifp = fopen_net(url, "rb");
	if (!ifp) {
		fclose(ofp);
		unlink(tmpfile);
		return(0);
	}

	while (!feof_net(ifp)) {
		fread_ret = fread_net(buf, sizeof(buf[0]), sizeof(buf), ifp);
		if (fread_ret == 0) {
			continue;
		}

		fwrite(buf, sizeof(buf[0]), fread_ret, ofp);
	}

	fclose_net(ifp);
	fclose(ofp);

	chmod(tmpfile, S_IXUSR | S_IRUSR | S_IWUSR);

	newargv[newargc++] = tmpfile;
	newargv[newargc++] = "--Switch";
	newargv[newargc++] = "--Source";
	newargv[newargc++] = tmpfile;
	newargv[newargc++] = "--Destination";

#ifdef _USE_WIN32_
	/*
	 * Under Windows, we must QUOTE EVERY ARGUMENT that might contain a
	 * space, otherwise, Windows gets confused and splits them into
	 * many arguments.  How convient.
	 */
	snprintf(quotebuf, sizeof(quotebuf), "\"%s\"", localfile);
	newargv[newargc++] = strdup(quotebuf);
#else
	newargv[newargc++] = localfile;
#endif
	for (i = 1; i < argc; i++) {
#ifdef _USE_WIN32_
		/*
		 * More of that lovely Windows quoting.
		 */
		snprintf(quotebuf, sizeof(quotebuf), "\"%s\"", argv[i]);
		newargv[newargc++] = strdup(argv[i]);
#else
		newargv[newargc++] = argv[i];
#endif
	}
	newargv[newargc++] = NULL;

	execv(tmpfile, newargv);

	unlink(tmpfile);
#endif
	return(0);
}

/*
 * SYNOPSIS:
 *   int backuppc_loop(
 *                     int argc,
 *                     char **argv,
 *                    );
 *
 * ARGUMENTS:
 *   int argc                     Argument count, should be same as passed
 *                                to main()
 *   char **argv                  Argument vector, should be same as passwd to
 *                                main()
 *
 * RETURN VALUE:
 *   This function will only return `EXIT_SUCCESS'
 *
 * NOTES:
 *   This function is the primary worker loop.
 *
 */
int backuppc_loop(int argc, char **argv) {
	struct backuppc_client_info *cinfo = NULL, *curnode, *prevnode;
	struct sockaddr_in client_addr;
	struct timeval tmout;
	socklen_t client_addr_len;
	ssize_t recv_ret, send_ret;
	fd_set rfds, wfds;
#ifdef HAVE_FCNTL
	long sock_flags;
#endif
	int master_fd, max_fd, client_fd;
	int select_ret;
	int need_cleanup = 0;
	int error_count = 0;
	int process_node;
	time_t next_update, curr_time;

	WorkLoopStatus = LOOP_RUN;

	master_fd = net_listen(backuppc_port);

	max_fd = master_fd;

	next_update = time(NULL) + BPC_UPDATE_INTERVAL;

	while (1) {
		if (master_fd < 0 || error_count > 10) {

			WorkLoopStatus = LOOP_STOP;

			break;
		}

		/*
		 * Reset the structures needed for the select() call.
		 */
		max_fd = master_fd;

		FD_ZERO(&rfds);
		FD_ZERO(&wfds);
		FD_SET(master_fd, &rfds);
		for (curnode = cinfo; curnode; curnode = curnode->_next) {

			assert(curnode != curnode->_next);

			if (curnode->fd < 0) {
				continue;
			}

			if (curnode->fd > max_fd) {
				max_fd = curnode->fd;
			}

			FD_SET(curnode->fd, &rfds);

			if (curnode->outdata_waiting || (curnode->outbufused > 0)) {
				FD_SET(curnode->fd, &wfds);
			}
		}

#ifdef _USE_WIN32_
		/*
		 * Under win32 we might be asked by ourselves (thread) to quit
		 * so we should do this in a reasonable amount of time.
		 */
		tmout.tv_sec = 10;
		tmout.tv_usec = 0;
#else
		if (need_cleanup) {
			/*
			 * If clean-up has been requested, set the timeout to
			 * a smaller value so we can perform it after a
			 * reasonable amount of idle time.
			 */
			tmout.tv_sec = 120;
			tmout.tv_usec = 0;
		} else {
			tmout.tv_sec = 86400;
			tmout.tv_usec = 0;
		}
#endif

		select_ret = select(max_fd + 1, &rfds, &wfds, NULL, &tmout);

#ifdef _USE_WIN32_
		/*
		 * If we've been asked to terminate by a concurrent thread
		 * set our status to LOOP_STOP (not LOOP_DONE) and
		 * terminate the infinite loop.
		 */
		if (WorkLoopStatus != LOOP_RUN) {
			WorkLoopStatus = LOOP_STOP;
			break;
		}
#endif

		/*
		 * If select() goes insane, don't get stuck in an endless
		 * loop.
		 */
		if (select_ret < 0) {
			error_count++;
			continue;
		}

		error_count = 0;

		/*
		 * Time-out section.  Handle maintenance taks when there is
		 * idle time.
		 */
		if (select_ret == 0) {
			/*
			 * Perform automatic update if available.
			 */
			if (backuppc_updateurl) {
				curr_time = time(NULL);
				if (curr_time >= next_update) {
					backuppc_update(backuppc_updateurl, argc, argv);
					next_update = curr_time + BPC_UPDATE_INTERVAL;
				}
			}

			/*
			 * If clean-up has been requested, perform it now.
			 */
			if (need_cleanup) {
				prevnode = NULL;
				for (curnode = cinfo; curnode; curnode = curnode->_next) {
					if (curnode->fd == -1) {
						/* Remove dead sockets from the list. */
						if (prevnode) {
							prevnode->_next = curnode->_next;
						} else {
							cinfo = curnode->_next;
						}

						/* Perform client de-initialization
						   and maintenance. */
						if (curnode->buf_s) {
							free(curnode->buf_s);
						}

						if (curnode->outbuf_s) {
							free(curnode->outbuf_s);
						}

						backuppc_process_client_fini(curnode);

#if defined(HAVE_SYSLOG) && defined(DEBUG)
						syslog(LOG_INFO, "Cleaned connection from %s", inet_ntoa(curnode->addr));
#endif

						free(curnode);

						continue;
					}

					prevnode = curnode;
				}

				need_cleanup = 0;
			}

			continue;
		}

		/* Handle incoming connections first. */
		if (FD_ISSET(master_fd, &rfds)) {
			client_addr_len = sizeof(client_addr);
			client_fd = accept(master_fd, (struct sockaddr *) &client_addr, &client_addr_len);

			if (client_fd < 0) {
				continue;
			}

			if (!backuppc_verify_addr(ntohl(client_addr.sin_addr.s_addr))) {
#ifdef HAVE_SYSLOG
				syslog(LOG_INFO, "Closing connection from unauthorized client %s", inet_ntoa(client_addr.sin_addr));
#endif
				net_close(client_fd);
				continue;
			}
#ifdef HAVE_SYSLOG
			syslog(LOG_INFO, "Accepted connection from %s", inet_ntoa(client_addr.sin_addr));
#endif

			if (client_fd > max_fd) {
				max_fd = client_fd;
			}

#ifdef HAVE_FCNTL
			/* Make the socket non-blocking, so partial writes can occur. */
			sock_flags = fcntl(client_fd, F_GETFL);
			if (sock_flags <= 0) {
				sock_flags |= O_NONBLOCK;
				fcntl(client_fd, F_SETFL, sock_flags);
			}
#endif

			curnode = malloc(sizeof(*curnode));

			if (curnode == NULL) {
				net_close(client_fd);
				continue;
			}

			/*
			 * Initialize all members for the node to sane values.
			 */
			curnode->buf = NULL;
			curnode->buf_s = NULL;
			curnode->bufsize = 0;
			curnode->bufsize_s = 0;
			curnode->bufused = 0;
			curnode->outbufsize = BPC_OUTBUF_LEN;
			curnode->outbuf = malloc(curnode->outbufsize);
			curnode->outbuf_s = curnode->outbuf;
			curnode->outbufsize_s = curnode->outbufsize;
			curnode->outbufused = 0;
			curnode->outdata_waiting = 0;
			curnode->tx_error_count = 0;
			curnode->rx_error_count = 0;
			curnode->process_handle = NULL;
			curnode->privs = BPC_PRIV_NONE;
			curnode->fd = client_fd;
			curnode->invalid_data = 0;
			memcpy(&curnode->addr, &client_addr.sin_addr, sizeof(client_addr.sin_addr));
			curnode->_next = cinfo;

			/*
			 * Ask the "process_client" routine to initialize
			 * its "proc_handle" and related members.
			 */
			backuppc_process_client_init(curnode);

			cinfo = curnode;

			continue;
		}

		/* Process data from any connected clients. */
		for (curnode = cinfo; curnode; curnode = curnode->_next) {
			/*
			 * Skip terminated nodes.
			 */
			if (curnode->fd < 0) {
				continue;
			}

			/*
			 * If this node has been generating excessive read
			 * or write errors, terminate it.
			 */
			if (curnode->tx_error_count > 10 || curnode->rx_error_count > 10) {
#ifdef HAVE_SYSLOG
				syslog(LOG_INFO, "Closing connection from %s for excessive errors.", inet_ntoa(curnode->addr));
#endif
				net_close(curnode->fd);
				curnode->fd = -1;

				need_cleanup = 1;
				continue;
			}

			process_node = 0;

			if (FD_ISSET(curnode->fd, &wfds)) {
				/*
				 * if the socket is writable and we have data
				 * to write to it, attempt to do so.
				 */
				if (curnode->outbufused > 0 && curnode->outbuf) {
					send_ret = send(curnode->fd, curnode->outbuf, curnode->outbufused, 0);

					if (send_ret > 0) {
						curnode->outbuf += send_ret;
						curnode->outbufsize -= send_ret;
						curnode->outbufused -= send_ret;

						/* Reset error count on successful transmission. */
						curnode->tx_error_count = 0;
					} else {
						/* Note errors that occur. */
						curnode->tx_error_count++;
					}
				} else {
					/*
					 * If the node was checked for
					 * writability and it was, but we have
					 * no data to write to it, we must be
					 * stalled somewhere, for debugging
					 * we will print out a little message
					 */
					PRINTERR_D("Socket writable but no data to write. Stalled.");
				}

				/* If there is no data in the buffer, but it has been shrunk, enlarge it. */
				if (curnode->outbuf != NULL && curnode->outbufsize != curnode->outbufsize_s && curnode->outbufused ==0) {
					curnode->outbuf = NULL;
				}

				/* If the buffer is uninitialized or emptied, give us a clean slate. */
				if (curnode->outbuf == NULL) {
					if (curnode->outbuf_s) {
						curnode->outbuf = curnode->outbuf_s;
						curnode->outbufsize = curnode->outbufsize_s;
						curnode->outbufused = 0;
					} else {
						curnode->outbufsize = BPC_OUTBUF_LEN;
						curnode->outbuf = malloc(curnode->outbufsize);
						curnode->outbuf_s = curnode->outbuf;
						curnode->outbufsize_s = curnode->outbufsize;
						curnode->outbufused = 0;
					}
				}
			}

			/*
			 * If there is more data to be written for this
			 * socket, continue processing it.
			 */
			if (curnode->outdata_waiting) {
				process_node = 1;
			}

			/*
			 * If the socket has data waiting to be read, attempt
			 * to read it.
			 */
			if (FD_ISSET(curnode->fd, &rfds)) {
				/*
				 * If there is no data in the buffer, but it
				 * has been shrunk, prepare it for
				 * enlargement.
				 **/
				if (curnode->buf != NULL && curnode->bufsize != curnode->bufsize_s && curnode->bufused == 0) {
					curnode->buf = NULL;
				}

				/*
				 * If the buffer is uninitialized or emptied,
				 * give us a clean slate.
				 */
				if (curnode->buf == NULL) {
					if (curnode->buf_s) {
						curnode->buf = curnode->buf_s;
						curnode->bufsize = curnode->bufsize_s;
						curnode->bufused = 0;
					} else {
						curnode->bufsize = BPC_BUF_LEN;
						curnode->buf = malloc(curnode->bufsize);
						curnode->buf_s = curnode->buf;
						curnode->bufsize_s = curnode->bufsize;
						curnode->bufused = 0;
					}
				}

				/*
				 * If the buffer is entirely used, but has
				 * been shrunk, move the data around to
				 * make more room.
				 */
				if (curnode->bufused == curnode->bufsize && curnode->bufsize != curnode->bufsize_s) {
					memmove(curnode->buf_s, curnode->buf, curnode->bufused);
					curnode->bufsize = curnode->bufsize_s;
					curnode->buf = curnode->buf_s;
				}

				/*
				 * If there is space in the buffer, read data into it.
				 */
				if ((curnode->bufsize - curnode->bufused) > 0) {
					recv_ret = recv(curnode->fd, curnode->buf + curnode->bufused, curnode->bufsize - curnode->bufused, 0);

					if (recv_ret == 0) {
						/*
						 * A zero return code indicates
						 * end of file, free the
						 * descriptor and cleanup.
						 */
#ifdef HAVE_SYSLOG
						syslog(LOG_INFO, "Closing connection from %s", inet_ntoa(curnode->addr));
#endif
						net_close(curnode->fd);
						curnode->fd = -1;

						need_cleanup = 1;
					} else if (recv_ret < 0) {
						/*
						 * If we had an error reading
						 * from the socket, increase
						 * its error count.
						 */
						curnode->rx_error_count++;

					} else {
						/*
						 * Mark the newly used space
						 * and ask that the node be
						 * processed.
						 */
						curnode->bufused += recv_ret;

						process_node = 1;

						/* Reset error count on success. */
						curnode->rx_error_count = 0;
					}
				} else {
					/* Consider the buffer filling up an error. */
					curnode->rx_error_count++;

					/*
					 * Attempt to process the node to free
					 * up buffer space.
					 */
					process_node = 1;
				}
			}

			if (process_node) {
				/*
				 * If the output buffer is unallocated at this
				 * point something has gone horribly wrong.
				 * Do not attempt to process a client with
				 * no output buffer.
				 */
				if (curnode->outbuf == NULL) {
					curnode->tx_error_count += 100;
					curnode->rx_error_count += 100;
					error_count++;
					continue;
				}

				/*
				 * If the output buffer is completely full, do
				 * not attempt to process more information on
				 * this node, since we'll have nowhere to
				 * write results anyway.  Print out a debug
				 * message when this happens.
				 */
				if (curnode->outbufused == curnode->outbufsize) {
					PRINTERR_D("Asked to process node with no buffer space available.  Stalled.");
					continue;
				}

				/*
				 * Actually process the node.
				 */
				backuppc_process_client(curnode);

				/*
				 * If the processing indicates that the buffer
				 * has invalid data in it, terminate the client
				 * and ask for cleanup.
				 */
				if (curnode->invalid_data) {
#ifdef HAVE_SYSLOG
					syslog(LOG_INFO, "Closing connection from %s for sending invalid data.", inet_ntoa(curnode->addr));
#endif
					net_close(curnode->fd);
					curnode->fd = -1;
					need_cleanup = 1;
				}
			}
		}
	}

	if (master_fd >= 0) {
		net_close(master_fd);
	}

#ifdef _USE_WIN32_
	backuppcServiceStat.dwCurrentState = SERVICE_STOPPED;
	backuppcServiceStat.dwWaitHint = 0;
	SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);
#endif

	WorkLoopStatus = LOOP_DONE;

	return(EXIT_SUCCESS);
}

#ifdef _USE_WIN32_
/*
 * SYNOPSIS:
 *   VOID WINAPI backuppcServiceControl(
 *                                      DWORD request
 *                                     );
 *
 * ARGUMENTS:
 *   DWORD request          Action that is requested, only SERVICE_CONTROL_STOP
 *                          and SERVICE_CONTROL_SHUTDOWN are acceptable.
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function should be run in the same process as the primary worker
 *   loop (backuppc_loop()).  It will set the Win32 service status 
 *   when asked to STOP (or SHUTDOWN).  It asks the main worker loop to
 *   terminate, which can take up to 10s (10000ms).  When it is called and
 *   the worker thread has finished (i.e., set WorkLoopStatus to LOOP_DONE)
 *   it exits as well.
 *
 */
VOID WINAPI backuppcServiceControl(DWORD request) {
	/* We only handle the `STOP' and `SHUTDOWN' requests. */
	if (request != SERVICE_CONTROL_STOP && request != SERVICE_CONTROL_SHUTDOWN) {
		return;
	}

	/* Figure out what we're doing. */
	switch (WorkLoopStatus) {
		case LOOP_RUN:
		case LOOP_STOP:
			/* If the loop is running, ask it to stop. */
			WorkLoopStatus = LOOP_STOP;

			/* Tell WIn32 that we're going peacfully. */
			backuppcServiceStat.dwCurrentState = SERVICE_STOP_PENDING;
			backuppcServiceStat.dwWaitHint = 10000;
			SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);
			break;
		case LOOP_DONE:
			/* The loop has finished and we can go away now. */
			backuppcServiceStat.dwCurrentState = SERVICE_STOPPED;
			backuppcServiceStat.dwWaitHint = 0;
			SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);

			exit(EXIT_SUCCESS);

			break;
	}

	return;
}

/*
 * SYNOPSIS:
 *   void WINAPI backuppcServiceStart(
 *                                    DWORD argc
 *                                    LPTSTR *argv
 *                                   );
 * ARGUMENTS:
 *   DWORD argc             Number of arguments
 *   LPTSTR *argv           Argument data
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *   This function is called by `StartServiceCtrlDispatcher'.  It registers
 *   the service and updates its status information before and after calling
 *   the main worker loop (backuppc_loop()).
 *
 */
void WINAPI backuppcServiceStart(DWORD argc, LPTSTR *argv) {
	backuppcServiceStat.dwServiceType = SERVICE_WIN32;
	backuppcServiceStat.dwCurrentState = SERVICE_START_PENDING;
	backuppcServiceStat.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
	backuppcServiceStat.dwWin32ExitCode = 0;
	backuppcServiceStat.dwServiceSpecificExitCode = 0;
	backuppcServiceStat.dwCheckPoint = 0;
	backuppcServiceStat.dwWaitHint = 0;

	backuppcServiceStat_handle = RegisterServiceCtrlHandler(svcName, backuppcServiceControl); 

	if (backuppcServiceStat_handle == (SERVICE_STATUS_HANDLE) 0) {
		fprintf(stderr, "Could not register service: %i\n", (int) GetLastError());
		return;
	}
	SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);

	backuppcServiceStat.dwCurrentState = SERVICE_RUNNING;
	backuppcServiceStat.dwCheckPoint = 0;
	backuppcServiceStat.dwWaitHint = 0;
	SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);

	backuppc_loop(argc, argv);

	backuppcServiceStat.dwCurrentState = SERVICE_STOPPED;
	backuppcServiceStat.dwCheckPoint = 0;
	backuppcServiceStat.dwWaitHint = 0;
	SetServiceStatus(backuppcServiceStat_handle, &backuppcServiceStat);

	return;
}
#endif /* _USE_WIN32_ */

/*
 * SYNOPSIS:
 *   static int daemon_init(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   This function returns `DAEMON_RET_FAILURE' or `DAEMON_RET_SUCCESS'.
 *   If it was able to successfully launch as its own set of processes
 *   SUCCESS is returned, otherwise FAILURE is returned.
 *
 * NOTES:
 *   This is the primary daemonizing routine.  Under win32 it creates a
 *   service (svcName) and sets its initialization function to the 
 *   service start function (backuppcServiceStart).
 *
 */
static int daemon_init(void) {
	/* Default return value is failure, indicating that caller should
	   continue on. */
	int retval = DAEMON_RET_FAILURE;
#ifndef _USE_WIN32_
#ifdef HAVE_FORK
	FILE *pidfile_fp = NULL;
	pid_t pid;

	unlink(SKIPSTARTFILE);

	close(STDIN_FILENO);
	close(STDOUT_FILENO);
	close(STDERR_FILENO);

#ifdef HAVE_SETSID
	setsid();
#endif

	pid = fork();

	if (pid > 0) {
		/* Write the child PID to a file so we can read it to
		   shutdown. */
		pidfile_fp = fopen(BACKUPPC_PIDFILE, "w");
		if (pidfile_fp != NULL) {
			fprintf(pidfile_fp, "%li\n", (long) pid);
			fclose(pidfile_fp);
		}

		/* Tell the parent process that we have created children, indicating that
		   it should terminate. */
		return(DAEMON_RET_SUCCESS);
	}

	if (pid < 0) {
		/* On a fork error, terminate everyone immediately. */
		exit(EXIT_FAILURE);
	}

	/* The child returns FAILURE to indicate that it should not terminate, but
	   rather carry on processing. */

	retval = DAEMON_RET_FAILURE;
#endif
#else
	DWORD win32Error;
	SERVICE_TABLE_ENTRY serviceTable[] = {
		{svcName, backuppcServiceStart},
		{ NULL, NULL}
	};

	if (!StartServiceCtrlDispatcher(serviceTable)) {
		win32Error = GetLastError();
		switch (win32Error) {
			case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
				/* Print no error here because we're not being
				   run as a service yet.  This will happen
				   later.  CreateService() will cause a new
				   process to be created where this will
				   succeed. */
				break;
			default:
				fprintf(stderr, "Could not start service dispatcher: %i\n", (int) win32Error);
				break;
		}
	} else {
		/* We were able to start the "Control Dispatcher" so we
		   indicate success.  This should tell the caller to
		   terminate since we'll be starting our own set of
		   processes. */
		retval = DAEMON_RET_SUCCESS;
	}
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static int daemon_stop(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   Upon sucessfully stopping the running daemon, 0 is returned.  Otherwise
 *   -1 is returned.
 *
 * NOTES:
 *   This function uses the Service Control functions on Win32.
 *   This function uses a pid file (BACKUPPC_PIDFILE) on POSIX platforms.
 *
 */
static int daemon_stop(void) {
	int retval = -1;
#ifndef _USE_WIN32_
	FILE *pidfile_fp = NULL;
	char buf[128];
	pid_t pid;
	int killret, unlinkret;

	pidfile_fp = fopen(BACKUPPC_PIDFILE, "r");
	if (pidfile_fp == NULL) {
		fprintf(stderr, "Service is not running.\n");
		return(-1);
	}

	fgets(buf, sizeof(buf), pidfile_fp);
	pid = strtol(buf, NULL, 10);

	if (pid <= 0) {
		fprintf(stderr, "Invalid process ID: %li\n", (long) pid);
		return(-1);
	}

	unlinkret = unlink(BACKUPPC_PIDFILE);
	if (unlinkret < 0) {
		fprintf(stderr, "Error removing pid file (\"%s\"): ", BACKUPPC_PIDFILE);
		perror("unlink");
	}

	killret = kill(pid, 1);
	if (killret < 0) {
		perror("kill");
		return(-1);
	}

#ifdef HAVE_SYSLOG
	syslog(LOG_INFO, "Terminating running backuppcd (pid = %lu)", (unsigned long) pid);
#endif

	retval = 0;
#else
	/* Open a connection to the SCM if there isn't one already. */
	if (manager == NULL) {
		manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (manager == NULL) {
			fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	/* Connect to our service. */
	service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
	if (service == NULL) {
		fprintf(stderr, "Could not open service: %i\n", (int) GetLastError());
		return(-1);
	}

	/* Ask ourselves to stop through the SCM. */
	if (!ControlService(service, SERVICE_CONTROL_STOP, &backuppcServiceStat)) {
		if (GetLastError() != ERROR_SERVICE_NOT_ACTIVE) {
			fprintf(stderr, "Could not stop service: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	fprintf(stderr, "Service stopped.\n");

	retval = 0;
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static int daemon_remove(void);
 *
 * ARGUMENTS:
 *   (none)
 *
 * RETURN VALUE:
 *   Upon sucessfully removing the (possibly running) daemon from startup, 0
 *   is returned.  Otherwise -1 is returned.
 *
 * NOTES:
 *   On win32 this does the services thing.
 *
 *   Otherwise, it cooperates with the supplied "backuppc.init" script to
 *   manage the SKIPSTARTFILE.  If this file exists, the "backuppc.init"
 *   script's start action aborts.  backuppcd deletes the file upon sucessful
 *   startup.  This function creates the file and stops the running daemon.
 *
 */
static int daemon_remove(void) {
	int retval = -1;
	int stop_ret;

	stop_ret = daemon_stop();

	if (stop_ret < 0) {
		retval = -1;
	}

#ifndef _USE_WIN32_
	creat(SKIPSTARTFILE, 0600);
	retval = 0;
#else
	if (manager == NULL) {
		manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (manager == NULL) {
			fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
			return(-1);
		}
	}

	service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
	if (service == NULL) {
		fprintf(stderr, "Could not open service: %i\n", (int) GetLastError());
		return(-1);
	}

	if (!DeleteService(service)) {
		fprintf(stderr, "Could not delete service: %i\n", (int) GetLastError());
		return(-1);
	}

	fprintf(stderr, "Service deleted.\n");

	retval = 0;
#endif
	return(retval);
}

/*
 * SYNOPSIS:
 *   static void daemon_start(
 *                            int argc,
 *                            char **argv
 *                           );
 *
 * ARGUMENTS:
 *   int argc               Number of arguments
 *   char *argv             Argument data
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static void daemon_start(int argc, char **argv) {
#ifdef _USE_WIN32_
	/* Create a Win32 Service. */
	/* Most of this was taken from "tinc" (src/process.c) */
#ifdef NO_WIN32_NT4
	/* Windows NT 4.0 lacks the `ChangeServiceConfig2' call. */
	SERVICE_DESCRIPTION description = {"BackupPC Client Daemon"};
#endif
	char svcCommand[8192], *svcptr;
	int argind;

	/*
	 * This whole mess is to construct the "lpBinaryPathName" which needs
	 * quotes and all kinds of other non-sense.
	 */
	svcptr = svcCommand;

	strcpy(svcptr, "\"");
	svcptr += strlen(svcptr);  /* This is not the same as svcptr++. */

	/* If the full path wasn't specified, assume it's in the current directory. */
	if (strchr(argv[0], '\\') == NULL) {
		GetCurrentDirectory(sizeof(svcCommand) - (svcptr - svcCommand) - 1, svcptr);
		svcptr += strlen(svcptr);
		strncat(svcptr, "\\", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);
	}
	strncat(svcptr, argv[0], sizeof(svcCommand) - (svcptr - svcCommand) - 1);
	svcptr += strlen(svcptr);
	strncat(svcptr, "\"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
	svcptr += strlen(svcptr);

	for (argind = 1; argind < argc; argind++) {
		strncat(svcptr, " \"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);

		strncat(svcptr, argv[argind], sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);

		strncat(svcptr, "\"", sizeof(svcCommand) - (svcptr - svcCommand) - 1);
		svcptr += strlen(svcptr);
	}

	manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
	if (manager == NULL) {
		fprintf(stderr, "Could not contact service manager: %i\n", (int) GetLastError());
		exit(EXIT_FAILURE);
	}

	/*
         * This will ultimately spawn another set of processes so we may exit.
	 */
	service = CreateService(manager, svcName, svcName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, svcCommand, "NDIS", NULL, NULL, NULL, NULL);

	if (service == NULL) {
		if (GetLastError() == ERROR_SERVICE_EXISTS) {
			service = OpenService(manager, svcName, SERVICE_ALL_ACCESS);
		}

		if (service == NULL) {
			fprintf(stderr, "Could not create service: %i\n", (int) GetLastError());
			exit(EXIT_FAILURE);
		}
	}

#ifdef NO_WIN32_NT4
	/* Windows NT 4.0 lacks this call. */
	ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &description);
#endif

	if (!StartService(service, 0, NULL)) {
		fprintf(stderr, "Could not start service: %i\n", (int) GetLastError());
		exit(EXIT_FAILURE);
	} else {
		printf("Service started.\n");
	}


	exit(EXIT_SUCCESS);
#endif
	return;
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  );
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_remove_svc(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	if (daemon_remove() < 0) {
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
static int backuppc_stop_svc(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	if (daemon_stop() < 0) {
		exit(EXIT_FAILURE);
	}

	exit(EXIT_SUCCESS);
}

static int backuppc_showvers(const char *shortvar, const char *var, const char *arguments, const char *value, lc_flags_t flags, void *extra) {
	printf("%s\n", PACKAGE_VERSION);
	exit(EXIT_SUCCESS);
}
/*
 * SYNOPSIS:
 *   static void backuppc_pathmangle(
 *                                   char *pathname
 *                                  )
 *
 * ARGUMENTS:
 *   char *pathname               Pathname to mangle. 
 *
 * RETURN VALUE:
 *   (none)
 *
 * NOTES:
 *
 */
int main(int argc, char *argv[]) {
	int lc_p_ret = 0;
	char *update_source = NULL, *update_dest = NULL, *update_delefile = NULL;
	int do_switch = 0;

	lc_register_callback("Remove", 'r', LC_VAR_NONE, backuppc_remove_svc, NULL);
	lc_register_callback("Stop", 'k', LC_VAR_NONE, backuppc_stop_svc, NULL);
	lc_register_callback("Version", 'V', LC_VAR_NONE, backuppc_showvers, NULL);
	lc_register_var("Port", LC_VAR_INT, &backuppc_port, 'p');
	lc_register_var("UpdateURL", LC_VAR_STRING, &backuppc_updateurl, 'U');
	lc_register_var("Source", LC_VAR_STRING, &update_source, 0);
	lc_register_var("Destination", LC_VAR_STRING, &update_dest, 0);
	lc_register_var("Switch", LC_VAR_BOOL_BY_EXISTANCE, &do_switch, 0);
	lc_register_var("DeleteFile", LC_VAR_STRING, &update_delefile, 0);
	lc_register_var("BinaryFile", LC_VAR_STRING, &backuppc_binfile, 0);

	lc_p_ret = lc_process(argc, argv, "backuppcd", LC_CONF_SPACE, SYSCONFDIR "/backuppcd.conf");
	if (lc_p_ret < 0) {
		fprintf(stderr, "Error processing configuration information: %s.\n", lc_geterrstr());
		return(EXIT_FAILURE);
	}

	if (update_delefile) {
		unlink(update_delefile);
	}

	if (do_switch) {
		if (update_source == NULL || update_dest == NULL) {
			fprintf(stderr, "Error: You must provide a --Source and --Destination to use --Switch\n");
			return(EXIT_FAILURE);
		}

#if 1
		/*
		 * Overcome a bug in BackupPC version 0.0.16 where the "Dest"
		 * is specifed incorrectly.  This will code will go away on:
		 *    22 July 2005
		 * Since version 0.0.16 was only deployed at one site.
		 */
		if (strstr(update_dest, "BackupPC.exe") != NULL) {
			update_dest = "c:/program files/backuppcd/backuppcd.exe";
		}
#endif

		backuppc_switchupdate(update_source, update_dest, argc, argv);

		return(EXIT_SUCCESS);
	}

#ifndef _USE_WIN32_
	/*
	 * We cannot check for a new version at start-up on Windows because it
	 * really messes with the "Services" stuff.  Bummer, man.
	 */
	if (backuppc_updateurl) {
		backuppc_update(backuppc_updateurl, argc, argv);
	}
#endif

#ifndef DEBUG
	/*
	 * If daemon initialization went well, we're no longer needed.
	 */
	if (daemon_init() == DAEMON_RET_SUCCESS) {
		return(EXIT_SUCCESS);
	}

	daemon_start(argc, argv);
#endif

#ifdef HAVE_SIGNAL
	signal(SIGPIPE, SIG_IGN);
#endif
#ifdef HAVE_OPENLOG
	openlog("backuppcd", LOG_PID, LOG_DAEMON);
#endif

	return(backuppc_loop(argc, argv));
}
