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

#define BPC_CMD_GET_DATA (BPC_CMD_GET | 0x100)
#define BPC_MAX_RDIFF_BLOCKSIZE 8192

/*
 * This structure contains all the variables that are needed to keep track of
 * the state of a transfer (GET).
 */
struct bpc_transfer {
	uint64_t filesize;
	int64_t idealblocknum;
	uint32_t currblocknum;
	uint32_t blocksize;
	uint32_t blockoffset;
};

/*
 * This structure is used as a handle for a connection to a BackupPC server.
 */
struct bpc_conn {
	struct bpc_fileinfo dent;
	struct bpc_transfer tinfo;
	backuppc_cmd_t state;
	int fd;
};

/*
 * These values are used by _bpc_client_read() and _bpc_client_write() to
 * determine the type of variable to read or write.
 */
typedef enum {
        BPC_CDT_END,
        BPC_CDT_UINT8,
        BPC_CDT_UINT16,
        BPC_CDT_UINT32,
        BPC_CDT_UINT64,
        BPC_CDT_BYTEARRAY,
        BPC_CDT_STRING,
        BPC_CDT_STRING_PTR,
	BPC_CDT_SINK,
} bpc_clntdt_t;

#ifndef MSG_WAITALL
#define MSG_WAITALL 0
#endif

#ifndef BPC_MAXPATH_LEN
#define BPC_MAXPATH_LEN 4096
#endif                                                                                                                                    

/*
 * SYNOPSIS (internal):
 *   static int _bpc_client_read(
 *                               const int sockid,
 *                               ...
 *                              );
 *
 * ARGUMENTS:
 *   const int sockid             Socket descriptor to read from
 *   ...                          List of parameters with the appropriate type
 *                                and terminated by a BPC_CDT_END
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function reads a variable number of parameters in various formats
 *   from a socket descriptor and stores them in the pointer given.  To
 *   specify a value, use the appropriate value from `bpc_clntdt_t' followed
 *   by a pointer to the memory location to store the actual value (in most
 *   cases) or the size of the memory location and the start of the memory
 *   location (for BYTEARRAY, STRING, STRING_PTR).
 *
 *   If the pointer at the pointed to location is NULL and the data type is
 *   STRING_PTR (size) bytes will be allocated and (size-1) bytes read.
 *   You are responsible for deallocating allocated memory using free().
 *
 *   This is an internal function and not part of the LibBackupPCd API and
 *   therefore subject to change or removal.
 *
 * EXAMPLES:
 *   uint8_t val_u8;
 *   char *val_str = NULL;
 *
 *   // Read a uint8_t into val_u8
 *   if (!_bpc_client_read(sockid, BPC_CDT_UINT8, &val_u8, BPC_CDT_END)) {
 *         return(-1);
 *   }
 *
 *   // Read 4 bytes and a NIL termination char into newly allocated memory
 *   if (!_bpc_client_read(sockid, BPC_CDT_STRING_PTR, (size_t) 5, &val_str, BPC_CDT_END)) {
 *         return(-1);
 *   }
 *
 *   if (val_str) {
 *         free(val_str);
 *   }
 *
 */
static int _bpc_client_read(const int sockid, ...) {
	bpc_clntdt_t typeid;
	char tmpbuf[1024];
	va_list ap;
	uint8_t *u8v;
	uint16_t *u16v;
	uint32_t *u32v;
	uint64_t *u64v;
	char *cpv, **cppv;
	void *vpv = NULL, *vpv_s;
	size_t vpv_len, bytes_to_copy;
	ssize_t read_ret;

	va_start(ap, sockid);
	while (1) {
		typeid = va_arg(ap, bpc_clntdt_t);

		if (typeid == BPC_CDT_END) {
			break;
		}

		switch (typeid) {
			case BPC_CDT_UINT8:
				u8v = va_arg(ap, uint8_t *);
				vpv_len = sizeof(*u8v);
				vpv = u8v;
				break;
			case BPC_CDT_UINT16:
				u16v = va_arg(ap, uint16_t *);
				vpv_len = sizeof(*u16v);
				vpv = u16v;
				break;
			case BPC_CDT_UINT32:
				u32v = va_arg(ap, uint32_t *);
				vpv_len = sizeof(*u32v);
				vpv = u32v;
				break;
			case BPC_CDT_UINT64:
				u64v = va_arg(ap, uint64_t *);
				vpv_len = sizeof(*u64v);
				vpv = u64v;
				break;
			case BPC_CDT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_CDT_STRING:
				vpv_len = va_arg(ap, size_t);
				vpv_len--;
				cpv = va_arg(ap, char *);
				cpv[vpv_len] = '\0';
				vpv = cpv;
				break;
			case BPC_CDT_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;
			case BPC_CDT_SINK:
				vpv_len = va_arg(ap, size_t);
				vpv = NULL;
				break;
			case BPC_CDT_END:
				return(0);
				break;
			default:
				return(0);
		}

		vpv_s = vpv;
		while (vpv_len) {
			if (vpv) {
				read_ret = recv(sockid, vpv, vpv_len, MSG_WAITALL);
			} else {
				/*
				 * If we have been given no place to put the
				 * data, we just read it to a buffer and
				 * ignore it (aka, sink it).
				 */
				bytes_to_copy = sizeof(tmpbuf);
				if (vpv_len < bytes_to_copy) {
					bytes_to_copy = vpv_len;
				}
				read_ret = recv(sockid, tmpbuf, bytes_to_copy, MSG_WAITALL);
			}

			if (read_ret <= 0) {
				return(0);
			}

			vpv_len -= read_ret;

			if (vpv) {
				vpv += read_ret;
			}
		}
		vpv = vpv_s;

		switch (typeid) {
			case BPC_CDT_UINT16:
				u16v = vpv;
				*u16v = ntohs(*u16v);
				break;
			case BPC_CDT_UINT32:
				u32v = vpv;
				*u32v = ntohl(*u32v);
				break;
			case BPC_CDT_UINT64:
				u64v = vpv;
				*u64v = ntohll(*u64v);
				break;
			default:
				break;
		}
	}
	va_end(ap);

	return(1);
}

