/*-
 * Copyright (c) 2000-2005 MAEKAWA Masahide <maekawa@cvsync.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>

#include <stdlib.h>

#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <string.h>

#include "compat_stdbool.h"
#include "compat_stdint.h"
#include "compat_inttypes.h"
#include "compat_limits.h"
#include "basedef.h"

#include "attribute.h"
#include "collection.h"
#include "cvsync.h"
#include "cvsync_attr.h"
#include "hash.h"
#include "logmsg.h"
#include "mux.h"
#include "network.h"
#include "version.h"

#include "defs.h"

void protocol_compat_0(uint8_t, int, uint8_t, uint8_t *, uint8_t *);
void protocol_compat_0_19(uint8_t *, uint8_t *);
void protocol_compat_0_20(int, uint8_t *, uint8_t *);
void protocol_compat_0_21(int, uint8_t, uint8_t *, uint8_t *);

bool collection_fetch(int, uint8_t *, size_t *, uint8_t *, size_t *, uint8_t *,
		      size_t *);

bool compress_exchange(int, struct config *, uint32_t, int *);

bool
protocol_exchange(int sock, int status, uint8_t error, uint32_t *proto)
{
	uint8_t cmd[CVSYNC_MAXCMDLEN], r_mj, r_mn;
	size_t len;

	if (!sock_recv(sock, cmd, 2))
		return (false);
	if ((len = GetWord(cmd)) != 2)
		return (false);
	if (!sock_recv(sock, cmd, len))
		return (false);

	r_mj = cmd[0];
	r_mn = cmd[1];

	switch (r_mj) {
	case 0:
		protocol_compat_0(r_mn, status, error, &cmd[2], &cmd[3]);
		break;
	default:
		cmd[2] = CVSYNC_PROTO_ERROR;
		cmd[3] = CVSYNC_ERROR_UNSPEC;
		break;
	}

	SetWord(cmd, 2);
	if (!sock_send(sock, cmd, 4))
		return (false);

	if (!sock_recv(sock, cmd, 2))
		return (false);
	if ((len = GetWord(cmd)) != 2)
		return (false);
	if (!sock_recv(sock, cmd, len))
		return (false);

	if ((cmd[0] != r_mj) && (cmd[1] != r_mn))
		return (false);

	*proto = CVSYNC_PROTO(r_mj, r_mn);

	return (true);
}

void
protocol_compat_0(uint8_t r_mn, int status, uint8_t error, uint8_t *mj,
		  uint8_t *mn)
{
	switch (r_mn) {
	case 20:
		protocol_compat_0_20(status, mj, mn);
		break;
	case 21:
		protocol_compat_0_21(status, error, mj, mn);
		break;
	default:
		if (r_mn <= 19) {
			protocol_compat_0_19(mj, mn);
		} else {
			*mj = CVSYNC_PROTO_MAJOR;
			*mn = CVSYNC_PROTO_MINOR;
		}
		break;
	}
}

void
protocol_compat_0_19(uint8_t *mj, uint8_t *mn)
{
	*mj = CVSYNC_PROTO_ERROR;
	*mn = CVSYNC_ERROR_UNSPEC;
}

void
protocol_compat_0_20(int status, uint8_t *mj, uint8_t *mn)
{
	if ((status == ACL_ALLOW) || (status == ACL_ALWAYS)) {
		*mj = CVSYNC_PROTO_MAJOR;
		*mn = 20;
	} else {
		*mj = CVSYNC_PROTO_ERROR;
		*mn = CVSYNC_ERROR_UNSPEC;
	}
}

void
protocol_compat_0_21(int status, uint8_t error, uint8_t *mj, uint8_t *mn)
{
	switch (status) {
	case ACL_ALLOW:
	case ACL_ALWAYS:
		*mj = CVSYNC_PROTO_MAJOR;
		*mn = 21;
		break;
	case ACL_DENY:
		*mj = CVSYNC_PROTO_ERROR;
		*mn = error;
		break;
	default:
		*mj = CVSYNC_PROTO_ERROR;
		*mn = CVSYNC_ERROR_UNSPEC;
		break;
	}
}

int
hash_exchange(int sock, struct config *cf)
{
	uint8_t cmd[CVSYNC_MAXCMDLEN];
	char name[CVSYNC_NAME_MAX + 1];
	size_t len;
	int type;

	if (!sock_recv(sock, cmd, 2))
		return (HASH_UNSPEC);
	len = GetWord(cmd);
	if ((len == 0) || (len >= sizeof(name)))
		return (HASH_UNSPEC);
	if (!sock_recv(sock, name, len))
		return (HASH_UNSPEC);

	if ((type = hash_pton(name, len)) == HASH_UNSPEC)
		type = HASH_MD5;
	if (type != cf->cf_hash)
		type = HASH_MD5;

	if ((len = hash_ntop(type, name, sizeof(name))) == 0)
		return (HASH_UNSPEC);

	SetWord(cmd, len);
	if (!sock_send(sock, cmd, 2))
		return (false);
	if (!sock_send(sock, name, len))
		return (false);

	return (type);
}

struct collection *
collectionlist_exchange(int sock, struct config *cf)
{
	struct collection *cls = NULL, *cl;
	uint16_t mode_umask;
	uint8_t name[CVSYNC_NAME_MAX + 1], relname[CVSYNC_NAME_MAX + 1];
	uint8_t cmd[CVSYNC_MAXCMDLEN], aux[CVSYNC_MAXAUXLEN];
	size_t namelen, relnamelen, auxlen, len;
	int type;
	bool recv_list = false;

	for (;;) {
		namelen = sizeof(name);
		relnamelen = sizeof(relname);
		auxlen = sizeof(aux);
		if (!collection_fetch(sock, name, &namelen, relname,
				      &relnamelen, aux, &auxlen)) {
			collection_destroy_all(cls);
			return (NULL);
		}

		if ((namelen == 1) && (name[0] == '.') &&
		    (relnamelen == 1) && (relname[0] == '.') &&
		    (auxlen == 0)) {
			break;
		}

		if (recv_list) {
			collection_destroy_all(cls);
			return (NULL);
		}

		switch (cvsync_release_pton((char *)relname)) {
		case CVSYNC_RELEASE_LIST:
			if (auxlen != 0) {
				collection_destroy_all(cls);
				return (NULL);
			}
			if (cls != NULL) {
				collection_destroy_all(cls);
				return (NULL);
			}
			type = cvsync_list_pton((char *)name);
			if (type == CVSYNC_LIST_UNKNOWN) {
				SetWord(cmd, 0);
				if (!sock_send(sock, cmd, 2)) {
					collection_destroy_all(cls);
					return (NULL);
				}
				continue;
			}
			if ((cl = malloc(sizeof(*cl))) == NULL) {
				logmsg_err("%s", strerror(errno));
				collection_destroy_all(cls);
				return (NULL);
			}
			(void)memset(cl, 0, sizeof(*cl));
			(void)memcpy(cl->cl_name, name, namelen);
			cl->cl_name[namelen] = '\0';
			(void)memcpy(cl->cl_release, relname, relnamelen);
			cl->cl_release[relnamelen] = '\0';
			auxlen = 0;
			recv_list = true;
			break;
		case CVSYNC_RELEASE_RCS:
			if (auxlen != 2) {
				collection_destroy_all(cls);
				return (NULL);
			}
			mode_umask = GetWord(aux);
			if (mode_umask & ~CVSYNC_ALLPERMS) {
				collection_destroy_all(cls);
				return (NULL);
			}
			cl = collection_lookup(cf->cf_collections,
					       (const char *)name,
					       (const char *)relname);
			if (cl == NULL) {
				SetWord(cmd, 0);
				if (!sock_send(sock, cmd, 2)) {
					collection_destroy_all(cls);
					return (NULL);
				}
				continue;
			}
			mode_umask &= cl->cl_umask;

			SetWord(aux, mode_umask);
			if (cl->cl_rprefixlen > 0) {
				(void)memcpy(&aux[2], cl->cl_rprefix,
					     cl->cl_rprefixlen);
			}
			auxlen = cl->cl_rprefixlen + 2;
			break;
		default:
			collection_destroy_all(cls);
			return (NULL);
		}

		if ((len = namelen + relnamelen + 4) + auxlen >= sizeof(cmd)) {
			collection_destroy(cl);
			collection_destroy_all(cls);
			return (NULL);
		}
		SetWord(cmd, len + auxlen - 2);
		cmd[2] = namelen;
		cmd[3] = relnamelen;
		if (!sock_send(sock, cmd, 4)) {
			collection_destroy(cl);
			collection_destroy_all(cls);
			return (NULL);
		}
		if (!sock_send(sock, name, namelen)) {
			collection_destroy(cl);
			collection_destroy_all(cls);
			return (NULL);
		}
		if (!sock_send(sock, relname, relnamelen)) {
			collection_destroy(cl);
			collection_destroy_all(cls);
			return (NULL);
		}
		if (!sock_send(sock, aux, auxlen)) {
			collection_destroy(cl);
			collection_destroy_all(cls);
			return (NULL);
		}

		if (cls == NULL) {
			cls = cl;
		} else {
			struct collection *prev;

			for (prev = cls ;
			     prev->cl_next != NULL ;
			     prev = prev->cl_next) {
				/* Nothing to do */;
			}
			prev->cl_next = cl;
		}
	}

	SetWord(cmd, 4);
	cmd[2] = 1;
	cmd[3] = 1;
	cmd[4] = '.';
	cmd[5] = '.';
	if (!sock_send(sock, cmd, 6)) {
		collection_destroy_all(cls);
		return (NULL);
	}

	if (cls == NULL)
		return (NULL);

	return (cls);
}

