/*****************************************************************************
POPular -- A POP3 server and proxy for large mail systems
$Id: ringd.c,v 1.7 2004/08/21 10:19:29 sqrt Exp $
http://www.remote.org/jochen/mail/popular/
******************************************************************************
Copyright (C) 1999-2004 Jochen Topf <jochen@remote.org>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
*****************************************************************************/
#if HAVE_CONFIG_H
# include <config.h>
#endif
#include "popular.h"
#include "ringd.h"
#include "daemon.h"
/* define this if you want debugging messages in the log */
/*#undef DEBUG */
#define DEBUG
/* this is the size of the message buffer */
#define BUFSIZE 80
int debug;
struct socket_list {
int refcount;
int socket;
struct sockaddr_in addr;
} socket_list[RINGD_MAX_SOCK];
/*****************************************************************************
open_and_bind()
*****************************************************************************/
int
open_and_bind(const struct sockaddr_in *addr) {
const int one=1;
int sock;
int i;
int slot = -1;
/***************************************************************************
Find first empty slot in socket_list
***************************************************************************/
for (i=0; i < RINGD_MAX_SOCK; i++) {
if (socket_list[i].refcount == 0) {
slot = i;
break;
}
}
if (slot == -1) {
/* XLOG-DOC:SOS:0187:socket_max_reached
* The maximum number of sockets is reached. If you need more sockets
* change RINGD_MAX_SOCK and recompile. */
xlog_printf(xlog_sos, 0x0187, "socket_max_reached");
return -1;
}
/***************************************************************************
Create socket and bind it
***************************************************************************/
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
/* XLOG-DOC:ADM:0188:socket_error
* The socket system call returned an error. */
xlog_printf(xlog_sos, 0x0188, "socket_error errno=%d errmsg='%s'", errno, strerror(errno));
return -1;
}
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) == -1) {
/* XLOG-DOC:ADM:0189:setsockopt_error
* The setsockopt system call returned an error. */
xlog_printf(xlog_adm, 0x0189, "setsockopt_error errno=%d errmsg='%s'", errno, strerror(errno));
}
if (bind(sock, (struct sockaddr*)addr, sizeof(struct sockaddr_in)) == -1) {
/* XLOG-DOC:SOS:018a:bind_error
* The bind system call returned an error. */
xlog_printf(xlog_sos, 0x018a, "bind_error errno=%d errmsg='%s'", errno, strerror(errno));
close(sock);
return -1;
}
socket_list[slot].refcount = 1;
socket_list[slot].socket = sock;
socket_list[slot].addr = *addr;
return sock;
}
/*****************************************************************************
read_port(char *arg)
*****************************************************************************/
int
read_port(char *arg)
{
static int ports[] = { ALLOWED_PORTS, 0 };
long int port;
int i;
char *tptr;
port = strtol(arg, &tptr, 10);
if (*tptr) { /* trailing chars after number */
/* XLOG-DOC:ADM:018f:port_invalid
* The port number given as second argument to a ringd command is
* syntactically invalid. */
xlog_printf(xlog_adm, 0x018e, "port_invalid");
return 0;
}
if (port < 1 || port > 65535) {
/* XLOG-DOC:ADM:0190:port_out_of_range
* The port number given as second argument to a ringd command is
* out of range. */
xlog_printf(xlog_adm, 0x0190, "port_out_of_range");
return 0;
}
for (i=0; ports[i]; i++) {
if (ports[i] == port) return (int)port;
}
/* XLOG-DOC:ADM:0191:port_forbidden
* The port number given as second argument to a ringd command is
* forbidden by configuration (change ALLOWED_PORTS and recompile). */
xlog_printf(xlog_adm, 0x0191, "port_forbidden");
return 0;
}
/*****************************************************************************
handle_request()
Handle a single bind request.
*****************************************************************************/
void
handle_request(int control_socket)
{
char buf[BUFSIZE];
struct sockaddr_in server_addr;
int sock=-1;
int len;
unsigned short int port;
char *space;
struct sockaddr_un from;
socklen_t fromlen=sizeof(struct sockaddr_un);
#define UNIX_MAX_PATH 108
char addr[UNIX_MAX_PATH+1];
char command;
int n;
/***************************************************************************
Get request from control socket.
***************************************************************************/
len = recvfrom(control_socket, buf, sizeof(buf)-1, 0, (struct sockaddr *)&from, &fromlen);
if (len <= 0) {
DEBUG0(DG_IO, "handle_request", "recvfrom <= 0");
return;
}
buf[len] = 0;
DEBUG1(DG_IO, "handle_request", "received request: '%s'", buf);
strncpy(addr, from.sun_path, fromlen);
addr[fromlen] = '\0';
/***************************************************************************
Find command.
***************************************************************************/
command = buf[0];
if (! strchr("ockl", command)) {
/* XLOG-DOC:ADM:018c:unknown_command
* ringd received an unknown command. */
xlog_printf(xlog_adm, 0x018c, "unknown_command cmd='%c'", command);
uds_send(control_socket, addr, "- Syntax error in request", -1);
return;
}
if (strchr("ock", command)) {
/*************************************************************************
Check for space as 2nd char
*************************************************************************/
if (buf[1] != ' ') {
/* XLOG-DOC:ADM:018d:space_char_missing
* ringd misses the space char at the 2nd position in the command
* string which separates the command from arguments. */
xlog_printf(xlog_adm, 0x018c, "space_char_missing");
uds_send(control_socket, addr, "- Syntax error in request", -1);
return;
}
/*************************************************************************
Find the space char in request, that is separating IP number and port.
*************************************************************************/
space = strchr(buf+2, ' ');
if (! space) {
/* XLOG-DOC:ADM:018e:space_char_missing
* ringd misses the space char in the command string which separates
* the first from the second argument */
xlog_printf(xlog_adm, 0x018e, "space_char_missing");
uds_send(control_socket, addr, "- Syntax error in request", -1);
return;
}
*space++ = 0;
/*************************************************************************
Get and check port.
*************************************************************************/
port = read_port(space);
if (! port) {
uds_send(control_socket, addr, "- Illegal port", -1);
return;
}
/*************************************************************************
Get IP number from request and fill in 'server_addr' struct.
*************************************************************************/
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
if (! strcmp(buf+2, "ANY")) {
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
} else {
if (! inet_aton(buf+2, &server_addr.sin_addr)) {
/* XLOG-DOC:ADM:0192:invalid_ip
* The port number given as first argument to a ringd command is
* is invalid. */
xlog_printf(xlog_adm, 0x0192, "invalid_ip");
uds_send(control_socket, addr, "- Invalid IP number", -1);
return;
}
}
DEBUG3(DG_MAIN, "handle_request", "Got command %c for IP %s port %d", command, buf+2, port);
} else {
DEBUG1(DG_MAIN, "handle_request", "Got command %c", command);
}
if (command == 'o') {
/*************************************************************************
Check if we already opened this port at some time in the past.
*************************************************************************/
for (n=0; n < RINGD_MAX_SOCK; n++) {
if (socket_list[n].refcount == 0) continue;
if (!memcmp(&server_addr, &(socket_list[n].addr), sizeof(struct sockaddr_in))) {
socket_list[n].refcount++;
sock = socket_list[n].socket;
DEBUG0(DG_MAIN, "handle_request", "socket matched");
break;
}
}
/*************************************************************************
Open socket and bind it to requested IP number and port.
*************************************************************************/
if (sock < 0) {
sock = open_and_bind(&server_addr);
if (sock < 0) {
uds_send(control_socket, addr, "- failed", -1);
return;
}
}
/*************************************************************************
Send message with 'enclosed' file descriptor.
*************************************************************************/
DEBUG2(DG_MAIN, "handle_request", "control_socket=%d sock=%d\n", control_socket, sock);
(void) uds_send(control_socket, addr, "+ OK", sock);
} else if (command == 'l') {
for (n=0; n < RINGD_MAX_SOCK; n++) {
if (socket_list[n].refcount == 0) continue;
/* XLOG-DOC:INF:018b:port_list
* List of currently opend ports. */
xlog_printf(xlog_inf, 0x018b, "port_list n=%d ip=%s port=%d refcount=%d", n, print_ip(&(socket_list[n].addr), NULL), ntohs(socket_list[n].addr.sin_port), socket_list[n].refcount);
}
(void) uds_send(control_socket, addr, "+ OK", -1);
} else if ((command == 'c') || (command == 'k')) {
for (n=0; n < RINGD_MAX_SOCK; n++) {
if (socket_list[n].refcount == 0) continue;
if (!memcmp(&server_addr, &(socket_list[n].addr), sizeof(struct sockaddr_in))) {
DEBUG0(DG_MAIN, "handle_request", "socket matched");
if (command == 'c') { /* close command */
socket_list[n].refcount--;
if (socket_list[n].refcount == 0) {
close(socket_list[n].socket);
}
} else { /* kill command */
socket_list[n].refcount = 0;
close(socket_list[n].socket);
}
break;
}
}
(void) uds_send(control_socket, addr, "+ OK", -1);
}
DEBUG0(DG_MAIN, "handle_request", "done");
return;
}
/*****************************************************************************
print_usage()
*****************************************************************************/
void
print_usage(const char *prg)
{
printf("Usage: %s [OPTION] ...\n", prg);
printf("Ringd binds sockets for non-root programs.\n");
printf("Options:\n");
#if 0
printf(" -l, --logfile=FILE set name of logfile\n");
printf(" (default: `%s')\n", PPROXY_LOG_FILE);
printf(" -r, --rundir=DIR set name of directory for pid and state file,\n");
printf(" and for ctrl socket (default: `%s')\n", RUN_DIR);
#endif
printf(" -u, --user=USER set owner for UNIX domain socket\n");
printf(" -g, --group=GROUP set group for UNIX domain socket \n");
printf(" --help print this help message\n");
printf(" --version print version information\n");
printf("\n");
}
/*****************************************************************************
main()
*****************************************************************************/
int
main(int argc, char *argv[])
{
int control_socket;
int sig;
struct timeval timeout;
fd_set fdset;
int i;
int c;
char user[30];
char group[30];
uid_t uid;
gid_t gid;
char *prgname = argv[0];
static struct option long_options[] = {
{ "help", no_argument, 0, 0 },
{ "version", no_argument, 0, 0 },
{ "user", required_argument, 0, 'u' },
{ "group", required_argument, 0, 'g' },
{ NULL, 0, 0, 0 }
};
#ifdef DEBUG
debug = DG_ALL;
#endif
/***************************************************************************
Set defaults for various config options.
***************************************************************************/
user[0] = '\0';
group[0] = '\0';
uid = RINGD_DEFAULT_UID;
gid = RINGD_DEFAULT_GID;
/***************************************************************************
Parse command line options.
***************************************************************************/
while (1) {
int option_index = 0;
c = getopt_long(argc, argv, "u:g:", long_options, &option_index);
if (c == -1) break;
switch (c) {
case 0:
if (! strcmp(long_options[option_index].name, "version")) {
printf("ringd - POPular POP server suite %s\n", VERSION);
exit(RCODE_OK);
} else if (! strcmp(long_options[option_index].name, "help")) {
print_usage(prgname);
exit(RCODE_OK);
} else {
fprintf(stderr, "%s: unknown option: %s\n", prgname, long_options[option_index].name);
exit(RCODE_INTERNAL);
}
break;
case 'u': /* user */
(void) strlcpy(user, optarg, sizeof(user));
break;
case 'g': /* group */
(void) strlcpy(group, optarg, sizeof(group));
break;
#if 0
case 'l': /* logfile */
(void) strlcpy(conf.logfile, optarg, sizeof(conf.logfile));
break;
case 'r': /* rundir */
(void) strlcpy(conf.rundir, optarg, sizeof(conf.rundir));
break;
#endif
default:
fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
exit(RCODE_CMDLINE);
}
}
if (! user[0]) {
fprintf(stderr, "%s: missing -u option.\n", prgname);
fprintf(stderr, "Try `%s --help' for more information.\n", prgname);
exit(RCODE_CMDLINE);
}
if (optind != argc) {
fprintf(stderr, "%s: invalid extra arguments.\nTry `%s --help' for more information.\n", prgname, prgname);
exit(RCODE_CMDLINE);
}
/***************************************************************************
Check user and group names and find corresponding uid and gid.
***************************************************************************/
{
struct passwd *pwd;
struct group *grp;
pwd = getpwnam(user);
if (! pwd) {
fprintf(stderr, "%s: user `%s' not found\n", prgname, user);
exit(RCODE_CMDLINE);
}
if (pwd->pw_uid) uid = pwd->pw_uid;
else {
fprintf(stderr, "%s: user `%s' is uid 0. Don't do that!\n", prgname, user);
exit(RCODE_CMDLINE);
}
if (group[0]) {
grp = getgrnam(group);
if (! grp) {
fprintf(stderr, "%s: group `%s' not in /etc/group.\n", prgname, group);
exit(RCODE_CMDLINE);
}
gid = grp->gr_gid;
} else {
gid = pwd->pw_gid;
grp = getgrgid(gid);
if (! grp) {
fprintf(stderr, "%s: group `%s' not in /etc/group.\n", prgname, group);
exit(RCODE_CMDLINE);
}
(void) strlcpy(group, grp->gr_name, sizeof(group));
}
}
/***************************************************************************
Open log file
***************************************************************************/
if (xlog_open(RINGD_PRG_NAME, RINGD_LOG_NAME, RINGD_LOG_MODE) < 0) {
fprintf(stderr, "Could not open log file: %s\n", RINGD_LOG_NAME);
exit(RCODE_ERR);
}
/***************************************************************************
Become a daemon and initialize signal handling
***************************************************************************/
daemonize(RINGD_PRG_NAME);
signal_init(0);
/* XLOG-DOC:INF:0183:start
* ringd has started. */
xlog_printf(xlog_inf, 0x0183, "start");
/***************************************************************************
Open pid file now and write pid into it.
***************************************************************************/
write_pidfile(RUN_DIR, RINGD_PID_TEMPL, RINGD_PID_FILE);
/***************************************************************************
Open control socket.
***************************************************************************/
unlink(RINGD_SOCK_FILE); /* in case there is one left from last run */
control_socket = uds_socket(RINGD_SOCK_FILE, RINGD_SOCK_UMASK);
if (control_socket < 0) {
/* XLOG-DOC:SOS:0184:open_socket
* ringd can't open the unix domain socket to listen for commands. */
xlog_printf(xlog_sos, 0x0184, "open_socket");
goto clean_exit;
}
if (chown(RINGD_SOCK_FILE, uid, gid) != 0) {
/* XLOG-DOC:SOS:0185:chown_socket
* ringd can't chown its unix domain socket. */
xlog_printf(xlog_sos, 0x0185, "chown_socket");
goto clean_exit;
}
/***************************************************************************
Initialize socket list
***************************************************************************/
for (i=0; i<RINGD_MAX_SOCK; i++) {
socket_list[i].refcount=0;
}
/***************************************************************************
Main loop to handle requests.
***************************************************************************/
while (1) {
timeout.tv_sec = 1;
timeout.tv_usec = 0;
FD_ZERO(&fdset);
FD_SET(control_socket, &fdset);
if (select(FD_SETSIZE, &fdset, (fd_set *) 0, (fd_set *) 0, &timeout) > 0) {
handle_request(control_socket);
}
sig = signal_next();
if (sig == SIGHUP) {
DEBUG0(DG_MAIN, "main", "Got SIGHUP. Reopening Logfile.");
xlog_reopen(RINGD_LOG_NAME, RINGD_LOG_MODE);
} else if (sig) {
/* XLOG-DOC:INF:0186:quit
* ringd go a signal and will quit now. */
xlog_printf(xlog_inf, 0x0186, "quit sig=%d", sig);
break;
}
}
clean_exit:
unlink(RINGD_SOCK_FILE);
unlink_in_rundir(RUN_DIR, RINGD_PID_TEMPL, RINGD_PID_FILE);
return 0;
}
/** THE END *****************************************************************/
syntax highlighted by Code2HTML, v. 0.9.1