/*
 * SYNOPSIS (internal):
 *   static int _bpc_client_write(
 *                                const int sockid,
 *                                ...
 *                               );
 *
 * ARGUMENTS:
 *   const int sockid             Socket descriptor to write to
 *   ...                          List of parameters with the appropriate type
 *                                and terminated by a BPC_CDT_END
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function writes a variable number of parameters in various formats
 *   to a socket descriptor.  To specify a value, use the appropriate value
 *   from `bpc_clntdt_t' followed by a value of the appropriate type (in most
 *   cases), or the size of the data to write followed by the appropriate
 *   value (for BYTEARRAY, STRING, STRING_PTR)
 *
 *   This is an internal function and not part of the LibBackupPCd API and
 *   therefore subject to change or removal.
 *
 * EXAMPLES:
 *   uint8_t val_u8 = 3;
 *   char *val_str = "Joe";
 *
 *   // Write 3 in a uint8_t format to the socket
 *   if (!_bpc_client_write(sockid, BPC_CDT_UINT8, val_u8, BPC_CDT_END)) {
 *         return(-1);
 *   }
 *
 *   // Write 3 bytes from a string to the socket
 *   if (!_bpc_client_write(sockid, BPC_CDT_STRING, (size_t) 3, val_str, BPC_CDT_END)) {
 *         return(-1);
 *   }
 *
 */
static int _bpc_client_write(const int sockid, ...) {
	bpc_clntdt_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;
	ssize_t write_ret;

	va_start(ap, sockid);
	while (1) {
		typeid = va_arg(ap, bpc_clntdt_t);

		if (typeid == BPC_CDT_END) {
			break;
		}

		switch (typeid) {
			case BPC_CDT_UINT8:
				u8v = va_arg(ap, int);
				vpv_len = sizeof(u8v);
				vpv = &u8v;
				break;
			case BPC_CDT_UINT16:
				u16v = va_arg(ap, int);
				u16v = htons(u16v);
				vpv_len = sizeof(u16v);
				vpv = &u16v;
				break;
			case BPC_CDT_UINT32:
				u32v = va_arg(ap, uint32_t);
				u32v = htonl(u32v);
				vpv_len = sizeof(u32v);
				vpv = &u32v;
				break;
			case BPC_CDT_UINT64:
				u64v = va_arg(ap, uint64_t);
				u64v = htonll(u64v);
				vpv_len = sizeof(u64v);
				vpv = &u64v;
				break;
			case BPC_CDT_BYTEARRAY:
				vpv_len = va_arg(ap, size_t);
				vpv = va_arg(ap, void *);
				break;
			case BPC_CDT_STRING:
				cpv = va_arg(ap, char *);
				vpv_len = strlen(cpv);
				vpv = cpv;
				break;
			case BPC_CDT_SINK:
			case BPC_CDT_STRING_PTR:
			case BPC_CDT_END:
				return(0);
			default:
				return(0);
		}

		while (vpv_len) {
			write_ret = send(sockid, vpv, vpv_len, 0);

			if (write_ret < 0) {
				return(0);
			}

			vpv_len -= write_ret;
			vpv += write_ret;
		}
	}
	va_end(ap);

	return(1);
}

/*
 * SYNOPSIS:
 *   int bpc_auth(
 *                BPC_CONN *handle,
 *                const char *username,
 *                const char *password
 *               );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   const char *username         Username to authenticate with
 *   const char *password         Password to authenticate with
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function authenticates with the server specified by the handle.
 *
 * EXAMPLES:
 *
 *   // Authenticate with the user "user" and password "pass"
 *   if (!bpc_auth(bpc_handle, "user", "pass")) {
 *         return(-1);
 *   }
 */
