/*
*
* Murat's Ident Daemon
*
* (C) 1998-2002 Murat Deligonul
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* ------------------------
* FIXME:
* * Checks for idle time limits and such are not
* very efficient (at all?) right now.
*
* * num_conns and num_unix ought to be handled by the
* linkedlist code. That is, the linkedlist should know
* how many elements it has when we ask for it.
*
* * is_valid_num works but is borken
*
* * Fake ident lifetime seems to not work reliably?
* They get deleted, but sometimes after the life
* period.
*
* * I need to eliminate defs.h and make it all command
* line configurable.
*
* * On FreeBSD socket name is truncated??
*
*/
#include "autoconf.h"
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <signal.h>
#include <syslog.h>
#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <sys/time.h>
#include "identd.h"
#include "defs.h"
#include "lib_mdidentd.h"
static int daemon(bool cr);
static int unix_listen_fd, listen_fd;
static bool foreground, auto_kill; /* Some options */
bool no_real;
static uid_t uid; /* Become this user after binding sock */
static unsigned int hard_limit; /* Max # of fake idents */
static unsigned int num_conns;
static unsigned int num_unix;
static char * identd_path; /* where secondary identd resides */
static char ** identd_args;
static int num_cmdargs;
static int max_time; /* How long fake ident requests will live */
static list<conn> conns; /* store the connections on port 113 */
static list<conn> unix_conns; /* store connections to the domain socket */
static ilist * table; /* store all of the fake idents */
int gettok(const char *, char *, unsigned long, char, int, bool = 0);
int safe_strcpy(char *, const char *, unsigned long );
int fdprintf(int fd, const char *, ...);
const char *MDIDENTD_VERSION = "0.85.5";
inline void usage(void)
{
fprintf(stderr, "mdidentd v%s - Murat Deligonul (druglord@freelsd.org) - (C) 1998-2002\n"
"\nUsage:\n"
"mdidentd [-f|-k|-h|-u] <identd> [options]\nWhere:\n"
"-u <userid>: Setuid to this user after binding sockets\n"
"-h <number>: Hard fake ident limit (max # of fake idents to store at once\n"
"-f: Stay in foreground\n"
"-k: Immediately kill fake ident requests after they have been used\n"
"-r: Prevent users from using setting idents that are real users on this machine\n"
"\n"
"identd: The path to your existing ident daemon.\n"
" It will be launched by mdidentd if needed\n"
" This is a required argument. Try /usr/sbin/in.identd if unsure.\n"
"options Optional arguments to pass on to the ident daemon mentioned above\n",
MDIDENTD_VERSION);
}
/*
* FIXME: '6667ax a' is valid according to this
* but "ax66667" is not
*/
static inline bool is_valid_num(const char * num)
{
bool success = 0;
do {
if (!isdigit(*num) && !isspace(*num) && !(*num == '\r' || *num == '\n'))
return 0;
success = 1;
} while (*++num);
return 1;
}
/*
* Parse arguments including:
* the identd to spawn, and what arguments to give to it
* userid to become after getting port
* path of domain socket to create
* how long to keep custom idents alive.
*
* We don't make copies of the stuff in argv but that should be ok
* for now.
*/
static bool parse_cmdline(int argc, char ** argv)
{
int cmdarg_start = 0;
listen_fd = unix_listen_fd = -1;
num_cmdargs = 0;
foreground = auto_kill = 0;
uid = 0;
hard_limit = 200;
identd_args = 0;
num_unix = num_conns = 0;
if (argc < 2)
{
usage();
return 0;
}
for (int y = 1; y < argc; y++)
{
switch (argv[y][0])
{
case '-':
switch (argv[y][1])
{
case 'r':
no_real = 1;
break;
case 'f':
foreground = 1;
break;
case 'k':
auto_kill = 1;
break;
case 'u':
char * id;
id = argv[y+1];
if (!id)
{
fprintf(stderr, "Option '-u' requires numeric user id to follow it\n");
exit(1);
}
uid = (uid_t) atoi(id);
y++;
break;
case 'h':
id = argv[y+1];
if ((!id) || !(hard_limit = (unsigned) atoi(id)))
{
fprintf(stderr, "Option '-h' requires a number greater than 0\n");
exit(1);
}
y++;
break;
default:
fprintf(stderr, "Unknown option %c, but continuing anyway\n", argv[y][1]);
}
break;
default:
/*
* Assume this is the start of the path & cmdline
* for the other identd. argv[y] is the executable
* and the rest is the arguments.
* We set the other program's argv[0] ourselves.
*/
identd_path = argv[y];
cmdarg_start = y + 1;
num_cmdargs = argc - (cmdarg_start) + 1;
identd_args = new char*[num_cmdargs + 1 /* need one argument for argv[0]
* to be given to spawned identd */
+ 1 /* and one for NULL */];
identd_args[0] = identd_path; /* set argv[0] here */
for (int q = 1; q - 1 < num_cmdargs; q++)
identd_args[q] = argv[cmdarg_start + q - 1];
identd_args[num_cmdargs] = NULL;
/* we're done */
return 1;
}
}
return 1;
}
static int listen(unsigned short port)
{
struct sockaddr_in sin;
int parm = 1;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char * ) &parm, sizeof(int));
if (listen_fd < 0
|| bind(listen_fd, (struct sockaddr *) &sin, sizeof(sin)) < 0
|| ::listen(listen_fd, 5) < 0)
{
close(listen_fd);
listen_fd = -1;
return 0;
}
return 1;
}
/*
* Note: this effectively destroys whatever file
* is at 'path'.
*
* Return: 1 success
* 0 failure - check errno
*/
static int listen_unix(char const * path)
{
struct sockaddr_un thing;
size_t len;
if ((unix_listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
return 0;
unlink(path);
memset(&thing, 0, sizeof(thing));
thing.sun_family = AF_UNIX;
safe_strcpy(thing.sun_path, path, sizeof(thing.sun_path));
len = sizeof(thing.sun_family) + strlen(thing.sun_path);
if (bind(unix_listen_fd, (struct sockaddr *) &thing, len) < 0
|| ::listen(unix_listen_fd, 3) < 0)
{
unix_listen_fd = -1;
return 0;
}
/* Must make it world writable if we expect to receive connections
on it */
fchmod(unix_listen_fd, 0777);
return 1;
}
static int service_unix(conn * c)
{
table->read_from(c->fd);
close_conn(c);
return -1;
}
/*
* Accept domain socket connection.
* Does limits and such.
*/
static int accept_unix_connection(int fd)
{
struct sockaddr_un thing;
socklen_t x = sizeof(thing);
int fd2 = accept(fd, (struct sockaddr *) &thing, &x);
if (fd2 < 0)
return 0;
if (table->get_num() >= hard_limit)
{
syslog(LOG_ERR, "Not accepting domain socket connection: hard limit on fake idents exceeded");
close(fd2);
return 0;
}
conn * c = new conn;
if (!c)
{
syslog(LOG_ERR, "accept: memory allocation error after accepting connection on domain sock");
close(fd2);
return 0;
}
c->fd = fd2;
c->bytes_in = 0;
c->time = time(NULL);
memset(c->input_buff, 0, sizeof(c->input_buff));
unix_conns.add(c);
num_unix++;
return 1;
}
static int accept_connection(int fd)
{
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
int fd2 = accept(fd, (struct sockaddr *) &sin, &len);
if (fd2 < 0)
{
syslog(LOG_ERR, "accept: %s", strerror(errno));
return 0;
}
conn * c = new conn;
if (!c)
{
syslog(LOG_ERR, "accept: memory allocation error after accepting");
close(fd2);
return 0;
}
c->fd = fd2;
c->bytes_in = 0;
c->time = time(NULL);
memset(c->input_buff, 0, sizeof(c->input_buff));
conns.add(c);
num_conns++;
return 1;
}
/*
* We need to read with MSG_PEEK, because the data must also be readable
* to the other ident daemon if it needs to be spawned.
*
* Problems:
* Socket will still be marked as readable afterwards. Select() will indicate
* so. Will cause infinite loop. For that reason if there is no valid
* ident response that we can reply to in the first recv attempt,
* then the request will be given to the other identd. Better fix for this
* issue? I don't know!
*
* Return -1 if 'c' should be destroyed.
*/
static int service(conn * c)
{
c->bytes_in = recv(c->fd, c->input_buff, sizeof(c->input_buff) - 1, MSG_PEEK);
if (c->bytes_in <= 0)
{
if (errno)
syslog(LOG_INFO, "Connection closed: %s", strerror(errno));
close_conn(c);
return -1;
}
/* We must add null char to the end */
c->input_buff[c->bytes_in] = 0;
reply(c);
return -1;
}
static void close_conn(conn * c)
{
close(c->fd);
c->fd = -1;
c->bytes_in = 0;
}
/*
* Read the ident request from its input buffer and
* see if matches any of our fake ones, if not..
* its time to load the user's other identd.
*
* Currently replies system name as 'OTHER'.
*/
static int reply(conn * c)
{
char port1[6], port2[16];
unsigned short p1 = 0, p2 = 0;
/*
* We must have \n or \r or we drop it
*/
if (strchr(c->input_buff, '\r') || strchr(c->input_buff, '\n'))
{
if ((gettok(c->input_buff, port1, sizeof port1, ',', 1))
&& (gettok(c->input_buff, port2, sizeof port2, ',', 2))
&& (p1 = atoi(port1))
&& (p2 = atoi(port2))
&& is_valid_num(port1)
&& is_valid_num(port2))
{
char ident[50];
struct sockaddr_in sin;
socklen_t len = sizeof(sin);
getpeername(c->fd, (struct sockaddr *) &sin, &len);
if (table->lookup(p1, p2, ident, sizeof(ident)))
{
syslog(LOG_INFO, "Request from %s for: %d , %d ", inet_ntoa(sin.sin_addr), p1, p2);
syslog(LOG_INFO, "(fake) reply: %d, %d : USERID : OTHER :%s", p1, p2, ident);
fdprintf(c->fd, "%d , %d : USERID : OTHER :%s\r\n",
p1, p2, ident);
if (auto_kill)
table->remove(p1, p2, ident);
close_conn(c);
return 1;
}
}
}
/* Have sex */
switch (fork())
{
case 0:
/*
* This is the child: close out unneeded fds,
* redirect stdin/err/out to socket.
*/
close(unix_listen_fd);
close(listen_fd);
dup2(c->fd, 0);
/* close_conn(c); */
delete c;
dup2(0, 1);
dup2(0,2);
signal(SIGPIPE, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGALRM, SIG_DFL);
if (execv(identd_path, identd_args) < 0)
syslog(LOG_ERR, "Unable to spawn %s: %s", identd_path, strerror(errno));
/* unreached */
exit(0);
return 1;
case -1:
syslog(LOG_ERR, "Can't spawn %s: fork: %s", identd_path, strerror(errno));
default:
close_conn(c);
break;
}
return 1;
}
/*
* Start the server and enter the main loop.
* Create fd_sets, select, poll ilist.
* Blah blah.
*/
static int start_server(time_t max)
{
fd_set fds;
struct timeval tv;
time_t select_time = time(NULL);
time_t curtime;
conn * c;
max_time = max;
table = new ilist(max_time);
if (!table)
return fprintf(stderr,"Memory allocation failure. Bailing out.\n"), 1;
if (!listen(113))
return perror("While binding to port 113"), 1;
if (!listen_unix(MDIDENTD_SOCK_PATH))
return perror(MDIDENTD_SOCK_PATH), 1;
/* we already did this. but it doesn't work. i dunno why.
* i am messing up somwhere. dunno where. but we will do it again.
* and it will work. so there!! */
chmod(MDIDENTD_SOCK_PATH, 0777);
#ifndef NOFORK
if (!foreground)
{
switch (daemon(1))
{
case -1:
perror("While trying to go into the background");
return 0;
case 0:
break;
default:
return 0;
}
}
#endif
if (setuid(uid) != 0)
syslog(LOG_ERR, "setuid(%d) failed: %s (but continuing anyway)", uid, strerror(errno));
/* Set up signals */
struct sigaction sa;
sa.sa_flags = 0;
sa.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &sa, NULL);
sa.sa_handler = &sighandler;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
/* syslog setup */
openlog("mdidentd", LOG_PID, LOG_DAEMON);
syslog(LOG_INFO, "Version %s starting. Using %s as secondary ident daemon.", MDIDENTD_VERSION, identd_path);
list_iterator<conn> i(&conns);
list_iterator<conn> i2(&unix_conns);
while (69)
{
int hf = listen_fd > unix_listen_fd ? listen_fd : unix_listen_fd;
i.set(0);
i2.set(0);
FD_ZERO(&fds);
FD_SET(listen_fd, &fds);
FD_SET(unix_listen_fd, &fds);
while (i.has_next())
{
c = i.next();
FD_SET(c->fd, &fds);
hf = (hf > c->fd) ? hf : c->fd;
}
while (i2.has_next())
{
c = i2.next();
FD_SET(c->fd, &fds);
hf = (hf > c->fd) ? hf : c->fd;
}
tv.tv_sec = max_time;
tv.tv_usec = 0;
/*
* Update the select time counter only if the new time would
* be more than 'max_time' newer than the old one.
*/
if ((select_time - time(&curtime)) < -max_time)
select_time = curtime;
switch (select(hf + 1, &fds, (fd_set *) NULL, (fd_set *) NULL, max_time ? &tv : (struct timeval *) NULL))
{
case 0:
/* This is starting to look like the linux kernel... */
goto check_time;
case -1:
if (errno != EINTR)
{
syslog(LOG_ERR, "select: %s -- bailing out", strerror(errno));
return 0;
}
continue;
}
if (FD_ISSET(unix_listen_fd, &fds))
accept_unix_connection(unix_listen_fd);
if (FD_ISSET(listen_fd,&fds))
accept_connection(listen_fd);
/*
* FIXME: these ought to be combined in a singe loop
*/
i.set(0);
i2.set(0);
time(&curtime);
while (i.has_next())
{
c = i.next();
if (c->fd != listen_fd && FD_ISSET(c->fd, &fds) && service(c) == -1)
{
delete c;
i.remove();
num_conns--;
} /* Check for idlers while we're here */
else if (c->fd != listen_fd && curtime - c->time >= MAX_IDENT_WAIT_TIME)
{
struct sockaddr_in sin;
socklen_t size = sizeof(sin);
getpeername(c->fd, (struct sockaddr *) &sin, &size);
syslog(LOG_INFO, "Killing idling connection from %s", inet_ntoa(sin.sin_addr));
close_conn(c);
delete c;
i.remove();
num_conns--;
}
}
while (i2.has_next())
{
c = i2.next();
if (c->fd != unix_listen_fd && FD_ISSET(c->fd, &fds) && service_unix(c) == -1)
{
delete c;
i2.remove();
num_unix--;
} /* Check for idlers while we're here */
else if (c->fd != unix_listen_fd && curtime - c->time >= MAX_UNIX_WAIT_TIME)
{
close_conn(c);
syslog(LOG_INFO, "Killing idling connection on domain socket");
delete c;
i2.remove();
num_unix--;
}
}
check_time:
/*
* Check for expired fake request and other things
*/
if ((curtime - select_time) >= max_time)
table->kill_expired(curtime);
}
return 1;
}
int main(int argc, char **argv)
{
if (parse_cmdline(argc, argv))
return start_server(FAKE_IDENT_LIFETIME);
return 1;
}
void sighandler(int sig)
{
switch (sig)
{
case SIGCHLD:
/* Collect exit status from exiting child */
wait(&sig);
break;
case SIGTERM:
case SIGINT:
syslog(LOG_INFO, "Exiting on signal %d", sig);
closelog();
delete[] identd_args;
identd_args = 0;
exit(0);
case SIGHUP:
syslog(LOG_INFO, "HANGUP signal, don't know what to do.");
break;
case SIGUSR1:
syslog(LOG_INFO, "Dumping stats");
syslog(LOG_INFO, "Active unix conns: %d Inet conns: %d", num_unix, num_conns);
syslog(LOG_INFO, "Fake ident requests in memory: %lu; lifetime is %d", table->get_num(),
FAKE_IDENT_LIFETIME);
syslog(LOG_INFO, " hard limit is %d", hard_limit);
syslog(LOG_INFO, "Number served: billions and billions");
break;
}
}
/*
* Return pid of child to parent.
* 0 to child.
* -1 on error.
*/
static int daemon(bool cr)
{
pid_t xx;
switch ((xx = fork()))
{
case 0:
/* Child */
setsid();
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
if (cr)
chroot("/");
return 0;
case -1:
return -1;
default:
return xx;
}
}
/*
* Gets which'th token, seperated by sep
* return 1 on success 0 on failure.
* Last argument indicates whether.
*
* Null argument can be passed as buffer to indicate you only
* want to check the existance of a token.
*/
int gettok(const char *p, char *buffer, unsigned long maxsize,
char sep, int which, bool get_rest)
{
int numfound = 1;
/* remove leading seps */
while (*p == sep)
p++;
do {
if (numfound == which && *p)
{
char * next = strchr(p, sep);
if ((get_rest && buffer) || (!next /* && *(p+1) */) )
{
if (buffer)
safe_strcpy(buffer, p, maxsize);
return 1;
}
else if (next)
{
if (buffer)
{
u_long bytes2copy = (next - p) + 1; /* how many bytes must be copied, including
NULL character */
if (bytes2copy >= maxsize)
bytes2copy = maxsize;
strncpy(buffer, p, bytes2copy);
buffer[bytes2copy - 1] = 0;
}
return 1;
}
return 0;
}
/* check if this letter == sep, and the next letter is not sep */
if (*p == sep && *(p+1) != sep)
numfound++;
} while (*++p);
/* failed */
return 0;
}
/*
* Copies src to dest, pretends dest is maxsize long.
* will truncate by 1 if needs room to add \0 (bad?)
*/
int safe_strcpy(char *dest, const char *src, unsigned long maxsize)
{
unsigned long len = strlen(src);
if (len >=maxsize)
len = maxsize - 1;
memcpy(dest, src, len);
dest[len] = 0;
return 1;
}
int fdprintf(int fd, const char *format, ...)
{
static char __mbuffer[1024];
__mbuffer[0] = 0;
va_list ap;
va_start(ap, format);
int len = vsnprintf(__mbuffer, sizeof(__mbuffer), format, ap);
va_end(ap);
return (write(fd, __mbuffer, len));
}
syntax highlighted by Code2HTML, v. 0.9.1