/*	$Id: kissd.c,v 1.1.1.1 2004/01/20 17:38:06 dhartmei Exp $ */

/*
 * Copyright (c) 2004 Daniel Hartmeier
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *    - 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 COPYRIGHT HOLDERS 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
 * COPYRIGHT HOLDERS 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.
 *
 */

static const char rcsid[] = "$Id: kissd.c,v 1.1.1.1 2004/01/20 17:38:06 dhartmei Exp $";

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <fts.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

static void	 usage(void);
static void	 sigchld(int);
static int	 fts_compare(const FTSENT **, const FTSENT **);
static int	 cmd_list(const char *, const char *, int);
static int	 cmd_action(const char *, int);
static int	 cmd_size(const char *, int);
static int	 cmd_get(const char *, long, long, int, int);
static int	 parse_args(const char *, char [16][1024]);
static int	 handle_cmd(int, char *);
static void	 handle_client(int);
static int	 sync_write(int, const char *, int);

struct {
	char	 name[MAXPATHLEN];
	int	 desc;
	char	*addr;
	off_t	 size;
}			 cur = { "", -1, MAP_FAILED, 0 };
static const char	*path_base;
static int		 debug = 0;
static int		 reverse = 0;

static void
usage(void)
{
	extern char *__progname;

	fprintf(stderr, "usage: %s [-d] [-l address] path\n", __progname);
	exit(1);
}

static void
sigchld(int sigraised)
{
	int status;

	wait(&status);
}

int
main(int argc, char *argv[])
{
	const char *addr_listen = NULL;
	unsigned port_listen = 8000;
	int ch;
	int listen_fd = -1;
	struct sockaddr_in sa;
	socklen_t len;
	int val;

	while ((ch = getopt(argc, argv, "dl:p:s:P:")) != -1) {
		switch (ch) {
		case 'd':
			debug++;
			break;
		case 'l':
			addr_listen = optarg;
			break;
		case 'p':
			port_listen = atoi(optarg);
			break;
		default:
			usage();
		}
	}
	argc -= optind;
	if (argc != 1)
		usage();
	path_base = argv[optind];

	if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		goto error;
	}

	if (fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) |
	    O_NONBLOCK)) {
		perror("fcntl");
		goto error;
	}

        val = 1;
        if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR,
	    (const char *)&val, sizeof(val))) {
		perror("setsockopt");
		goto error;
        }

	memset(&sa, 0, sizeof(sa));
	sa.sin_family = AF_INET;
	if (addr_listen != NULL)
		sa.sin_addr.s_addr = inet_addr(addr_listen);
	else
		sa.sin_addr.s_addr = INADDR_ANY;
	sa.sin_port = htons(port_listen);
        if (bind(listen_fd, (const struct sockaddr *)&sa, sizeof(sa))) {
		fprintf(stderr, "bind %s:%u: %s\n", inet_ntoa(sa.sin_addr),
		    ntohs(sa.sin_port), strerror(errno));
		goto error;
        }

        if (listen(listen_fd, 1)) {
		perror("listen");
		goto error;
        }

	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR ||
	    signal(SIGCHLD, sigchld) == SIG_ERR) {
		perror("signal");
		return (1);
	}

	if (!debug && daemon(0, 0)) {
		perror("daemon");
		goto error;
	}
	if (debug)
		printf("kissd ready, serving %s\n", path_base);

	while (1) {
		fd_set readfds;
		struct timeval tv;
		int r;

		FD_ZERO(&readfds);
		FD_SET(listen_fd, &readfds);
		memset(&tv, 0, sizeof(tv));
		tv.tv_sec = 10;
		r = select(listen_fd + 1, &readfds, NULL, NULL, &tv);
		if (r < 0) {
			if (errno != EINTR) {
				perror("select");
				break;
			}
			continue;
		}
		if (r > 0 && FD_ISSET(listen_fd, &readfds)) {
			int client_fd;
			pid_t pid;

			memset(&sa, 0, sizeof(sa));
			len = sizeof(sa);
			client_fd = accept(listen_fd,
			    (struct sockaddr *)&sa, &len);
			if (client_fd < 0) {
				if (errno != ECONNABORTED) {
					perror("accept");
					break;
				}
				continue;
			}
			if (debug)
				printf("connection from %s:%i\n",
				    inet_ntoa(sa.sin_addr), ntohs(sa.sin_port));
			pid = fork();
			if (pid < 0) {
				perror("fork");
				close(client_fd);
				continue;
			} else if (pid == 0) {
				handle_client(client_fd);
				exit(0);
			} else
				close(client_fd);
		}
	}

	close(listen_fd);
	return (0);