int bpc_auth(BPC_CONN *handle, const char *username, const char *password) {
	uint8_t cmd_reply;
	uint8_t status;
	int sockid;

	if (!handle || !username || !password) {
		CHECKPOINT;
		return(0);
	}

	if (handle->state != BPC_CMD_NONE) {
		CHECKPOINT;
		return(0);
	}

	sockid = handle->fd;

	if (!_bpc_client_write(sockid,
	                      BPC_CDT_UINT8, (uint8_t) BPC_CMD_AUTH,
	                      BPC_CDT_UINT16, (uint16_t) strlen(username),
	                      BPC_CDT_UINT16, (uint16_t) strlen(password),
	                      BPC_CDT_STRING, username,
	                      BPC_CDT_STRING, password,
	                      BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	if (!_bpc_client_read(sockid,
	                     BPC_CDT_UINT8, (uint8_t *) &cmd_reply,
	                     BPC_CDT_UINT8, (uint8_t *) &status,
	                     BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	if (cmd_reply != BPC_CMD_AUTH_REPLY) {
		CHECKPOINT;
		return(0);
	}

	if (status != BPC_STATUS_OKAY) {
		CHECKPOINT;
		return(0);
	}

	CHECKPOINT;
	return(1);
}

/*
 * SYNOPSIS (internal):
 *   static int _bpc_listget_open(
 *                                BPC_CONN *handle,
 *                                backuppc_cmd_t sendcmd,
 *                                const char *rootpath,
 *                                const int recursive,
 *                                backuppc_hashid_t hashalgo,
 *                                const char *exclpat,
 *                                const char *inclpat
 *                               );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   backuppc_cmd_t sendcmd       Command to send (BPC_CMD_LIST or BPC_CMD_GET)
 *   const char *rootpath         Root path to start the GET or LIST from
 *   const int recursive          Boolean option to specify whether or not to
 *                                recurse into directories
 *   backuppc_hashid_t hashalgo   OR'd list of hash algorithms to ask the
 *                                server to compute for each file
 *   const char *exclpat          <<NOT USED:XXX>>
 *   const char *inclpat          <<NOT USED:XXX>>
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function initiates a LIST or GET command (depending on the value of
 *   the `sendcmd' parameter) and updates the internal state to indicate that
 *   it is in the middle of such a command.
 *
 *   You should then call _bpc_list() to get either the LIST results or the
 *   header of a GET result.
 *
 *   This is an internal function and not part of the LibBackupPCd API and
 *   therefore subject to change or removal.
 *
 * EXAMPLES:
 *
 *   // List every node under the root directory non-recursively with no
 *   // hashing performed on files.
 *   if (!_bpc_listget_open(bpc_handle, BPC_CMD_LIST, "/", 0, BPC_HASH_NONE, NULL, NULL)) {
 *         return(-1);
 *   }
 *
 */
static int _bpc_listget_open(BPC_CONN *handle, backuppc_cmd_t sendcmd, const char *rootpath, const int recursive, backuppc_hashid_t hashalgo, const char *exclpat, const char *inclpat) {
	uint8_t cmd, cmd_options = 0;
	int sockid;

	sockid = handle->fd;

	if (sendcmd != BPC_CMD_LIST && sendcmd != BPC_CMD_GET) {
		CHECKPOINT;
		return(0);
	}

	/*
	 * Handle NULL patterns as blank ones.
	 */
	if (!exclpat) {
		exclpat = "";
	}
	if (!inclpat) {
		inclpat = "";
	}

	if (recursive) {
		cmd_options |= BPC_OPT_RECURSIVE;
	}

	switch (hashalgo) {
		case BPC_HASH_NONE:
			break;
		case BPC_HASH_MD4:
			cmd_options |= BPC_OPT_MD4;
			break;
		case BPC_HASH_MD5:
			cmd_options |= BPC_OPT_MD5;
			break;
		case BPC_HASH_SHA1:
			cmd_options |= BPC_OPT_SHA1;
			break;
		case BPC_HASH_BPC:
			cmd_options |= BPC_OPT_BPCHASH;
			break;
	}

	if (!_bpc_client_write(sockid,
	                      BPC_CDT_UINT8, (uint8_t) sendcmd,
	                      BPC_CDT_UINT8, (uint8_t) cmd_options,
	                      BPC_CDT_UINT32, (uint32_t) strlen(exclpat),
	                      BPC_CDT_UINT32, (uint32_t) strlen(inclpat),
	                      BPC_CDT_UINT32, (uint32_t) strlen(rootpath),
	                      BPC_CDT_STRING, (char *) rootpath,
	                      BPC_CDT_STRING, (char *) exclpat,
	                      BPC_CDT_STRING, (char *) inclpat,
	                      BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	if (!_bpc_client_read(sockid,
	                     BPC_CDT_UINT8, (uint8_t *) &cmd,
	                     BPC_CDT_END)) {
		CHECKPOINT;
		return(0);
	}

	/*
	 * If the server sends us a reply for something other than what we
	 * asked for, something went wrong, abort.
	 */
	if (cmd != (sendcmd | 0x80)) {
		CHECKPOINT;
		return(0);
	}

	handle->state = sendcmd;

	handle->dent.owner_group[0] = '\0';
	handle->dent.owner_user[0] = '\0';
	handle->dent.linkdest[0] = '\0';
	handle->dent.name[0] = '\0';

	memset(&handle->dent.hash_md4, 0, sizeof(handle->dent.hash_md4));
	memset(&handle->dent.hash_md5, 0, sizeof(handle->dent.hash_md5));
	memset(&handle->dent.hash_sha1, 0, sizeof(handle->dent.hash_sha1));
	memset(&handle->dent.hash_bpc, 0, sizeof(handle->dent.hash_bpc));

	CHECKPOINT;
	return(1);
}

/*
 * SYNOPSIS (internal):
 *   static int _bpc_listget_close(
 *                                 BPC_CONN *handle
 *                                );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function sets the state of a handle back to NONE after a LIST or GET
 *   request has finished.
 *
 *   This is an internal function and not part of the LibBackupPCd API and
 *   therefore subject to change or removal.
 *
 * EXAMPLES:
 *
 *   // Terminate an existing LIST or GET command
 *   if (!_bpc_listget_close(bpc_handle)) {
 *         return(-1);
 *   }
 *
 */
static int _bpc_listget_close(BPC_CONN *handle) {

	handle->state = BPC_CMD_NONE;

	CHECKPOINT;
	return(1);
}

/*
 * SYNOPSIS (internal):
 *   static struct bpc_fileinfo *_bpc_list(
 *                                         BPC_CONN *handle,
 *                                         backuppc_cmd_t cmd
 *                                        );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   backuppc_cmd_t cmd           Command to process results from (LIST or GET)
 *
 * RETURN VALUE:
 *   This function returns a pointer to a (struct bpc_fileinfo) object.  The
 *   contents of this objects may be overwritten with subsequent calls to
 *   _bpc_list() for the same handle.  NULL is returned on error or when no
 *   more objects exist in the stream.
 *
 * NOTES:
 *   This function gets the contents of a LIST_REPLY or GET_REPLY from the
 *   handle and sets them in the (struct bpc_fileinfo) value that it
 *   returns.  If it is processing a GET_REPLY it also initializes the
 *   values in the (struct bpc_transfer) members.
 *
 *   This is an internal function and not part of the LibBackupPCd API and
 *   therefore subject to change or removal.
 *
 * EXAMPLES:
 *
 *   // Get an item from an open LIST command
 *   struct bpc_fileinfo *fileinfo;
 *
 *   fileinfo = _bpc_list(bpc_handle, BPC_CMD_LIST);
 *
 *   if (!fileinfo) {
 *         return(-1);
 *   }
 *
 */
static struct bpc_fileinfo *_bpc_list(BPC_CONN *handle, backuppc_cmd_t cmd) {
	struct bpc_fileinfo *ret;
	struct bpc_transfer *tinfo;
	int sockid;

	uint64_t filesize;
	uint32_t attr_sect_len, blocksize, pathnamelen;
	uint32_t attr_uid, attr_gid, attr_ctime, attr_mtime, attr_devmajor, attr_devminor;
	uint32_t attrlen;
	uint16_t attrid;
	uint16_t attr_mode;
	uint8_t filetype;
	mode_t modeval;

	ret = &handle->dent;
	tinfo = &handle->tinfo;

	sockid = handle->fd;

	if (!_bpc_client_read(sockid,
	                      BPC_CDT_UINT8, (uint8_t *) &filetype,
	                      BPC_CDT_END)) {
		CHECKPOINT;
		return(NULL);
	}

	if (filetype == 0xff) {
		_bpc_listget_close(handle);
		CHECKPOINT;
		return(NULL);
	}

	if (!_bpc_client_read(sockid,
	                      BPC_CDT_UINT32, (uint32_t *) &attr_sect_len,
	                      BPC_CDT_UINT64, (uint64_t *) &filesize,
	                      BPC_CDT_UINT32, (uint32_t *) &blocksize,
	                      BPC_CDT_UINT32, (uint32_t *) &pathnamelen,
	                      BPC_CDT_END)) {
		CHECKPOINT;
		return(NULL);
	}

	if (pathnamelen >= sizeof(ret->name)) {
		SPOTVAR_I(pathnamelen);
		CHECKPOINT;
		return(NULL);
	}

	if (!_bpc_client_read(sockid,
	                      BPC_CDT_STRING, (size_t) (pathnamelen + 1), &ret->name,
	                      BPC_CDT_END)) {
		CHECKPOINT;
		return(NULL);
	}

	attr_uid = 0;
	attr_gid = 0;
	attr_mode = 0;
	attr_ctime = 0;
	attr_mtime = 0;
	attr_devmajor = 0;
	attr_devminor = 0;

	memset(ret->hash_md4, '\0', sizeof(ret->hash_md4));
	memset(ret->hash_md5, '\0', sizeof(ret->hash_md5));
	memset(ret->hash_bpc, '\0', sizeof(ret->hash_bpc));
	memset(ret->hash_sha1, '\0', sizeof(ret->hash_sha1));

	/*
	 * Read the sent attributes.
	 */
	if (attr_sect_len != 0) {
		while (1) {
			if (!_bpc_client_read(sockid,
			                      BPC_CDT_UINT16, (uint16_t *) &attrid,
			                      BPC_CDT_END)) {
				CHECKPOINT;
				return(NULL);
			}

			if (attrid == 0xffff) {
				break;
			}

			if (!_bpc_client_read(sockid,
			                      BPC_CDT_UINT32, (uint32_t *) &attrlen,
			                      BPC_CDT_END)) {
				CHECKPOINT;
				return(NULL);
			}

			switch ((backuppc_attrid_t) attrid) {
				case BPC_ATTRID_NOP:
					if (attrlen != 0) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_MTIME:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_UINT32, (uint32_t *) &attr_mtime,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_CTIME:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_UINT32, (uint32_t *) &attr_ctime,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_UID:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_UINT32, (uint32_t *) &attr_uid,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_GID:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_UINT32, (uint32_t *) &attr_gid,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_ACL:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_UINT16, (uint16_t *) &attr_mode,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}

					break;
				case BPC_ATTRID_USER:
					if (attrlen >= sizeof(ret->owner_user)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_STRING, (size_t) attrlen + 1, (char *) &ret->owner_user,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_GROUP:
					if (attrlen >= sizeof(ret->owner_group)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_STRING, (size_t) attrlen + 1, (char *) &ret->owner_group,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_SYMLINKDEST:
					if (attrlen >= sizeof(ret->linkdest)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_STRING, (size_t) attrlen + 1, (char *) &ret->linkdest,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_HRDLINKDEST:
					if (attrlen >= sizeof(ret->linkdest)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_STRING, (size_t) attrlen + 1, (char *) &ret->linkdest,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_MD4:
					if (attrlen != sizeof(ret->hash_md4)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_BYTEARRAY, (size_t) sizeof(ret->hash_md4), (void *) &ret->hash_md4,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_MD5:
					if (attrlen != sizeof(ret->hash_md5)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_BYTEARRAY, (size_t) sizeof(ret->hash_md5), (void *) &ret->hash_md5,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_SHA1:
					if (attrlen != sizeof(ret->hash_sha1)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_BYTEARRAY, (size_t) sizeof(ret->hash_sha1), (void *) &ret->hash_sha1,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
				case BPC_ATTRID_BPCHASH:
					if (attrlen != sizeof(ret->hash_bpc)) {
						CHECKPOINT;
						return(NULL);
					}

					if (!_bpc_client_read(sockid,
					                      BPC_CDT_BYTEARRAY, (size_t) sizeof(ret->hash_bpc), (void *) &ret->hash_bpc,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
#ifndef DEBUG
				default:
					if (!_bpc_client_read(sockid,
					                      BPC_CDT_SINK, (size_t) attrlen,
					                      BPC_CDT_END)) {
						CHECKPOINT;
						return(NULL);
					}
					break;
#endif
			}
		}
	}

	/*
	 * Determine the POSIX "mode" value from the ACL attrs.
	 */
	modeval = 0;
	if (attr_mode) {
		if ((attr_mode & BPC_ACL_XUSR) == BPC_ACL_XUSR) {
			modeval |= S_IXUSR;
		}
		if ((attr_mode & BPC_ACL_WUSR) == BPC_ACL_WUSR) {
			modeval |= S_IWUSR;
		}
		if ((attr_mode & BPC_ACL_RUSR) == BPC_ACL_RUSR) {
			modeval |= S_IRUSR;
		}
#ifndef _USE_WIN32_
		if ((attr_mode & BPC_ACL_XGRP) == BPC_ACL_XGRP) {
			modeval |= S_IXGRP;
		}
		if ((attr_mode & BPC_ACL_WGRP) == BPC_ACL_WGRP) {
			modeval |= S_IWGRP;
		}
		if ((attr_mode & BPC_ACL_RGRP) == BPC_ACL_RGRP) {
			modeval |= S_IRGRP;
		}
		if ((attr_mode & BPC_ACL_XOTH) == BPC_ACL_XOTH) {
			modeval |= S_IXOTH;
		}
		if ((attr_mode & BPC_ACL_WOTH) == BPC_ACL_WOTH) {
			modeval |= S_IWOTH;
		}
		if ((attr_mode & BPC_ACL_ROTH) == BPC_ACL_ROTH) {
			modeval |= S_IROTH;
		}
		if ((attr_mode & BPC_ACL_STCK) == BPC_ACL_STCK) {
			modeval |= S_ISVTX;
		}
		if ((attr_mode & BPC_ACL_SGID) == BPC_ACL_SGID) {
			modeval |= S_ISGID;
		}
		if ((attr_mode & BPC_ACL_SUID) == BPC_ACL_SUID) {
			modeval |= S_ISUID;
		}
#endif
	}

#if 0
	if (filetype == BPC_FILE_REG) {
		modeval |= S_IFREG;
	}
#ifndef _USE_WIN32_
	if (filetype == BPC_FILE_SYMLINK) {
		modeval |= S_IFLNK;
	}
	if (filetype == BPC_FILE_SOCKET) {
		modeval |= S_IFSOCK;
	}
#endif
	if (filetype == BPC_FILE_DIR) {
		modeval |= S_IFDIR;
	}
	if (filetype == BPC_FILE_BDEV) {
		modeval |= S_IFBLK;
	}
	if (filetype == BPC_FILE_CDEV) {
		modeval |= S_IFCHR;
	}
	if (filetype == BPC_FILE_FIFO) {
		modeval |= S_IFIFO;
	}
#endif

	ret->mode = modeval;
	ret->type = filetype;
	ret->size = filesize;
	ret->uid = attr_uid;
	ret->gid = attr_gid;
	ret->mtime = attr_mtime;
	ret->ctime = attr_ctime;

	if (cmd == BPC_CMD_GET) {
		tinfo->blocksize = blocksize;
		tinfo->idealblocknum = -1;
		tinfo->currblocknum = 0;
		tinfo->filesize = filesize;
		tinfo->blockoffset = 0;
	}

	return(ret);
}

/*
 * SYNOPSIS:
 *   int bpc_list_open(
 *                     BPC_CONN *handle,
 *                     const char *rootpath,
 *                     const int recursive,
 *                     backuppc_hashid_t hashalgo,
 *                     const char *exclpat,
 *                     const char *inclpat
 *                    );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   const char *rootpath         Root path to start the GET or LIST from
 *   const int recursive          Boolean option to specify whether or not to
 *                                recurse into directories
 *   backuppc_hashid_t hashalgo   OR'd list of hash algorithms to ask the
 *                                server to compute for each file
 *   const char *exclpat          <<NOT USED:XXX>>
 *   const char *inclpat          <<NOT USED:XXX>>
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function initiates a LIST command (by calling _bpc_listget_open()).
 *   You must not be in the middle of any other command at the time.
 *
 *   You should then call bpc_list() to get the LIST results.
 *
 * EXAMPLES:
 *
 *   // List every node under the root directory non-recursively with no
 *   // hashing performed on files.
 *   if (!bpc_list_open(bpc_handle, "/", 0, BPC_HASH_NONE, NULL, NULL)) {
 *         return(-1);
 *   }
 *
 */
int bpc_list_open(BPC_CONN *handle, const char *rootpath, const int recursive, backuppc_hashid_t hashalgo, const char *exclpat, const char *inclpat) {
	if (!handle) {
		return(0);
	}

	if (handle->state != BPC_CMD_NONE) {
		return(0);
	}

	return(_bpc_listget_open(handle, BPC_CMD_LIST, rootpath, recursive, hashalgo, exclpat, inclpat));
}

/*
 * SYNOPSIS:
 *   int bpc_list_close(
 *                      BPC_CONN *handle
 *                     );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function verifies that a previous operation completed
 *   sucessfully and that we are now not in the middle of any command.
 *
 * EXAMPLES:
 *
 *   // Verify that the previous LIST command is complete
 *   if (!bpc_list_close(bpc_handle)) {
 *         return(-1);
 *   }
 *
 */
int bpc_list_close(BPC_CONN *handle) {
	if (!handle) {
		return(0);
	}

	if (handle->state != BPC_CMD_NONE) {
		return(0);
	}

	return(1);
}

/*
 * SYNOPSIS:
 *   struct bpc_fileinfo *bpc_list(
 *                                 BPC_CONN *handle,
 *                                );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns a pointer to a (struct bpc_fileinfo) object.  The
 *   contents of this objects may be overwritten with subsequent calls to
 *   bpc_list() for the same handle.  NULL is returned on error or when no
 *   more objects exist in the stream.
 *
 * NOTES:
 *   This function gets the contents of a LIST_REPLY from the handle and sets
 *   them in the (struct bpc_fileinfo) value that it returns.
 *
 * EXAMPLES:
 *
 *   // Get an item from an open LIST command
 *   struct bpc_fileinfo *fileinfo;
 *
 *   fileinfo = bpc_list(bpc_handle);
 *
 *   if (!fileinfo) {
 *         return(-1);
 *   }
 *
 */
struct bpc_fileinfo *bpc_list(BPC_CONN *handle) {
	if (!handle) {
		return(NULL);
	}

	if (handle->state != BPC_CMD_LIST) {
		return(NULL);
	}

	return(_bpc_list(handle, BPC_CMD_LIST));
}

/*
 * SYNOPSIS:
 *   int bpc_get_open(
 *                    BPC_CONN *handle,
 *                    const char *rootpath,
 *                    const int recursive,
 *                    backuppc_hashid_t hashalgo,
 *                    const char *exclpat,
 *                    const char *inclpat
 *                   );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   const char *rootpath         Root path to start the GET or LIST from
 *   const int recursive          Boolean option to specify whether or not to
 *                                recurse into directories
 *   backuppc_hashid_t hashalgo   OR'd list of hash algorithms to ask the
 *                                server to compute for each file
 *   const char *exclpat          <<NOT USED:XXX>>
 *   const char *inclpat          <<NOT USED:XXX>>
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function initiates a GET command and updates the internal state to
 *   indicate that it is in the middle of such a command.
 *
 *   You should then call bpc_get_head() to get the header of the GET result.
 *
 * EXAMPLES:
 *
 *   // Get every node under "/" non-recursively and do not perform any
 *   // hashing on the files.
 *   if (!bpc_get_open(bpc_handle, "/", 0, BPC_HASH_NONE, NULL, NULL)) {
 *         return(-1);
 *   }
 *
 */
int bpc_get_open(BPC_CONN *handle, const char *rootpath, const int recursive, backuppc_hashid_t hashalgo, const char *exclpat, const char *inclpat) {
	int retval;

	if (!handle) {
		CHECKPOINT;
		return(0);
	}

	if (handle->state != BPC_CMD_NONE) {
		CHECKPOINT;
		return(0);
	}

	retval = _bpc_listget_open(handle, BPC_CMD_GET, rootpath, recursive, hashalgo, exclpat, inclpat);

	if (retval == 0) {
		CHECKPOINT;
		return(0);
	}

	CHECKPOINT;
	return(retval);
}

/*
 * SYNOPSIS:
 *   int bpc_get_close(
 *                     BPC_CONN *handle
 *                    );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns 0 on failure, 1 on success.
 *
 * NOTES:
 *   This function verifies that a previous operation completed
 *   sucessfully and that we are now not in the middle of any command.
 *
 * EXAMPLES:
 *
 *   // Verify that we have terminated a previously existing GET command
 *   if (!bpc_get_close(bpc_handle)) {
 *         return(-1);
 *   }
 *
 */
int bpc_get_close(BPC_CONN *handle) {
	if (!handle) {
		return(0);
	}

	if (handle->state != BPC_CMD_NONE) {
		return(0);
	}

	return(1);
}

/*
 * SYNOPSIS (internal):
 *   struct bpc_fileinfo *bpc_get_head(
 *                                     BPC_CONN *handle,
 *                                    );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns a pointer to a (struct bpc_fileinfo) object.  The
 *   contents of this objects may be overwritten with subsequent calls to
 *   bpc_get_head() for the same handle.  NULL is returned on error or when no
 *   more objects exist in the stream.
 *
 * NOTES:
 *   This function gets the header of a GET_REPLY from the handle and sets
 *   them in the (struct bpc_fileinfo) value that it returns.
 *
 *   You should then call bpc_get() if the header indicates that this
 *   represents this is a regular file.
 *
 * EXAMPLES:
 *
 *   // Get an item from an open LIST command
 *   struct bpc_fileinfo *fileinfo;
 *
 *   fileinfo = bpc_get_head(bpc_handle);
 *
 *   if (!fileinfo) {
 *         return(-1);
 *   }
 *
 */
struct bpc_fileinfo *bpc_get_head(BPC_CONN *handle) {
	uint32_t blocknum;
	struct bpc_fileinfo *ret;

	if (!handle) {
		CHECKPOINT;
		return(NULL);
	}

	if (handle->state != BPC_CMD_GET) {
		CHECKPOINT;
		return(NULL);
	}

	/*
	 * The "GET" operation is the same as the "LIST" operation, except
	 * data will follow if the type is BPC_FILE_REG.
	 */
	ret = _bpc_list(handle, BPC_CMD_GET);

	if (!ret) {
		CHECKPOINT;
		return(NULL);
	}

	if (ret->type != BPC_FILE_REG) {
		if (!_bpc_client_read(handle->fd,
		                      BPC_CDT_UINT32, &blocknum,
		                      BPC_CDT_END)) {
			CHECKPOINT;
			return(NULL);
		}

		if (blocknum != 0xffffffff) {
			CHECKPOINT;
			return(NULL);
		}

		CHECKPOINT;
		return(ret);
	}

	handle->state = BPC_CMD_GET_DATA;

	return(ret);
}

/*
 * SYNOPSIS:
 *   ssize_t bpc_get(
 *                   BPC_CONN *handle,
 *                   char *buf,
 *                   size_t count
 *                  );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *   void *buf                    Pointer to a buffer to return data into
 *   size_t count                 Size of buffer
 *
 * RETURN VALUE:
 *   This function returns the number of bytes written to the memory pointed to
 *   by `buf' on success.  On error -1 is returned.
 *
 * NOTES:
 *
 * EXAMPLES:
 *
 *   // Get an item from an open LIST command
 *   ssize_t ret;
 *   char buf[20];
 *
 *   ret = bpc_get(bpc_handle, buf, sizeof(buf));
 *
 *   if (ret < 0) {
 *         return(-1);
 *   }
 *
 */
ssize_t bpc_get(BPC_CONN *handle, void *buf, size_t count) {
	struct bpc_transfer *tinfo;
	ssize_t recv_ret;
	uint32_t bytes_left;
	uint32_t sinkbytes = 0;
	int sockid;

	if (!handle) {
		CHECKPOINT;
		return(-1);
	}

	if (handle->state != BPC_CMD_GET_DATA) {
		CHECKPOINT;
		return(-1);
	}

	sockid = handle->fd;

	tinfo = &handle->tinfo;

	if (tinfo->blockoffset == 0) {
		if (tinfo->currblocknum == tinfo->idealblocknum || tinfo->idealblocknum == -1) {
			if (!_bpc_client_read(sockid,
			                      BPC_CDT_UINT32, &tinfo->currblocknum,
			                      BPC_CDT_END)) {
				CHECKPOINT;
				return(-1);
			}

			if (tinfo->currblocknum == 0xffffffff) {
				handle->state = BPC_CMD_GET;
				CHECKPOINT;
				return(0);
			}
		}

		if (tinfo->currblocknum == 0) {
			tinfo->idealblocknum = 0;
		} else {
			tinfo->idealblocknum++;
		}
	}

	bytes_left = tinfo->blocksize - tinfo->blockoffset;

	if (count > bytes_left) {
		count = bytes_left;
	}

	if ((tinfo->idealblocknum * tinfo->blocksize + tinfo->blockoffset) > tinfo->filesize) {
		count = 0;
		sinkbytes = tinfo->blocksize - tinfo->blockoffset;
	} else {
		if ((tinfo->idealblocknum * tinfo->blocksize + tinfo->blockoffset + count) > tinfo->filesize) {
			count = tinfo->filesize - (tinfo->idealblocknum * tinfo->blocksize + tinfo->blockoffset);
			sinkbytes = tinfo->blocksize - count;
		}
	}

	if (tinfo->currblocknum != tinfo->idealblocknum) {
		memset(buf, '\0', count);
		recv_ret = count;
	} else {
		recv_ret = recv(sockid, buf, count, MSG_WAITALL);

		if (sinkbytes) {
			if (!_bpc_client_read(sockid,
			                      BPC_CDT_SINK, (ssize_t) sinkbytes,
			                      BPC_CDT_END)) {
				CHECKPOINT;
				return(-1);
			}
		}
	}

	if (recv_ret < 0) {
		CHECKPOINT;
		return(-1);
	}

	tinfo->blockoffset += recv_ret + sinkbytes;

	if (tinfo->blockoffset == tinfo->blocksize) {
		tinfo->blockoffset = 0;
	}

	CHECKPOINT;
	return(recv_ret);
}

int bpc_copy(BPC_CONN *handle, bpc_mode_t mode, int fd) {
	char buf[32768];
	ssize_t read_ret, write_ret;
	int write_fail_count = 0;
	int retval = 1;

	while (1) {
		read_ret = bpc_get(handle, buf, sizeof(buf));

		if (read_ret == 0) {
			break;
		}

		if (read_ret < 0) {
			retval = 0;
			break;
		}

		while (read_ret) {
			switch (mode) {
				case BPC_MODE_WRITE:
					write_ret = write(fd, buf, read_ret);
					break;
				case BPC_MODE_SEND:
					write_ret = send(fd, buf, read_ret, 0);
					break;
				default:
					write_ret = -1;
			}
			

			if (write_ret < 0) {
				retval = 0;
				break;
			}

			if (write_ret == 0) {
				write_fail_count++;
			} else {
				write_fail_count = 0;
			}

			if (write_fail_count >= 10) {
				retval = 0;
				break;
			}

			read_ret -= write_ret;
		}
	}

	CHECKPOINT;
	return(retval);
}

int bpc_rdiffget(BPC_CONN *handle, const char *remote_src, const char *dest) {
	backuppc_hashid_t hashalgo = BPC_HASH_MD4;
	struct stat stbuf;
	uint64_t filesize;
	uint32_t blocksize;
	uint32_t num_hash_ents, hash_ent, hash_ent_size, hash_list_size;
	ssize_t read_ret;
	char buf[BPC_MAX_RDIFF_BLOCKSIZE];
	void *hash_handle;
	int stat_ret;
	int sockid;
	int fd;

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

	sockid = handle->fd;

	stat_ret = stat(dest, &stbuf);
	if (stat_ret < 0) {
		filesize = 0xffffffffffffffffLLU;
		blocksize = 0;
		num_hash_ents = 0;
	} else {
		filesize = stbuf.st_size;

		/*
		 * Determine the block size:
		 * 	0 bytes to 20 MBytes:  2 KBytes
		 * 	20 MBytes to 1 GByte:  4 KBytes
		 *	1 GByte to Infinity:   8 KBytes
		 */
		if (filesize < (20 * 1024 * 1024)) {
			blocksize = 2048;
		} else if (filesize < (1024 * 1024 * 1024)) {
			blocksize = 4096;
		} else {
			blocksize = BPC_MAX_RDIFF_BLOCKSIZE;
		}

		if (blocksize > BPC_MAX_RDIFF_BLOCKSIZE) {
			blocksize = BPC_MAX_RDIFF_BLOCKSIZE;
		}

		num_hash_ents = (filesize + blocksize - 1) / blocksize;
	}

	switch (hashalgo) {
		case BPC_HASH_NONE:
			break;
		case BPC_HASH_MD5:
			hash_ent_size = (128 / 8);
			hash_handle = NULL;
			break;
		case BPC_HASH_MD4:
			hash_ent_size = (128 / 8);
			hash_handle = NULL;
			break;
		case BPC_HASH_SHA1:
			hash_ent_size = (160 / 8);
			hash_handle = malloc(sizeof(SHA1_CTX));
			break;
		case BPC_HASH_BPC:
			hash_ent_size = (128 / 8);
			hash_handle = NULL;
			break;
	}

	hash_list_size = num_hash_ents * hash_ent_size;

	if (!_bpc_client_write(sockid,
	                       BPC_CDT_UINT8, (uint8_t) BPC_CMD_RDGET,
	                       BPC_CDT_UINT64, (uint64_t) filesize,
	                       BPC_CDT_UINT32, (uint32_t) blocksize, 
	                       BPC_CDT_UINT8, (uint8_t) hashalgo,
	                       BPC_CDT_UINT32, (uint32_t) hash_list_size, 
	                       BPC_CDT_UINT32, (uint32_t) strlen(remote_src),
	                       BPC_CDT_STRING, (char *) remote_src,
	                       BPC_CDT_END)) {
		return(0);
	}

	fd = open(dest, O_RDONLY);

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

	for (hash_ent = 0; hash_ent < num_hash_ents; hash_ent++) {
		read_ret = read(fd, buf, blocksize);
		if (read_ret < 0) {
			read_ret = 0;
		}

		if (read_ret == 0) {
			CHECKPOINT;
		}

		switch (hashalgo) {
			case BPC_HASH_NONE:
				break;
			case BPC_HASH_SHA1:
				SHA1Init(hash_handle);
				SHA1Update(hash_handle, buf, read_ret);
//				SHA1Final(digest[20], hash_handle);
				break;
			case BPC_HASH_MD5:
			case BPC_HASH_MD4:
			case BPC_HASH_BPC:
				break;
		}
	}

	close(fd);

	if (hash_handle) {
		free(hash_handle);
	}

	return(1);
}

int bpc_copyfile(BPC_CONN *handle, struct bpc_fileinfo *src, const char *dest, int preserve) {
	int fd;
	int retval = 0;
	int sys_ret;

	switch (src->type) {
		case BPC_FILE_DIR:
			backuppc_mkdir(dest);
			retval = 1;
			break;
		case BPC_FILE_SYMLINK:
#ifdef HAVE_SYMLINK
			sys_ret = symlink(src->linkdest, dest);
			if (sys_ret == 0) {
				retval = 1;
			}
#endif
			break;
		case BPC_FILE_HRDLINK:
			sys_ret = link(src->linkdest, dest);
			if (sys_ret == 0) {
				retval = 1;
			}
			break;
		case BPC_FILE_CDEV:
		case BPC_FILE_BDEV:
		case BPC_FILE_SOCKET:
		case BPC_FILE_FIFO:
		case BPC_FILE_UNKNOWN:
			CHECKPOINT;
			return(0); 
		case BPC_FILE_REG:
			/*
			 * Regular files are handled seperately
			 */
			break;
	}

	if (src->type != BPC_FILE_REG) {
		if (retval && preserve) {
			chmod(dest, src->mode);
#ifdef HAVE_LCHOWN
			lchown(dest, src->uid, src->gid);
#else
			chown(dest, src->uid, src->gid);
#endif
#ifdef HAVE_UTIME
//			utime(...)
#endif
		}
		CHECKPOINT;
		return(retval);
	}


	/*
	 * Handle the regular files using either RDIFF or GET.
	 */
	if (access(dest, F_OK) == 0 && 0 /* XXX: RGET NOT IMPLEMENTED */) {
		/*
		 * Do RDIFF 
		 */
	} else {
		/*
		 * Copy the entire file.
		 */
		fd = open(dest, O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0600);

		if (bpc_copy(handle, BPC_MODE_WRITE, fd)) {
			retval = 1;
		}

		close(fd);
	}
	if (preserve) {
		chmod(dest, src->mode);
		chown(dest, src->uid, src->gid);
#ifdef HAVE_UTIME
//		utime(...)
#endif
	}

	CHECKPOINT;
	return(retval);
}

/*
 * SYNOPSIS:
 *   BPC_CONN *bpc_connect(
 *                         const char *host,
 *                         const int port,
 *                         const char *username,
 *                         const char *password
 *                        );
 *
 * ARGUMENTS:
 *   const char *host             BackupPCd host to connect to
 *   const int port               Port to connect to host on (default if == 0)
 *   const char *username         (optional) Username to authenticate with
 *   const char *password         (optional) Password to authenticate with
 *
 * RETURN VALUE:
 *   This function returns NULL on failure, a pointer to a (BPC_CONN) on success.
 *
 * NOTES:
 *
 * EXAMPLES:
 *
 */
BPC_CONN *bpc_connect(const char *host, const int port, const char *username, const char *password) {
	BPC_CONN *ret;
	int sockid;
	int real_port;

	if (port == 0) {
		real_port = BPC_TCP_PORT;
	} else {
		real_port = port;
	}

	sockid = net_connect_tcp(host, real_port);

	if (sockid < 0) {
		CHECKPOINT;
		return(NULL);
	}

	ret = malloc(sizeof(*ret));

	if (!ret) {
		close(sockid);

		CHECKPOINT;
		return(NULL);
	}

	ret->fd = sockid;
	ret->state = BPC_CMD_NONE;

	if (username && password) {
		if (!bpc_auth(ret, username, password)) {
			close(ret->fd);
			free(ret);

			CHECKPOINT;
			return(NULL);
		}
	}

	CHECKPOINT;
	return(ret);
}

/*
 * SYNOPSIS:
 *   int bpc_disconnect(
 *                      BPC_CONN *handle
 *                     );
 *
 * ARGUMENTS:
 *   BPC_CONN *handle             Connection handle
 *
 * RETURN VALUE:
 *   This function returns -1 on failure, 0 on success.
 *
 * NOTES:
 *
 * EXAMPLES:
 *
 */
int bpc_disconnect(BPC_CONN *handle) {
	int close_ret;

	if (!handle) {
		CHECKPOINT;
		return(-1);
	}

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

	close_ret = net_close(handle->fd);

	if (close_ret < 0) {
		CHECKPOINT;
		return(close_ret);
	}

	free(handle);

	CHECKPOINT;
	return(0);
}
