-/* $Id: mmspawnd.c,v 1.18 2004/03/14 09:26:13 mmondor Exp $ */
+/* $Id: mmspawnd.c,v 1.19 2004/04/06 16:35:23 mmondor Exp $ */
/*
* Copyright (C) 2003, Matthew Mondor
* a library like libevent or libio which could take adventage of features
* like kqueue and epoll where available. Using poll(2) wouldn't be too
* bad however...
+ * - Think properly. We have a recursion prevention lock, which should use
+ * a lock path outside of the chroot(2) if any. We have the ALOCK_PATH,
+ * which can also be outside the chroot(2), since only the parent locks it.
+ * However, there is the LOCK_PATH which should be inside the chroot(2) if
+ * any, because both the parent, cache manager and children processes are
+ * locking it. These files also need to be readable by the user or group
+ * under which mmspawnd(8) runs, so that shlocks_reopen() works. Hmm these
+ * are not used in the children processes however, only by the cache manager
+ * process after launch... Either we have chroot(2) called in each, in which
+ * case the files can be anywhere on the filesystem, or we cause those lock
+ * files to be created after chroot(2). Also, because we drop privileges
+ * after this, both can write and open the locks fine permission-wise.
+ * So plz hlp wut 2 doo 2 nut teh errer kthx!!11111 u teh their!?1//
+ * Argh!!! children processes call client_resolve()! Hence they also need
+ * to reopen lock file(s)... So LOCK_PATH HAS to be within the chroot(2),
+ * and chroot(2) needs to be called before calling shlocks_init()!
+ * - Fix client_resolve() so that getnameinfo(3) can be called concurrently.
+ * - Update man page about lock paths requirements etc!
*/
MMCOPYRIGHT("@(#) Copyright (c) 2003\n\
\tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmspawnd.c,v 1.18 2004/03/14 09:26:13 mmondor Exp $");
+MMRCSID("$Id: mmspawnd.c,v 1.19 2004/04/06 16:35:23 mmondor Exp $");
/* DEFINITIONS */
#define DAEMON_NAME "mmspawnd"
-#define DAEMON_VERSION "0.0.1/mmondor"
+#define DAEMON_VERSION "0.0.2/mmondor"
struct configuration {
- char CHROOT_DIR[256], LOCK_PATH[256], PID_PATH[256],
- USER[32], GROUPS[256], LOG_FACILITY[32], LISTEN_ADDRESSES[1024],
- COMMAND[256], ALOCK_PATH[256], ALOCK_USER[32], ALOCK_GROUP[32],
- ALOCK_MODE[4], PROCTITLE[64], STDERR_FILE[256];
+ char CHROOT_DIR[256], LOCKS_PATH[256], LOCKS_USER[32], LOCKS_GROUP[32],
+ LOCKS_MODE[4], PID_PATH[256], USER[32], GROUPS[256], LOG_FACILITY[32],
+ LISTEN_ADDRESSES[1024], COMMAND[256], ALOCK_PATH[256], ALOCK_USER[32],
+ ALOCK_GROUP[32], ALOCK_MODE[4], PROCTITLE[64], STDERR_FILE[256];
long LISTEN_PORT, MAX_CONNECTIONS, MAX_ADDRESSES, MAX_PER_ADDRESS,
CONNECTION_RATE, CONNECTION_PERIOD, MAX_ARGUMENTS, COMMAND_TIMEOUT,
LIMIT_CORE, LIMIT_CPU, LIMIT_DATA, LIMIT_FSIZE, LIMIT_MEMLOCK,
syslog(LOG_NOTICE, "* setrlimit(%s, %ld) - (%s)", \
(restxt), (limit), strerror(errno)); \
} \
-} while (/* CONSTCOND */0) ;
+} while (/* CONSTCOND */0)
static int key_pidcmp(const void *, const void *, size_t);
static u_int32_t key_pidhash(const void *, size_t);
+void launch_server_child_init(const char *, uid_t, gid_t *, int);
+
+static pid_t launch_server_process(uid_t, gid_t *, int);
+static void server_process_main(void);
+
static pid_t launch_ipaddr_expire_process(uid_t, gid_t *, int);
-static void ipaddr_expire_process(void);
+static void ipaddr_expire_process_main(void);
static bool ipaddr_expire_process_iterator(hashnode_t *, void *);
carg_t *cargp;
carg_t cargs[] = {
{CAT_STR, CAF_NONE, 0, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
- {CAT_STR, CAF_NONE, 1, 255, "LOCK_PATH", CONF.LOCK_PATH},
+ {CAT_STR, CAF_NONE, 1, 255, "LOCKS_PATH", CONF.LOCKS_PATH},
+ {CAT_STR, CAF_NONE, 1, 31, "LOCKS_USER", CONF.LOCKS_USER},
+ {CAT_STR, CAF_NONE, 1, 31, "LOCKS_GROUP", CONF.LOCKS_GROUP},
+ {CAT_STR, CAF_NONE, 1, 3, "LOCKS_MODE", CONF.LOCKS_MODE},
{CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
{CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
{CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
/* Set harmless defaults */
*CONF.CHROOT_DIR = '\0';
- (void) mm_strcpy(CONF.LOCK_PATH, "/var/run/mmspawnd.lock");
+ (void) mm_strcpy(CONF.LOCKS_PATH, "/var/run/mmspawnd.lock");
+ (void) mm_strcpy(CONF.LOCKS_USER, "mmspawnd");
+ (void) mm_strcpy(CONF.LOCKS_GROUP, "mmspawnd");
+ (void) mm_strcpy(CONF.LOCKS_MODE, "400");
(void) mm_strcpy(CONF.PID_PATH, "/var/run/mmspawnd.pid");
(void) mm_strcpy(CONF.USER, "mmspawnd");
(void) mm_strcpy(CONF.GROUPS, "mmspawnd");
CONF.GROUPS);
exit(EXIT_FAILURE);
}
+ /* Initialize alock */
if (*CONF.ALOCK_PATH != '\0') {
uid_t u;
gid_t g;
}
} else
server.alock = -1;
+ /* Initialize shlocks */
+ if (CONF.SHLOCKS_PATH != '\0') { /* Actually should always be true */
+ uid_t u;
+ gid_t g;
+ mode_t m;
+
+ if ((u = mmgetuid(CONF.LOCKS_USER)) == -1) {
+ (void) fprintf(stderr, "\nUnknown LOCKS_USER '%s'\n\n",
+ CONF.ALOCK_USER);
+ exit(EXIT_FAILURE);
+ }
+ if ((g = mmgetgid(CONF.LOCKS_GROUP)) == -1) {
+ (void) fprintf(stderr, "\nUnknown LOCKS_GROUP '%s'\n\n",
+ CONF.ALOCK_GROUP);
+ exit(EXIT_FAILURE);
+ }
+ (void) sscanf(CONF.LOCKS_MODE, "%o", &m);
+ if (m == 0 || m > 0770) {
+ (void) fprintf(stderr, "\nIllegal LOCKS_MODE '%03o'\n\n", m);
+ exit(EXIT_FAILURE);
+ }
+ /* XXX */
+ }
if ((ifaces = ifaces_parse(CONF.LISTEN_ADDRESSES)) == NULL) {
(void) fprintf(stderr, "\nInvalid LISTEN_ADDRESSES\n\n");
exit(EXIT_FAILURE);
/* Create our locks for shared memory synchronization. These lock files
* (LOCK_PATH) should also be available under the new root since they
* need to be reopened by our children.
+ * XXX Should be done after chroot(2), with proper permissions so that
+ * we may reopen the files in children processes, Moreover, the
+ * lock_check() lock path should be independent (since outside the chroot)?
*/
if (!shlocks_init(CONF.LOCK_PATH, locks, SHLOCK_MAX, TRUE)) {
syslog(LOG_NOTICE, "main() - shlocks_init()");
* Note that the following function also causes the child to become
* unprivileged.
*/
- if (launch_ipaddr_expire_process(uid, gids, ngids) < 1) {
+ if (launch_ipaddr_expire_process(uid, gids, ngids, CONF.CHROOT_PATH) < 1) {
syslog(LOG_NOTICE, "main() - launch_ipaddr_expire_process()");
exit(EXIT_FAILURE);
}
/* We create our own process group so that the
* process after execve(2) could not kill(0)
- * causing the listerner process to exit.
+ * causing the listener process to exit.
* It also allows us to optionally not interrupt
* currently served client when we restart.
*/
(void) sigaction(SIGPIPE, &act, NULL);
/* Close bound sockets which this process shouldn't
- * have access to
+ * have access to. XXX We could use fcntl(3) to
+ * change socket inheritance at execve(2)...
*/
for (tif = ifaces; *(tif->address); tif++) {
if (tif->sock != -1) {
/* sock always >2 */
(void) close(sock);
- /* Let children know and remember these */
+ /* Let child know and remember these */
client.ipn = ipn;
client.saddr = addr;
client.ipaddr = (const char *)ipaddr;
/* We do not need to hold the lock files any
* longer, and in fact we do not want to let
* the process access them, especially if the
- * adminstrator sets up the service to not kill
+ * administrator sets up the service to not kill
* currently running children if the service is
* shut down.
*/
- shlocks_destroy(CONF.LOCK_PATH, locks,
- (enum shlocks)SHLOCK_MAX, FALSE);
+ (void) shlocks_destroy(CONF.LOCK_PATH, locks,
+ (enum shlocks)SHLOCK_MAX, FALSE);
(void) close(server.runlock);
(void) close(server.alock);
}
-/* Launches the ipaddress-hostname-rate cache entry expiration process,
- * a kind of asynchroneous garbage collector.
- */
-static pid_t launch_ipaddr_expire_process(uid_t uid, gid_t *gids, int ngids)
-{
- pid_t pid = -1;
-
- if ((pid = fork()) == 0) {
- struct sigaction act;
-
- /* Reset default signal action */
- act.sa_handler = SIG_DFL;
- act.sa_flags = SA_NOCLDSTOP;
- (void) sigemptyset(&act.sa_mask);
-
- (void) sigaction(SIGTERM, &act, NULL);
- (void) sigaction(SIGINT, &act, NULL);
- (void) sigaction(SIGCHLD, &act, NULL);
- (void) sigaction(SIGSEGV, &act, NULL);
-
- /* Release runlock which only our parent should hold */
- (void) close(server.runlock);
-
- /* Drop privileges */
- if (!mmdropprivs(uid, gids, ngids)) {
- syslog(LOG_NOTICE, "launch_ipaddr_expire_process() - Cannot \
-change uid and gids to safe privs");
- (void) kill(0, SIGTERM);
- (void) exit(EXIT_FAILURE);
- }
- ipaddr_expire_process();
- /* NOTREACHED */
- }
-
- /* Parent */
- return pid;
-}
-
-
/* A quick hashtable_hash() and memcmp() replacements which already deal with
* unique 32-bit values.
*/
}
-/* This process can run independantly and manage the ipaddr cache entry
+/* Useful function for use by launch_server_process() and
+ * launch_ipaddress_expire_process() initialization
+ */
+void launch_server_child_init(const char *func, uid_t uid, gid_t *gids,
+ int ngids)
+{
+ /* Reopen lock files as needed for flock(2) */
+ if (shlocks_reopen(CONF.LOCK_PATH, locks, SHLOCK_MAX) == -1) {
+ syslog(LOG_NOTICE, "%s - Could not reopen lock files - %s",
+ func, strerror(errno));
+ (void) kill(getppid(), SIGTERM);
+ exit(EXIT_FAILURE);
+ }
+
+ /* chroot(2) if necessary */
+ if (CONF.CHROOT_PATH != '\0') {
+ if ((chroot(chroot_path)) == -1) {
+ syslog(LOG_NOTICE, "%s - Could not chroot(2) to '%s' - %s",
+ func, CONF.CHROOT_PATH, strerror(errno));
+ (void) kill(getppid(), SIGTERM);
+ exit(EXIT_FAILURE);
+ }
+ (void) chdir("/");
+ }
+
+ /* Drop privileges */
+ if (!mmdropprivs(uid, gids, ngids)) {
+ syslog(LOG_NOTICE, "%s - Cannot change uid and gids to safe privs",
+ func);
+ (void) kill(getppid(), SIGTERM);
+ (void) exit(EXIT_FAILURE);
+ }
+}
+
+
+/* Launches the main server program, listening for connections and dispatching
+ * them to children processes.
+ */
+static pid_t launch_server_process(uid_t uid, gid_t *gids, int ngids)
+{
+}
+
+static void server_process_main(void)
+{
+}
+
+
+/* Launches the ipaddress-hostname-rate cache entry expiration process,
+ * a kind of asynchroneous garbage collector.
+ */
+static pid_t launch_ipaddr_expire_process(uid_t uid, gid_t *gids, int ngids)
+{
+ pid_t pid = -1;
+
+ if ((pid = fork()) == 0) {
+ struct sigaction act;
+
+ /* Reset default signal action */
+ act.sa_handler = SIG_DFL;
+ act.sa_flags = SA_NOCLDSTOP;
+ (void) sigemptyset(&act.sa_mask);
+
+ (void) sigaction(SIGTERM, &act, NULL);
+ (void) sigaction(SIGINT, &act, NULL);
+ (void) sigaction(SIGCHLD, &act, NULL);
+ (void) sigaction(SIGSEGV, &act, NULL);
+
+ /* Release runlock which only our parent should hold */
+ (void) close(server.runlock);
+
+ /* Will reopen lock files, chroot(2) and drop privileges */
+ launch_server_child_init("launch_ipaddr_expire_process()",
+ uid, gids, ngids);
+
+ /* Finally lanch main process loop */
+ ipaddr_expire_process_main();
+ /* NOTREACHED */
+ }
+
+ /* Parent */
+ return pid;
+}
+
+/* This process can run independently and manage the ipaddr cache entry
* expiration events.
*/
-static void ipaddr_expire_process(void)
+static void ipaddr_expire_process_main(void)
{
struct ipaddr_expire_process_iterator_udata data;
setproctitle("%s Cache service process", CONF.PROCTITLE);
#endif /* __GLIBC__ */
- /* Reopen lock files as needed for flock(2) */
- (void) shlocks_init(CONF.LOCK_PATH, locks, SHLOCK_MAX, FALSE);
-
/* Set initial timeout to maximum allowed */
data.soonest = CONF.CONNECTION_PERIOD;
for (;;) {
/* NOTREACHED */
}
-
/* Internally used by the cache events expiration process */
static bool ipaddr_expire_process_iterator(hashnode_t *hnod, void *udata)
{