error:
	if (listen_fd)
		close(listen_fd);

	return (1);
}

static int
fts_compare(const FTSENT **a, const FTSENT **b)
{
	/* directories first */
	if (((*a)->fts_info == FTS_F) != ((*b)->fts_info == FTS_F)) {
		if ((*a)->fts_info == FTS_F)
			return (1);
		else
			return (-1);
	}
	/* directories and files alphabetically (ignoring case) */
	return (strcasecmp((*a)->fts_name, (*b)->fts_name));
}

static int
cmd_list(const char *type, const char *path, int client_fd)
{
	char path_file[MAXPATHLEN];
	char *argv[] = { path_file, NULL };
	FTS *fts;
	FTSENT *e;
	int i;

	snprintf(path_file, sizeof(path_file), "%s%s%s", path_base,
	    path[0] ? "/" : "", path);
	fts = fts_open(argv, FTS_LOGICAL|FTS_NOSTAT, fts_compare);
	if (fts == NULL) {
		fprintf(stderr, "fts_open: %s: %s\n", argv[0],
		    strerror(errno));
		return (1);
	}
	while ((e = fts_read(fts)) != NULL) {
		char t[64], f[MAXPATHLEN], r[MAXPATHLEN];
		const char *p;

		if (e->fts_info == FTS_D) {
			if (e->fts_level > 0 &&
			    fts_children(fts, FTS_NAMEONLY) != NULL) {
				fts_set(fts, e, FTS_SKIP);
			}
			continue;
		}
		if (e->fts_level != 1 || e->fts_name[0] == '.')
			continue;
		p = e->fts_path;
		if (e->fts_info == FTS_F)
			p += strlen(path_base);
		else
			p += strlen(path_file);
		while (*p == '/')
			p++;
		strlcpy(f, p, sizeof(f));
		for (i = strlen(f); i > 0 && f[i - 1] == '/'; --i)
			f[i - 1] = 0;
		for (i = 0; i < sizeof(t) - 1 && e->fts_name[i]; ++i)
			if (e->fts_name[i] == '_')
				t[i] = ' ';
			else
				t[i] = e->fts_name[i];
		t[i] = 0;
		snprintf(r, sizeof(r), "%s| %s|%c|\r\n",
		    t, f, e->fts_info == FTS_F ? '0' : '1');
		sync_write(client_fd, r, strlen(r));
		if (debug)
			printf("<< %s", r);
	}
	fts_close(fts);
	return (1);
}

static int
cmd_action(const char *s, int client_fd)
{
	if (s[0] == '1' && s[1] == ' ') {
		sync_write(client_fd, "200", 3);
		if (debug)
			printf("<< 200\n");
		return (0);
	} else if (s[0] == '2' && s[1] == ' ') {
		char file[MAXPATHLEN];
		int i;

		s += 2;
		for (i = 0; s[i] && s[i] != '|' && i < sizeof(file) - 1; ++i)
			file[i] = s[i];
		file[i] = 0;
		if (i > 4 && !strcasecmp(file + i - 4, ".mp3")) {
			reverse = 1;
			sync_write(client_fd, "200", 3);
			if (debug)
				printf("<< 200\n");
		} else {
			sync_write(client_fd, "404", 3);
			if (debug)
				printf("<< 404\n");
		}
		return (0);
	}
	fprintf(stderr, "unknown ACTION '%s'\n", s);
	return (1);
}

