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