/* $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