/*
* Copyright (c) 2004-2007 Hypertriton, Inc.
* 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.
*
* 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 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
#ifdef AG_NETWORK
#include "core.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_SYSLOG
#include
#endif
#include
#include
#include
#include
#include
#include "net_command.h"
#include "net_client.h"
#include "net_server.h"
#include "net_sockunion.h"
#include "net_fgetln.h"
#define MAX_SERVER_SOCKS 64
#if defined(HAVE_SYSLOG) || defined(HAVE_VSYSLOG)
const int nsSyslogLevels[] = {
LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING,
LOG_ERR, LOG_CRIT, LOG_ALERT, LOG_EMERG
};
#else
const char *nsLogLevelNames[] = {
"DEBUG", "INFO", "NOTICE", "WARNING",
"ERR", "CRIT", "ALERT", "EMERG"
};
#endif
void
NS_InitSubsystem(Uint flags)
{
AG_RegisterClass(&nsServerClass);
AG_RegisterClass(&nsClientClass);
}
NS_Server *
NS_ServerNew(void *parent, Uint flags, const char *name, const char *proto,
const char *protoVer, const char *port)
{
NS_Server *ns;
ns = Malloc(sizeof(NS_Server));
AG_ObjectInit(ns, &nsServerClass);
AG_ObjectSetName(ns, "%s", name);
ns->flags |= flags;
NS_ServerSetProtocol(ns, proto, protoVer);
NS_ServerBind(ns, NULL, port);
AG_ObjectAttach(parent, ns);
return (ns);
}
static void
InitServer(void *obj)
{
NS_Server *ns = obj;
ns->flags = 0;
ns->host = NULL;
ns->port = NULL;
ns->listenProc = 0;
ns->cmds = NULL;
ns->ncmds = 0;
ns->authModes = 0;
ns->nAuthModes = 0;
ns->listItems = NULL;
ns->errorFn = NULL;
ns->sigCheckFn = NULL;
ns->loginFn = NULL;
ns->logoutFn = NULL;
TAILQ_INIT(&ns->clients);
}
static void
DestroyServer(void *obj)
{
NS_Server *ns = obj;
Free(ns->cmds);
Free(ns->authModes);
Free(ns->listItems);
}
/* Set the protocol version string to use (thread unsafe). */
void
NS_ServerSetProtocol(NS_Server *ns, const char *proto, const char *ver)
{
ns->protoName = proto;
ns->protoVer = ver;
}
/* Set the bind() hostname to use (thread unsafe). */
void
NS_ServerBind(NS_Server *ns, const char *hostName, const char *portName)
{
ns->host = hostName;
ns->port = portName;
}
void
NS_Log(enum ns_log_lvl loglvl, const char *fmt, ...)
{
#if !defined(HAVE_VSYSLOG) && defined(HAVE_SYSLOG)
char msg[256];
#endif
va_list ap;
va_start(ap, fmt);
#ifdef HAVE_VSYSLOG
vsyslog(nsSyslogLevels[loglvl], fmt, ap)
#else
# ifdef HAVE_SYSLOG
Vsnprintf(msg, sizeof(msg), fmt, ap);
syslog(nsSyslogLevels[loglvl], "%s", msg);
# else
fprintf(stderr, "[%s] ", nsLogLevelNames[loglvl]);
vfprintf(stderr, fmt, ap);
fputc('\n', stderr);
# endif
#endif
va_end(ap);
}
void
NS_BeginList(NS_Server *ns)
{
#ifdef AG_DEBUG
if (ns->listItems != NULL)
AG_FatalError("Nested NS_BeginList() calls");
#endif
ns->listItems = NULL;
ns->listItemSize = NULL;
ns->listItemCount = 0;
}
void
NS_EndList(NS_Server *ns)
{
char ack[2];
int i;
printf("0 %010u:", ns->listItemCount);
for (i = 0; i < ns->listItemCount; i++) {
printf("%012lu:", (unsigned long)ns->listItemSize[i]);
}
fputc('\n', stdout);
fgets(ack, 2, stdin);
fflush(stdout);
setvbuf(stdout, NULL, _IONBF, 0);
for (i = 0; i < ns->listItemCount; i++) {
void *itembuf = ns->listItems[i];
size_t itemsz = ns->listItemSize[i];
if (fwrite(itembuf, 1, itemsz, stdout) < itemsz) {
NS_Log(NS_ERR, "EndList: Write error");
exit(1);
}
}
fflush(stdout);
setvbuf(stdout, NULL, _IOLBF, 0);
fgets(ack, 2, stdin);
if (ns->listItemCount > 0) {
Free(ns->listItems);
Free(ns->listItemSize);
ns->listItemSize = NULL;
ns->listItemCount = 0;
}
ns->listItems = NULL;
}
void
NS_ListItem(NS_Server *ns, void *buf, size_t len)
{
if (ns->listItemCount == 0) {
ns->listItems = Malloc(sizeof(void *));
ns->listItemSize = Malloc(sizeof(size_t));
} else {
ns->listItems = Realloc(ns->listItems,
(ns->listItemCount+1)*sizeof(void *));
ns->listItemSize = Realloc(ns->listItemSize,
(ns->listItemCount+1)*sizeof(size_t));
}
ns->listItems[ns->listItemCount] = buf;
ns->listItemSize[ns->listItemCount] = len;
ns->listItemCount++;
}
void
NS_ListString(NS_Server *ns, const char *fmt, ...)
{
char *buf;
va_list ap;
va_start(ap, fmt);
Vasprintf(&buf, fmt, ap);
va_end(ap);
NS_ListItem(ns, buf, strlen(buf)+1);
}
void
NS_RegLoginFn(NS_Server *ns, NS_LoginFn fn)
{
ns->loginFn = fn;
}
void
NS_RegLogoutFn(NS_Server *ns, NS_LogoutFn fn)
{
ns->logoutFn = fn;
}
void
NS_RegErrorFn(NS_Server *ns, NS_ErrorFn fn)
{
ns->errorFn = fn;
}
/* Register a server command. */
void
NS_RegCmd(NS_Server *ns, const char *name, NS_CommandFn fn, void *arg)
{
ns->cmds = Realloc(ns->cmds, (ns->ncmds+1)*sizeof(NS_Cmd));
ns->cmds[ns->ncmds].name = name;
ns->cmds[ns->ncmds].fn = fn;
ns->cmds[ns->ncmds].arg = arg;
ns->ncmds++;
Debug(ns, "Registered function: %s (%p)", name, arg);
}
/* Register an authentication method. */
void
NS_RegAuthMode(NS_Server *ns, const char *name, NS_AuthFn fn, void *arg)
{
ns->authModes = Realloc(ns->authModes, (ns->nAuthModes+1) *
sizeof(NS_Auth));
ns->authModes[ns->nAuthModes].name = name;
ns->authModes[ns->nAuthModes].fn = fn;
ns->authModes[ns->nAuthModes].arg = arg;
ns->nAuthModes++;
Debug(ns, "Registered authmode: %s (%p)", name, arg);
}
static void
ProcessCommand(NS_Server *ns, NS_Command *ncmd)
{
int i;
for (i = 0; i < ns->ncmds; i++) {
if (strcmp(ns->cmds[i].name, ncmd->name) != 0) {
continue;
}
if (ns->cmds[i].fn(ns, ncmd, ns->cmds[i].arg) == -1) {
if (ns->errorFn == NULL ||
ns->errorFn(ns) == -1) {
NS_Message(ns, 1, "%s", AG_GetError());
}
}
fflush(stdout);
return;
}
NS_Logout(ns, 1, "unimplemented command");
}
/*
* Negotiate the version, authenticate and loop processing requests from
* the client.
*/
static void
ServerLoop(NS_Server *ns)
{
char tmp[AG_BUFFER_MAX];
char prot[32];
char *buf, *lbuf = NULL, *value;
NS_Command ncmd;
size_t len;
int seq = 0;
int i;
Snprintf(prot, sizeof(prot), "%s %s\n", ns->protoName, ns->protoVer);
fputs(prot, stdout);
fflush(stdout);
if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
goto read_failure;
}
if (strcmp(tmp, prot) != 0) {
NS_Log(NS_ERR, "incompatible version: `%s'!=`%s'", tmp, prot);
fputs("Incompatible client version\n", stdout);
exit(1);
}
Strlcpy(tmp, "auth:", sizeof(tmp));
for (i = 0; i < ns->nAuthModes; i++) {
Strlcat(tmp, ns->authModes[i].name, sizeof(tmp));
if (i < ns->nAuthModes)
Strlcat(tmp, ",", sizeof(tmp));
}
Strlcat(tmp, "\n", sizeof(tmp));
fputs(tmp, stdout);
if (fgets(tmp, sizeof(tmp), stdin) == NULL) {
goto read_failure;
}
for (i = 0; i < ns->nAuthModes; i++) {
if (strncmp(tmp, ns->authModes[i].name,
strlen(ns->authModes[i].name)-1) == 0)
break;
}
if (i == ns->nAuthModes) {
NS_Log(NS_NOTICE, "Bad auth mode: `%s'", tmp);
fputs("! Bad auth mode\n", stdout);
exit(1);
}
fputs("ok-send-auth\n", stdout);
fflush(stdout);
if (ns->authModes[i].fn(ns, ns->authModes[i].arg) == 0) {
NS_Log(NS_ERR, "Auth failed: %s", AG_GetError());
NS_Message(ns, 1, "Auth failed: %s", AG_GetError());
exit(1);
}
if (ns->loginFn != NULL) {
if (ns->loginFn(ns, NULL) == -1) {
NS_Log(NS_ERR, "Login failed: %s", AG_GetError());
NS_Message(ns, 1, "Login failed: %s", AG_GetError());
exit(1);
}
}
fputs("ok\n", stdout);
fflush(stdout);
while ((buf = NS_Fgetln(stdin, &len)) != NULL) {
if (buf[len-1] == '\n') {
buf[len-1] = '\0';
} else {
if ((lbuf = malloc(len+1)) == NULL) {
NS_Logout(ns, 1, "malloc line");
}
memcpy(lbuf, buf, len);
lbuf[len] = '\0';
buf = lbuf;
}
if (seq++ == 0) {
/* Initiate a new request. */
NS_InitCommand(&ncmd);
if (Strlcpy(ncmd.name, buf, sizeof(ncmd.name)) >=
sizeof(ncmd.name)) {
NS_DestroyCommand(&ncmd);
Free(lbuf);
NS_Logout(ns, 1, "command name too big");
}
} else if ((value = strchr(buf, '=')) != NULL) {
/*
* Append an argument to the current request.
*/
NS_CommandArg *narg;
ncmd.args = Realloc(ncmd.args,
++ncmd.nargs * sizeof(NS_CommandArg));
narg = &ncmd.args[ncmd.nargs-1];
narg->value = Strdup(value+1);
narg->size = sizeof(value);
*value = '\0';
if (Strlcpy(narg->key, buf, sizeof(narg->key)) >=
sizeof(narg->key)) {
NS_DestroyCommand(&ncmd);
Free(lbuf);
NS_Logout(ns, 1, "command key is too big");
}
} else {
ProcessCommand(ns, &ncmd);
NS_DestroyCommand(&ncmd);
seq = 0;
}
if (lbuf != NULL) {
Free(lbuf);
lbuf = NULL;
}
}
read_failure:
if (ferror(stdin)) {
NS_Log(NS_ERR, "Client read: %s", strerror(errno));
exit(1);
} else if (feof(stdin)) {
NS_Log(NS_ERR, "EOF from client");
exit(1);
}
exit(0);
}
void
NS_Logout(NS_Server *ns, int rv, const char *fmt, ...)
{
char buf[AG_BUFFER_MAX];
va_list ap;
va_start(ap, fmt);
Vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
NS_Message(ns, 1, "%s", buf);
exit(rv);
}
void
NS_Message(NS_Server *ns, int rv, const char *fmt, ...)
{
char *s;
va_list ap;
va_start(ap, fmt);
Vasprintf(&s, fmt, ap);
va_end(ap);
printf("%d %s\n", rv, s);
fflush(stdout);
Free(s);
}
/* Initiate a binary transfer. */
void
NS_BeginData(NS_Server *ns, size_t nbytes)
{
if (setvbuf(stdout, NULL, _IONBF, 0) == EOF) {
NS_Log(NS_ERR, "_IONBF stdout: %s", strerror(errno));
exit(1);
}
NS_Message(ns, 0, "%012lu", (unsigned long)nbytes);
fflush(stdout);
}
/* Send a chunk of binary data. */
size_t
NS_Data(NS_Server *ns, char *buf, size_t len)
{
return (fwrite(buf, 1, len, stdout));
}
/* Terminate binary transfer. */
void
NS_EndData(NS_Server *ns)
{
fflush(stdout);
if (setvbuf(stdout, NULL, _IOLBF, 0) == EOF) {
NS_Log(NS_ERR, "_IOLBF stdout: %s", strerror(errno));
exit(1);
}
}
static void
sig_chld(int sigraised)
{
int save_errno = errno;
int rv;
do {
rv = waitpid(-1, NULL, WNOHANG);
} while (rv > 0 || (rv == -1 && errno == EINTR));
errno = save_errno;
}
static void
sig_urg(int sigraised)
{
fprintf(stderr, "urgent condition on socket\n");
}
static void
sig_quit(int sigraised)
{
#ifdef HAVE_SYSLOG
struct syslog_data sdata = SYSLOG_DATA_INIT;
syslog_r(LOG_ERR, &sdata, "client got signal %s",
sys_signame[sigraised]);
#endif
_exit(0);
}
static void
sig_pipe(int sigraised)
{
#ifdef HAVE_SYSLOG
struct syslog_data sdata = SYSLOG_DATA_INIT;
syslog_r(LOG_DEBUG, &sdata, "client lost connection");
#endif
_exit(0);
}
static void
sig_die(int sigraised)
{
#ifdef HAVE_SYSLOG
struct syslog_data sdata = SYSLOG_DATA_INIT;
syslog_r(LOG_DEBUG, &sdata, "server got signal %s",
sys_signame[sigraised]);
#endif
kill(0, SIGUSR1);
_exit(0);
}
static int
cmd_quit(NS_Server *ns, NS_Command *cmd, void *arg)
{
NS_Logout(ns, 0, "logout");
return (0);
}
/* Main loop of the listening process. */
int
NS_Listen(NS_Server *ns)
{
struct addrinfo hints, *res, *res0;
const char *cause = NULL;
int rv, nservsocks, maxfd = 0;
int i, servsocks[MAX_SERVER_SOCKS];
fd_set servfds;
struct sigaction sa;
NS_RegCmd(ns, "quit", cmd_quit, NULL);
ns->listenProc = getpid();
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(ns->host, ns->port, &hints, &res0)) != 0) {
AG_SetError("%s", gai_strerror(rv));
return (-1);
}
FD_ZERO(&servfds);
for (nservsocks = 0, res = res0;
res != NULL && nservsocks < MAX_SERVER_SOCKS;
res = res->ai_next) {
rv = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (rv == -1) {
cause = "socket";
continue;
}
i = 1;
if (setsockopt(rv, SOL_SOCKET, SO_REUSEADDR, &i,
(socklen_t)sizeof(i)) == -1) {
NS_Log(NS_ERR, "SO_REUSEADDR: %s (ignored)",
strerror(errno));
}
if (bind(rv, res->ai_addr, res->ai_addrlen) == -1) {
cause = "bind";
close(rv);
continue;
}
if (listen(rv, 5) == -1) {
cause = "listen";
close(rv);
continue;
}
servsocks[nservsocks++] = rv;
FD_SET(rv, &servfds);
if (rv> maxfd)
maxfd = rv;
}
if (nservsocks == 0) {
AG_SetError("%s: %s", cause, strerror(errno));
return (-1);
}
freeaddrinfo(res0);
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sa.sa_handler = sig_chld;
sigaction(SIGCHLD, &sa, NULL);
sa.sa_flags = 0;
sa.sa_handler = sig_die;
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sa.sa_handler = SIG_IGN;
sigaction(SIGUSR1, &sa, NULL);
for (;;) {
fd_set rfds = servfds;
rv = select(maxfd+1, &rfds, NULL, NULL, NULL);
if (rv == -1) {
if (errno == EINTR) {
if (ns->sigCheckFn != NULL) {
ns->sigCheckFn(ns);
}
continue;
}
AG_SetError("select: %s", strerror(errno));
return (-1);
}
for (i = 0; i < nservsocks; i++) {
union sockunion paddr;
socklen_t paddrlen = sizeof(paddr);
pid_t pid;
if (!FD_ISSET(servsocks[i], &rfds))
continue;
rv = accept(servsocks[i], (struct sockaddr *)&paddr,
&paddrlen);
if (rv == -1) {
fprintf(stderr, "accept: %s\n",
strerror(errno));
continue;
}
if ((pid = fork()) == 0) { /* Child */
dup2(rv, 0);
dup2(rv, 1);
for (i = 0; i < nservsocks; i++) {
close(servsocks[i]);
}
sa.sa_handler = SIG_DFL;
sigaction(SIGCHLD, &sa, NULL);
sa.sa_handler = sig_urg; /* OOB */
sa.sa_flags = 0;
sigaction(SIGURG, &sa, NULL);
sa.sa_handler = sig_pipe; /* Lost conn. */
sigaction(SIGPIPE, &sa, NULL);
sigfillset(&sa.sa_mask); /* Fatal */
sa.sa_flags = SA_RESTART;
sa.sa_handler = sig_quit;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
i = 1;
if (setsockopt(0, SOL_SOCKET, SO_OOBINLINE, &i,
sizeof(i)) == -1) {
NS_Log(NS_ERR,
"SO_OOBINLINE: %s (ignored)",
strerror(errno));
}
if (fcntl(fileno(stdin), F_SETOWN, getpid())
== -1) {
NS_Log(NS_ERR,
"fcntl F_SETOWN: %s (ignored)",
strerror(errno));
}
if (setvbuf(stdout, NULL, _IOLBF, 0) == EOF) {
NS_Log(NS_ERR, "_IOLBF stdout: %s",
strerror(errno));
exit(1);
}
ServerLoop(ns);
}
close(rv);
}
}
return (0);
}
static void
InitClient(void *obj)
{
NS_Client *cl = obj;
cl->host[0] = '\0';
}
AG_ObjectClass nsServerClass = {
"NS_Server",
sizeof(NS_Server),
{ 0,0 },
InitServer,
NULL, /* free */
DestroyServer,
NULL, /* load */
NULL, /* save */
NULL /* edit */
};
AG_ObjectClass nsClientClass = {
"NS_Client",
sizeof(NS_Client),
{ 0,0 },
InitClient,
NULL, /* free */
NULL, /* destroy */
NULL, /* load */
NULL, /* save */
NULL /* edit */
};
#endif /* AG_NETWORK */