static int
cmd_size(const char *path, int client_fd)
{
	char path_full[MAXPATHLEN];
	struct stat sb;
	char r[16];

	snprintf(path_full, sizeof(path_full), "%s/%s", path_base, path);
	if (stat(path_full, &sb)) {
		fprintf(stderr, "stat: %s: %s\n", path_full, strerror(errno));
		return (1);
	}
	snprintf(r, sizeof(r), "%15.15lu", (unsigned long)sb.st_size);
	sync_write(client_fd, r, strlen(r));
	if (debug)
		printf("<< %s\n", r);
	return (0);
}

static int
cmd_get(const char *path, long off, long len, int last, int client_fd)
{
	char path_full[MAXPATHLEN];

	snprintf(path_full, sizeof(path_full), "%s/%s", path_base, path);

	if (strcmp(path_full, cur.name)) {
		struct stat sb;

		if (cur.addr != MAP_FAILED)
			if (munmap(cur.addr, cur.size))
				perror("munmap");
		if (cur.desc >= 0)
			close(cur.desc);
		strlcpy(cur.name, path_full, sizeof(cur.name));
		cur.desc = open(cur.name, O_RDONLY, 0);
		if (cur.desc < 0) {
			fprintf(stderr, "open: %s: %s\n", cur.name,
			    strerror(errno));
			cur.name[0] = 0;
			return (1);
		}
		if (fstat(cur.desc, &sb)) {
			fprintf(stderr, "fstat: %s: %s\n", cur.name,
			    strerror(errno));
			close(cur.desc);
			cur.desc = -1;
			cur.name[0] = 0;
			return (1);
		}
		cur.size = sb.st_size;
		cur.addr = mmap(NULL, cur.size, PROT_READ, MAP_FILE|MAP_SHARED,
		    cur.desc, 0);
		if (cur.addr == MAP_FAILED) {
			perror("mmap");
			close(cur.desc);
			cur.desc = -1;
			cur.name[0] = 0;
			return (1);
		}
		if (madvise(cur.addr, cur.size, MADV_SEQUENTIAL))
			perror("madvise");
	}

	if (len < 0) {
		fprintf(stderr, "len %ld < 0, aborting\n", len);
		return (1);
	}
	if (off < 0) {
		fprintf(stderr, "off %ld < 0, aborting\n", off);
		return (1);
	}
	if (off > cur.size) {
		fprintf(stderr, "off %ld > size %ld, truncating\n", off,
		    (long)cur.size);
		off = cur.size;
	}
	if (!len)
		len = cur.size - off;
	if (len > cur.size) {
		fprintf(stderr, "len %ld > size %ld, truncating\n",
		    len, (long)cur.size);
		len = cur.size;
	}

	if (reverse) {
		sync_write(client_fd, cur.addr + cur.size - len, len);
		if (debug)
			printf("<< (%ld bytes data)\n", len);
	} else if (off + len > cur.size) {
		char b[65536];

		sync_write(client_fd, cur.addr + off, cur.size - off);
		if (debug)
			printf("<< (%ld bytes data)\n", len);
		len -= cur.size - off;
		memset(b, 0, sizeof(b));
		while (len > 0) {
			sync_write(client_fd, b,
			    len > sizeof(b) ? sizeof(b) : len);
			if (debug)
				printf("<< (%ld bytes padding)\n",
				    len > sizeof(b) ? sizeof(b) : len);
			len -= sizeof(b);
		}
	} else {
		sync_write(client_fd, cur.addr + off, len);
		if (debug)
			printf("<< (%ld bytes data)\n", len);
	}

	if (last) {
		if (munmap(cur.addr, cur.size))
			perror("munmap");
		cur.addr = MAP_FAILED;
		if (close(cur.desc))
			perror("close");
		cur.desc = -1;
		cur.size = 0;
		cur.name[0] = 0;
		return (1);
	}
	return (0);
}

