inetd

/* $Id$ */
/* Copyright (c) 2011 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Servers inetd */
/* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "inetd.h"
#include "service.h"
/* service_new */
int _service_port(char * name, char * proto);
Service * service_new(char * name, ServiceSocket socket, ServiceProtocol proto,
ServiceWait wait, ServiceId id, char ** program)
{
int port;
Service * s;
if((port = _service_port(name, proto == SP_TCP ? "tcp" : "udp")) == -1)
return NULL; /* FIXME */
if((s = malloc(sizeof(Service))) == NULL)
{
inetd_error("malloc", 0);
return NULL;
}
s->name = strdup(name);
s->socket = socket;
s->proto = proto;
s->wait = wait;
s->id = id;
s->program = program;
s->fd = -1;
s->port = port;
s->pid = -1;
return s;
}
int _service_port(char * name, char * proto)
{
struct servent * se;
char * p;
int port;
if((se = getservbyname(name, proto)) != NULL)
return se->s_port;
port = strtol(name, &p, 10);
if(*name == '\0' || *p != '\0')
return -1;
return htons(port);
}
/* service_delete */
void service_delete(Service * s)
{
char ** p;
free(s->name);
if(s->program != NULL)
{
for(p = s->program; *p != NULL; p++)
free(*p);
free(s->program);
}
free(s);
}
/* useful */
/* service_listen */
int service_listen(Service * s)
{
struct sockaddr_in sa;
if((s->fd = socket(AF_INET, s->socket == SS_STREAM ? SOCK_STREAM
: SOCK_DGRAM, 0)) == -1)
return inetd_error("socket", 1);
sa.sin_family = AF_INET;
sa.sin_port = s->port;
sa.sin_addr.s_addr = INADDR_ANY;
if(bind(s->fd, (struct sockaddr*)&sa, sizeof(sa)) != 0)
{
close(s->fd);
s->fd = -1;
return inetd_error("bind", 1);
}
if(s->socket == SS_STREAM && listen(s->fd, SOMAXCONN) != 0)
{
close(s->fd);
s->fd = -1;
return inetd_error("listen", 1);
}
if(inetd_state->debug)
fprintf(stderr, "%s%s%s%d%s", "inetd: Service \"", s->name,
"\" listening on port ", ntohs(s->port), "\n");
return 0;
}
/* service_exec */
static int _exec_tcp(Service * s);
static int _exec_udp_nowait(Service * s);
static int _exec_udp_wait(Service * s);
int service_exec(Service * s)
{
switch(s->proto)
{
case SP_TCP:
return _exec_tcp(s);
case SP_UDP:
if(s->wait == SW_NOWAIT)
return _exec_udp_nowait(s);
return _exec_udp_wait(s);
default:
if(inetd_state->debug)
fputs("inetd: Not implemented\n", stderr);
return 1;
}
}
static int _exec_tcp(Service * s)
{
int fd;
pid_t pid;
struct sockaddr_in sa;
socklen_t sa_size = sizeof(struct sockaddr_in);
if((fd = accept(s->fd, (struct sockaddr*)&sa, &sa_size)) == -1)
return inetd_error("accept", 1);
if((pid = fork()) == -1)
return inetd_error("fork", 1);
else if(pid > 0)
return close(fd);
if(s->id.uid && setuid(s->id.uid))
inetd_error("setuid", 0);
if(s->id.gid && setgid(s->id.gid))
inetd_error("setgid", 0);
if(close(0) != 0 || close(1) != 0 || dup2(fd, 0) != 0
|| dup2(fd, 1) != 1)
inetd_error("dup2", 0);
else
{
execv(s->program[0], &s->program[s->program[1] ? 1 : 0]);
inetd_error(s->program[0], 0);
}
exit(2);
return inetd_error("exit", 1);
}
static int _exec_udp_nowait(Service * s)
{
pid_t pid;
if((pid = fork()) == -1)
return inetd_error("fork", 1);
else if(pid > 0)
return 0;
if(s->id.uid && setuid(s->id.uid))
inetd_error("setuid", 0);
if(s->id.gid && setgid(s->id.gid))
inetd_error("setgid", 0);
if(close(0) != 0 || close(1) != 0 || dup2(s->fd, 0) != 0
|| dup2(2, 1) != 1)
inetd_error("dup2", 0);
execv(s->program[0], &s->program[s->program[1] ? 1 : 0]);
/* FIXME
* - receive packet some time (normal and error cases) */
inetd_error(s->program[0], 0);
exit(2);
return inetd_error("exit", 1);
}
static int _exec_udp_wait(Service * s)
{
if((s->pid = fork()) == -1)
return inetd_error("fork", 1);
else if(s->pid > 0)
return 0;
if(close(0) != 0 || close(1) != 0 || dup2(s->fd, 0) != 0
|| dup2(2, 1) != 1)
inetd_error("dup2", 0);
else
{
execv(s->program[0], &s->program[s->program[1] ? 1 : 0]);
inetd_error(s->program[0], 0);
}
exit(2);
return inetd_error("exit", 1);
}