/*-
 * SPDX-License-Identifier: LicenseRef-scancode-bsd-unchanged
 *
 * Copyright (c) 2013-2014 Baptiste Daroussin <bapt@FreeBSD.org>
 * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.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
 *    in this position and unchanged.
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``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(S) 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.
 */

#ifdef HAVE_CONFIG_H
#include "pkg_config.h"
#endif
#if __has_include(<sys/capsicum.h>)
#define HAVE_CAPSICUM 1
#include <sys/capsicum.h>
#endif
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>

#include <ctype.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include <bsd_compat.h>

#include "pkg.h"
#include "private/event.h"
#include "private/utils.h"

int
pkg_sshserve(int fd)
{
	struct stat st;
	char *line = NULL;
	char *file, *age;
	size_t linecap = 0, r;
	ssize_t linelen;
	time_t mtime = 0;
	const char *errstr;
	int ffd;
	char buf[32768];
	char fpath[MAXPATHLEN];
	char rpath[MAXPATHLEN];
	const char *restricted = NULL;

	restricted = pkg_object_string(pkg_config_get("SSH_RESTRICT_DIR"));

	printf("ok: pkg "PKGVERSION"\n");
	for (;;) {
		if ((linelen = getline(&line, &linecap, stdin)) < 0)
			break;

		if (linelen == 0)
			continue;

		/* trim cr */
		if (line[linelen - 1] == '\n')
			line[linelen - 1] = '\0';

		if (STREQ(line, "quit")) {
			free(line);
			return (EPKG_OK);
		}

		if (strncmp(line, "get ", 4) != 0) {
			printf("ko: unknown command '%s'\n", line);
			continue;
		}

		file = line + 4;

		if (*file == '\0') {
			printf("ko: bad command get, expecting 'get file age'\n");
			continue;
		}
		if (*file == '/')
			file++;

		pkg_debug(1, "SSH server> file requested: %s", file);

		age = file;
		while (!isspace(*age)) {
			if (*age == '\0') {
				age = NULL;
				break;
			}
			age++;
		}

		if (age == NULL) {
			printf("ko: bad command get, expecting 'get file age'\n");
			continue;
		}

		*age = '\0';
		age++;

		while (isspace(*age)) {
			if (*age == '\0') {
				age = NULL;
				break;
			}
			age++;
		}

		if (age == NULL) {
			printf("ko: bad command get, expecting 'get file age'\n");
			continue;
		}

		mtime = strtonum(age, 0, LONG_MAX, &errstr);
		if (errstr) {
			printf("ko: bad number %s: %s\n", age, errstr);
			continue;
		}

#ifdef HAVE_CAPSICUM
		if (!cap_sandboxed() && restricted != NULL) {
#else
		if (restricted != NULL) {
#endif
			if (chdir(restricted)) {
				printf("ko: chdir failed (%s)\n", restricted);
				continue;
			}

			if (realpath(file, fpath) == NULL ||
			    realpath(restricted, rpath) == NULL ||
			    strncmp(rpath, get_dirname(fpath), strlen(rpath)) != 0) {
				printf("ko: file not found\n");
				continue;
			}
		}

		if (fstatat(fd, file, &st, 0) == -1) {
			pkg_debug(1, "SSH server> fstatat failed");
			printf("ko: file not found\n");
			continue;
		}

		if (!S_ISREG(st.st_mode)) {
			printf("ko: not a file\n");
			continue;
		}

		if (st.st_mtime <= mtime) {
			printf("ok: 0\n");
			continue;
		}

		if ((ffd = openat(fd, file, O_RDONLY)) == -1) {
			printf("ko: file not found\n");
			continue;
		}

		printf("ok: %" PRIdMAX "\n", (intmax_t)st.st_size);
		pkg_debug(1, "SSH server> sending ok: %" PRIdMAX "", (intmax_t)st.st_size);

		while ((r = read(ffd, buf, sizeof(buf))) > 0) {
			pkg_debug(1, "SSH server> sending data");
			fwrite(buf, 1, r, stdout);
		}

		pkg_debug(1, "SSH server> finished");

		close(ffd);
	}

	free(line);

	return (EPKG_OK);
}