static int
parse_args(const char *s, char args[16][1024])
{
	int i = 0, j = 0;

	while (*s && i < 16) {
		if (*s == '|' || j == 1023) {
			args[i][j] = 0;
			i++;
			j = 0;
		} else {
			args[i][j] = *s;
			j++;
		}
		s++;
	}
	return (i + 1);
}

static int
handle_cmd(int client_fd, char *s)
{
	int n;
	char args[16][1024];

	if (debug) {
		for (n = strlen(s); n > 0 && (s[n - 1] == '\r' ||
		    s[n - 1] == '\n'); --n)
			s[n - 1] = 0;
		printf(">> %s\n", s);
	}
	if (!strncmp(s, "LIST ", 5)) {
		s += 5;
		if (parse_args(s, args) < 2) {
			fprintf(stderr, "parse error LIST '%s'\n", s);
			return (1);
		}
		return (cmd_list(args[0], args[1], client_fd));
	} else if (!strncmp(s, "ACTION ", 7)) {
		s += 7;
		return cmd_action(s, client_fd);
	} else if (!strncmp(s, "SIZE ", 5)) {
		s += 5;
		if (parse_args(s, args) < 2) {
			fprintf(stderr, "parse error SIZE '%s'\n", s);
			return (1);
		}
		return (cmd_size(args[0], client_fd));
	} else if (!strncmp(s, "GET ", 4)) {
		long off, len;

		s += 4;
		n = parse_args(s, args);
		if (n < 2) {
			fprintf(stderr, "parse error GET '%s'\n", s);
			return (1);
		}
		if (sscanf(args[1], "%ld %ld", &off, &len) != 2) {
			printf("sscanf error GET '%s'\n", args[1]);
			return (1);
		}

		return (cmd_get(args[0], off, len, n == 4 && !args[2],
		    client_fd));
	}
	printf("unknown command '%s'\n", s);
	return (1);
}

static void
handle_client(int client_fd)
{
	fd_set readfds;
	struct timeval tv;
	char buf[65536];
	int r;

        r = 128 * 1024;
        if (setsockopt(client_fd, SOL_SOCKET, SO_SNDBUF,
	    (const char *)&r, sizeof(r))) {
		perror("setsockopt");
		return;
        }
	if (fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK)) {
		perror("fcntl");
		return;
	}

	while (1) {
		FD_ZERO(&readfds);
		FD_SET(client_fd, &readfds);
                tv.tv_sec = 1;
		tv.tv_usec = 0;
                r = select(client_fd + 1, &readfds, NULL, NULL, &tv);
                if (r < 0) {
			if (errno != EINTR) {
				perror("select");
				break;
			}
			continue;
		}
		if (r > 0) {
			r = read(client_fd, buf, sizeof(buf) - 1);
			if (r < 0) {
				if (errno == EINTR)
					continue;
				perror("read");
				return;
			}
			if (r == 0) {
				if (debug)
					printf("connection closed by peer\n");
				return;
			}
			buf[r] = 0;
			if (handle_cmd(client_fd, buf)) {
				close(client_fd);
				return;
			}
		}
	}
}

static int
sync_write(int fd, const char *buf, int len)
{
	int off = 0;
	fd_set writefds;
	struct timeval tv;
	int r;

	while (len > off) {
		FD_ZERO(&writefds);
		FD_SET(fd, &writefds);
		tv.tv_sec = 1;
		tv.tv_usec = 0;
		r = select(fd + 1, NULL, &writefds, NULL, &tv);
		if (r < 0) {
			if (errno != EINTR) {
				perror("select");
				return (1);
			}
			continue;
		}
		if (r > 0) {
			r = write(fd, buf + off, len - off);
			if (r < 0) {
				perror("write");
				return (1);
			}
			off += r;
		}
	}
	return (0);
}


syntax highlighted by Code2HTML, v. 0.9.1