bool
collection_fetch(int sock, uint8_t *name, size_t *namelen, uint8_t *relname,
		 size_t *relnamelen, uint8_t *aux, size_t *auxlen)
{
	uint8_t cmd[CVSYNC_MAXCMDLEN];
	size_t namemax = *namelen, relnamemax = *relnamelen, len;

	if (!sock_recv(sock, cmd, 2))
		return (false);
	len = GetWord(cmd);
	if ((len <= 2) || (len >= sizeof(cmd) - 2))
		return (false);

	if (!sock_recv(sock, cmd, 2))
		return (false);
	if ((cmd[0] == 0) || (cmd[0] >= namemax) ||
	    (cmd[1] == 0) || (cmd[1] >= relnamemax) ||
	    ((size_t)cmd[0] + (size_t)cmd[1] + 2 > len)) {
		return (false);
	}
	if (!sock_recv(sock, name, (size_t)cmd[0]))
		return (false);
	if (!sock_recv(sock, relname, (size_t)cmd[1]))
		return (false);
	name[cmd[0]] = '\0';
	*namelen = cmd[0];
	relname[cmd[1]] = '\0';
	*relnamelen = cmd[1];
	if ((*auxlen = len - cmd[0] - cmd[1] - 2) > 0) {
		if (!sock_recv(sock, aux, *auxlen))
			return (false);
	}

	return (true);
}

