From 20fe14d5c6e24b874c2ecbba7d3900d772406e69 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Thu, 31 May 2007 12:39:44 +0000 Subject: Add jabberd git-svn-id: file:///svnroot/gsc/trunk@246 d2de0444-eb31-0410-8365-af798a554d48 --- jabberd/main.c | 690 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 690 insertions(+) create mode 100644 jabberd/main.c (limited to 'jabberd/main.c') diff --git a/jabberd/main.c b/jabberd/main.c new file mode 100644 index 0000000..f195f72 --- /dev/null +++ b/jabberd/main.c @@ -0,0 +1,690 @@ +/* jabberd - a dispatcher program for jabber 2.x + Copyright (C) 2007 Sergey Poznyakoff + + 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., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. */ + +#include "jabberd.h" + +char *progname; +char *config_file = SYSCONFDIR "/jabberd.cfg"; +int debug_level = 0; +int foreground = 0; +int log_to_stderr = 0; +char *user = "jabber"; +char *syslog_tag = "jabberd"; +int log_facility = LOG_LOCAL7; +int x_argc; +char **x_argv; +char *pidfile; + +void +syslog_printer (int prio, const char *fmt, va_list ap) +{ +#ifdef USE_SYSLOG_ASYNC + vsyslog_async (prio, fmt, ap); +#elif HAVE_VSYSLOG + vsyslog (prio, fmt, ap); +#else + char buf[128]; + vsnprintf (buf, sizeof buf, fmt, ap); + syslog (prio, "%s", buf); +#endif +} + +void +stderr_printer (int prio, const char *fmt, va_list ap) +{ + char *p = NULL; + fprintf (stderr, "%s: ", progname); + + switch (prio) { + case LOG_EMERG: + p = "EMERG"; + break; + + case LOG_ALERT: + p = "ALERT"; + break; + + case LOG_CRIT: + p = "CRIT"; + break; + + case LOG_ERR: + p = "ERR"; + break; + + case LOG_WARNING: + p = "WARN"; + break; + + case LOG_NOTICE: + p = "NOTICE"; + break; + + case LOG_INFO: + p = "INFO"; + break; + + case LOG_DEBUG: + p = "DEBUG"; + break; + } + + if (p) + fprintf (stderr, "[%s] ", p); + + vfprintf (stderr, fmt, ap); + fputc ('\n', stderr); +} + + +static void (*log_printer) (int prio, const char *fmt, va_list ap) = + stderr_printer; + +void +logmsg(int prio, char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + log_printer (prio, fmt, ap); + va_end (ap); +} + +void * +emalloc (size_t size) +{ + char *p = malloc (size); + if (!p) + { + logmsg (LOG_EMERG, "%s", strerror (errno)); + exit (1); + } + return p; +} + +void +version () +{ + printf ("jabberd (%s)\n", PACKAGE_STRING); + printf ("Copyright (C) 2007 Sergey Poznyakoff\n"); + printf ("License GPLv2+: GNU GPL version 2 or later \n"); + printf ("This is free software: you are free to change and redistribute it.\n"); + printf ("There is NO WARRANTY, to the extent permitted by law.\n"); +} + +void +usage () +{ + printf ("usage: jabberd [-Dfehv][-c config][-p pidfile]\n"); + printf ("jabberd -- A dispatcher program for jabber 2.x\n"); + printf ("\n"); + printf ("Options:\n"); + printf (" -c FILE use this configuration file\n"); + printf (" -D increase debugging level\n"); + printf (" -f run in foreground mode (implies -e)\n"); + printf (" -e use standard error for diagnostics output\n"); + printf (" -h display this help list\n"); + printf (" -p FILE write master process ID to FILE\n"); + printf (" -v display program version\n"); + printf ("\n"); + printf ("Report bugs to <%s>\n", PACKAGE_BUGREPORT); +} + + +/* Configuration file handling */ + +typedef void (*cfg_fun) (unsigned line, char *kw, char *val); + +void +cfg_syslog_tag (unsigned line, char *kw, char *val) +{ + syslog_tag = strdup (val); +} + +void +cfg_syslog_facility (unsigned line, char *kw, char *val) +{ + int i; + static struct { + char *name; + int facility; + } syslog_kw[] = { + { "USER", LOG_USER }, + { "DAEMON", LOG_DAEMON }, + { "AUTH", LOG_AUTH }, + { "LOCAL0", LOG_LOCAL0 }, + { "LOCAL1", LOG_LOCAL1 }, + { "LOCAL2", LOG_LOCAL2 }, + { "LOCAL3", LOG_LOCAL3 }, + { "LOCAL4", LOG_LOCAL4 }, + { "LOCAL5", LOG_LOCAL5 }, + { "LOCAL6", LOG_LOCAL6 }, + { "LOCAL7", LOG_LOCAL7 }, + { "MAIL", LOG_MAIL } + }; + + if (strncasecmp (val, "LOG_", 4) == 0) + val += 4; + + for (i = 0; i < sizeof (syslog_kw) / sizeof (syslog_kw[0]); i++) + if (strcasecmp (syslog_kw[i].name, val) == 0) + { + log_facility = syslog_kw[i].facility; + return; + } + logmsg (LOG_ERR, "%s:%u: Unknown facility `%s'", config_file, line, val); +} + +void +cfg_user (unsigned line, char *kw, char *val) +{ + struct passwd *pwd = getpwnam (val); + if (!pwd) + logmsg (LOG_ERR, "%s:%u: no such user `%s'", config_file, line, val); + else if (pwd->pw_uid == 0) + logmsg (LOG_ERR, "%s:%u: user `%s' has zero UID", config_file, line, val); + else + user = strdup (val); +} + +struct group_list +{ + struct group_list *next; + gid_t gid; +}; + +static struct group_list *group_list; + +void +cfg_group (unsigned line, char *kw, char *val) +{ + struct group *group = getgrnam (val); + if (group) + { + struct group_list *p = emalloc (sizeof *p); + p->gid = group->gr_gid; + p->next = group_list; + group_list = p; + } + else + logmsg(LOG_ERR, "%s:%u: unknown group `%s'", config_file, + line, val); +} + +void +cfg_pidfile (unsigned line, char *kw, char *val) +{ + pidfile = strdup (val); +} + +void +cfg_prog (unsigned line, char *kw, char *val) +{ + char *prog = val; + char *p = val; + + for (; *p && !isspace (*p); p++) + ; + + if (*p) + { + *p++ = 0; + for (; *p && isspace (*p); p++) + ; + + val = p; + } + else + val = 0; + + register_prog (prog, val); +} + +struct kw_handler +{ + char *kw; + cfg_fun handler; +}; + +struct kw_handler kw_handler[] = { + { "syslog-tag", cfg_syslog_tag }, + { "syslog-facility", cfg_syslog_facility }, + { "user", cfg_user }, + { "group", cfg_group }, + { "pidfile", cfg_pidfile }, + { "prog", cfg_prog }, + { NULL } +}; + +cfg_fun +find_cfg_fun (char *kw) +{ + struct kw_handler *p; + for (p = kw_handler; p->kw; p++) + if (strcmp (p->kw, kw) == 0) + return p->handler; + return NULL; +} + +void +parse_config () +{ + FILE *fp = fopen (config_file, "r"); + char *buf = NULL; + size_t size = 0; + unsigned line = 0; + + if (!fp) + { + logmsg (LOG_EMERG, "cannot open config file `%s': %s", + config_file, strerror (errno)); + exit (1); + } + + while (getline (&buf, &size, fp) > 0) + { + size_t len; + char *p, *kw, *val = NULL; + cfg_fun fun; + + line++; + for (p = buf; *p && isspace (*p); p++) + ; + if (*p == '#' || *p == 0) + continue; + + len = strlen (p); + if (p[len-1] == '\n') + p[--len] = 0; + + for (;len > 0 && isspace (p[len-1]); len--) + ; + + if (len > 0) + p[len] = 0; + else + continue; + + kw = p; + for (; *p && !isspace (*p); p++) + ; + + if (*p) + { + *p++ = 0; + for (; *p && isspace (*p); p++) + ; + + val = p; + } + + fun = find_cfg_fun (kw); + if (fun) + fun(line, kw, val); + else + { + logmsg(LOG_WARNING, "%s:%u: legacy line", config_file, line); + register_prog (kw, val); + } + } + free (buf); + fclose (fp); +} + + +void +pidfile_write () +{ + if (pidfile) + { + FILE *fp = fopen (pidfile, "w"); + if (!fp) + { + logmsg (LOG_CRIT, "cannot open pidfile `%s' for writing: %s", + pidfile, strerror (errno)); + return; + } + fprintf (fp, "%lu\n", (unsigned long) getpid ()); + fclose (fp); + } +} + +void +pidfile_remove () +{ + if (pidfile && unlink (pidfile)) + logmsg (LOG_ERR, "cannot remove pidfile `%s': %s", + pidfile, strerror (errno)); +} + + +/* Switch to the given UID/GID */ +int +switch_to_privs (uid_t uid, gid_t gid) +{ + int rc = 0; + gid_t *emptygidset; + size_t size = 1, j = 1; + struct group_list *gp; + + if (uid == 0) + { + logmsg(LOG_EMERG, "refusing to run as root"); + return 1; + } + + /* Create a list of supplementary groups */ + for (gp = group_list; gp; gp = gp->next) + size++; + emptygidset = emalloc (size * sizeof emptygidset[0]); + emptygidset[0] = gid ? gid : getegid (); + + for (gp = group_list; gp; gp = gp->next) + emptygidset[j++] = gp->gid; + + /* Reset group permissions */ + if (geteuid () == 0 && setgroups (j, emptygidset)) + { + logmsg (LOG_ERR, "setgroups(1, %lu) failed: %s", + (unsigned long) emptygidset[0], + strerror (errno)); + rc = 1; + } + free (emptygidset); + + /* Switch to the user's gid. On some OSes the effective gid must + be reset first */ + +#if defined(HAVE_SETEGID) + if ((rc = setegid (gid)) < 0) + logmsg (LOG_ERR, "setegid(%lu) failed: %s", + (unsigned long) gid, strerror (errno)); +#elif defined(HAVE_SETREGID) + if ((rc = setregid (gid, gid)) < 0) + logmsg (LOG_ERR, "setregid(%lu,%lu) failed: %s", + (unsigned long) gid, (unsigned long) gid, + strerror (errno)); +#elif defined(HAVE_SETRESGID) + if ((rc = setresgid (gid, gid, gid)) < 0) + logmsg (LOG_ERR, "setresgid(%lu,%lu,%lu) failed: %s", + (unsigned long) gid, + (unsigned long) gid, + (unsigned long) gid, + strerror (errno)); +#endif + + if (rc == 0 && gid != 0) + { + if ((rc = setgid (gid)) < 0 && getegid () != gid) + logmsg (LOG_ERR, "setgid(%lu) failed: %s", + (unsigned long) gid, strerror (errno)); + if (rc == 0 && getegid () != gid) + { + logmsg (LOG_ERR, "Cannot set effective gid to %lu", + (unsigned long) gid); + rc = 1; + } + } + + /* Now reset uid */ + if (rc == 0 && uid != 0) + { + uid_t euid; + + if (setuid (uid) + || geteuid () != uid + || (getuid () != uid + && (geteuid () == 0 || getuid () == 0))) + { + +#if defined(HAVE_SETREUID) + if (geteuid () != uid) + { + if (setreuid (uid, -1) < 0) + { + logmsg (LOG_ERR, "setreuid(%lu,-1) failed", + (unsigned long) uid, + strerror (errno)); + rc = 1; + } + if (setuid (uid) < 0) + { + logmsg (LOG_ERR, "second setuid(%lu) failed", + (unsigned long) uid, + strerror (errno)); + rc = 1; + } + } + else +#endif + { + logmsg (LOG_ERR, "setuid(%lu) failed", + (unsigned long) uid, + strerror (errno)); + rc = 1; + } + } + + euid = geteuid (); + if (uid != 0 && setuid (0) == 0) + { + logmsg (LOG_ERR, "seteuid(0) succeeded when it should not"); + rc = 1; + } + else if (uid != euid && setuid (euid) == 0) + { + logmsg (LOG_ERR, "Cannot drop non-root setuid privileges"); + rc = 1; + } + + } + + return rc; +} + +void +priv_setup () +{ + if (getuid () == 0) + { + if (!user) + { + logmsg (LOG_ERR, "non-privileged user not given"); + exit (1); + } + else + { + struct passwd *pw = getpwnam (user); + if (!pw) + { + logmsg (LOG_ERR, "No such user: %s", user); + exit (1); + } + if (pw && switch_to_privs (pw->pw_uid, pw->pw_gid)) + exit (1); + } + } +} + + +#define ACTION_CONT 0 +#define ACTION_STOP 1 +#define ACTION_RESTART 2 + +int action = ACTION_CONT; +int children_cleanup = 0; +int got_alarm = 0; + +RETSIGTYPE +sig_handler(int sig) +{ + switch (sig) + { + case SIGCHLD: + children_cleanup = 1; + break; + + case SIGTERM: + case SIGINT: + case SIGQUIT: + action = ACTION_STOP; + logmsg (LOG_NOTICE, "received signal %d", sig); + break; + + case SIGHUP: + logmsg (LOG_NOTICE, "received signal %d", sig); + if (x_argc == 0) + { + logmsg (LOG_NOTICE, + "SIGHUP: not started with an absolute pathname"); + action = ACTION_STOP; + } + else + action = ACTION_RESTART; + break; + + case SIGALRM: + got_alarm = 1; + } + signal (sig, sig_handler); +} + +void +signal_setup (RETSIGTYPE (*sf)(int)) +{ + signal (SIGCHLD, sf); + signal (SIGTERM, sf); + signal (SIGQUIT, sf); + signal (SIGINT, sf); + signal (SIGHUP, sf); + signal (SIGALRM, sf); +} + + +int +main(int argc, char **argv) +{ + int c; + + progname = argv[0]; + while ((c = getopt(argc, argv, "c:Dfehp:v")) != EOF) + { + switch (c) + { + case 'c': + config_file = optarg; + break; + + case 'D': + debug_level++; + break; + + case 'f': + foreground = 1; + + case 'e': + log_to_stderr = 1; + break; + + case 'h': + usage (); + exit (0); + + case 'p': + pidfile = optarg; + break; + + case 'v': + version (); + exit (0); + + default: + logmsg (LOG_CRIT, "unknown option: %c", c); + exit (1); + } + } + + if (argc != optind) + { + logmsg (LOG_CRIT, "extra command line arguments"); + exit (1); + } + + parse_config(); + + if (!log_to_stderr) + { + openlog (syslog_tag, LOG_PID, log_facility); + log_printer = syslog_printer; + } + + if (argv[0][0] == '/') + { + x_argc = argc; + x_argv = argv; + } + else + logmsg (LOG_NOTICE, "jabberd not started as an absolute pathname; SIGHUP will not work"); + + priv_setup (); + umask (037); + logmsg (LOG_NOTICE, "jabberd started"); + if (!foreground && daemon (0, 0) == -1) + { + logmsg (LOG_CRIT, "cannot become a daemon: %s", + strerror (errno)); + exit (1); + } + pidfile_write (); + + signal_setup (sig_handler); + + progman_start (); + + while (action == ACTION_CONT) + { + pause (); + if (children_cleanup) + { + children_cleanup = 0; + progman_cleanup (0); + } + if (got_alarm) + { + got_alarm = 0; + progman_wake_disabled (); + } + } + progman_stop (); + + pidfile_remove (); + if (action == ACTION_RESTART) + { + int i; + + for (i = getmaxfd (); i > 0; i--) + close (i); + + signal_setup (SIG_DFL); + + execv (x_argv[0], x_argv); + openlog (syslog_tag, LOG_PID, log_facility); + logmsg (LOG_EMERG, "cannot restart: %s", strerror (errno)); + } + logmsg (LOG_NOTICE, "jabberd finished"); + exit (0); +} -- cgit v1.2.1