struct mux *
channel_establish(int sock, struct config *cf, uint32_t proto)
{
	struct mux *mx;
	struct muxbuf *mxb;
	uint16_t mss;
	uint8_t cmd[CVSYNC_MAXCMDLEN];
	size_t len;
	int compression, i;

	if (!compress_exchange(sock, cf, proto, &compression))
		return (NULL);

	if ((proto > CVSYNC_PROTO(0, 22)) &&
	    (compression != CVSYNC_COMPRESS_NO)) {
		mss = MUX_MAX_MSS_ZLIB;
	} else {
		mss = MUX_DEFAULT_MSS;
	}

	if ((mx = mux_init(sock, mss, compression,
			   cf->cf_compress_level)) == NULL) {
		return (NULL);
	}

	for (i = 0 ; i < MUX_MAXCHANNELS ; i++) {
		mxb = &mx->mx_buffer[MUX_OUT][i];

		if (!sock_recv(sock, cmd, 2)) {
			mux_destroy(mx);
			return (NULL);
		}
		if ((len = GetWord(cmd)) != 7) {
			mux_destroy(mx);
			return (NULL);
		}
		if (!sock_recv(sock, cmd, len)) {
			mux_destroy(mx);
			return (NULL);
		}
		if (cmd[0] != i) {
			mux_destroy(mx);
			return (NULL);
		}

		if (!muxbuf_init(mxb, GetWord(&cmd[1]), GetDWord(&cmd[3]),
				 compression)) {
			mux_destroy(mx);
			return (NULL);
		}

		mxb = &mx->mx_buffer[MUX_IN][i];

		SetWord(cmd, 7);
		cmd[2] = i;
		SetWord(&cmd[3], mxb->mxb_mss);
		SetDWord(&cmd[5], mxb->mxb_bufsize);
		if (!sock_send(sock, cmd, 9)) {
			mux_destroy(mx);
			return (NULL);
		}
	}

	return (mx);
}

bool
compress_exchange(int sock, struct config *cf, uint32_t proto,
		  int *compression)
{
	const char *name;
	uint8_t cmd[CVSYNC_MAXCMDLEN];
	size_t len;

	if (proto < CVSYNC_PROTO(0, 22)) {
		*compression = CVSYNC_COMPRESS_NO;
		return (true);
	}

	if (!sock_recv(sock, cmd, 2))
		return (false);
	len = GetWord(cmd);
	if ((len == 0) || (len >= sizeof(cmd)))
		return (false);
	if (!sock_recv(sock, cmd, len))
		return (false);
	cmd[len] = '\0';

	*compression = cvsync_compress_pton((const char *)cmd);
	if (cf->cf_compress == CVSYNC_COMPRESS_NO)
		*compression = CVSYNC_COMPRESS_NO;
	if (proto == CVSYNC_PROTO(0, 22))
		*compression = CVSYNC_COMPRESS_NO;

	name = cvsync_compress_ntop(*compression);

	len = strlen(name);
	if (len > (size_t)UINT16_MAX) {
		return (false);
	}
	SetWord(cmd, len);
	if (!sock_send(sock, cmd, 2))
		return (false);
	if (!sock_send(sock, name, len))
		return (false);

	if (*compression == CVSYNC_COMPRESS_UNSPEC)
		return (false);

	if (!sock_recv(sock, cmd, 2))
		return (false);
	if (GetWord(cmd) != (uint16_t)len)
		return (false);
	if (!sock_recv(sock, cmd, len))
		return (false);
	cmd[len] = '\0';

	if (*compression != cvsync_compress_pton((const char *)cmd))
		return (false);

	return (true);
}


syntax highlighted by Code2HTML, v. 0.9.1