/* tinyproxy - A fast light-weight HTTP proxy * Copyright (C) 1998 Steven Young * Copyright (C) 1998-2002 Robert James Kaes * Copyright (C) 2000 Chris Lightfoot * * 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. */ /* The initialize routine. Basically sets up all the initial stuff (logfile, * listening socket, config options, etc.) and then sits there and loops * over the new connections until the daemon is closed. Also has additional * functions to handle the "user friendly" aspects of a program (usage, * stats, etc.) Like any good program, most of the work is actually done * elsewhere. */ #include "main.h" #include "anonymous.h" #include "buffer.h" #include "conffile.h" #include "daemon.h" #include "heap.h" #include "filter.h" #include "child.h" #include "log.h" #include "reqs.h" #include "sock.h" #include "stats.h" #include "utils.h" RETSIGTYPE takesig (int sig); /* * Global Structures */ struct config_s config; unsigned int received_sighup = FALSE; /* boolean */ unsigned int processed_config_file = FALSE; /* boolean */ /* * Handle a signal */ RETSIGTYPE takesig (int sig) { pid_t pid; int status; switch (sig) { case SIGHUP: received_sighup = TRUE; break; case SIGTERM: config.quit = TRUE; break; case SIGCHLD: while ((pid = waitpid (-1, &status, WNOHANG)) > 0) ; break; } return; } /* * Display the version information for the user. */ static void display_version (void) { printf ("%s %s (%s)\n", PACKAGE, VERSION, TARGET_SYSTEM); } /* * Display the copyright and license for this program. */ static void display_license (void) { display_version (); printf ("\ Copyright 1998 Steven Young (sdyoung@well.com)\n\ Copyright 1998-2002 Robert James Kaes (rjkaes@users.sourceforge.net)\n\ Copyright 1999 George Talusan (gstalusan@uwaterloo.ca)\n\ Copyright 2000 Chris Lightfoot (chris@ex-parrot.com)\n\ \n\ This program is free software; you can redistribute it and/or modify\n\ it under the terms of the GNU General Public License as published by\n\ the Free Software Foundation; either version 2, or (at your option)\n\ any later version.\n\ \n\ This program is distributed in the hope that it will be useful,\n\ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\ GNU General Public License for more details.\n\ \n\ You should have received a copy of the GNU General Public License\n\ along with this program; if not, write to the Free Software\n\ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.\n"); } /* * Display usage to the user. */ static void display_usage (void) { printf ("Usage: %s [options]\n", PACKAGE); printf ("\ Options:\n\ -d Operate in DEBUG mode.\n\ -c FILE Use an alternate configuration file.\n\ -h Display this usage information.\n\ -l Display the license.\n\ -v Display the version number.\n"); /* Display the modes compiled into tinyproxy */ printf ("\nFeatures compiled in:\n"); #ifdef XTINYPROXY_ENABLE printf (" XTinyproxy header\n"); #endif /* XTINYPROXY */ #ifdef FILTER_ENABLE printf (" Filtering\n"); #endif /* FILTER_ENABLE */ #ifndef NDEBUG printf (" Debugging code\n"); #endif /* NDEBUG */ #ifdef TRANSPARENT_PROXY printf (" Transparent proxy support\n"); #endif /* TRANSPARENT_PROXY */ #ifdef REVERSE_SUPPORT printf (" Reverse proxy support\n"); #endif /* REVERSE_SUPPORT */ } static int get_id (char *str) { char *tstr; if (str == NULL) return -1; tstr = str; while (*tstr != 0) { if (!isdigit (*tstr)) return -1; tstr++; } return atoi (str); } /** * process_cmdline: * @argc: argc as passed to main() * @argv: argv as passed to main() * * This function parses command line arguments. **/ static void process_cmdline (int argc, char **argv) { int opt; while ((opt = getopt (argc, argv, "c:vldh")) != EOF) { switch (opt) { case 'v': display_version (); exit (EX_OK); case 'l': display_license (); exit (EX_OK); case 'd': config.godaemon = FALSE; break; case 'c': config.config_file = safestrdup (optarg); if (!config.config_file) { fprintf (stderr, "%s: Could not allocate memory.\n", argv[0]); exit (EX_SOFTWARE); } break; case 'h': display_usage (); exit (EX_OK); default: display_usage (); exit (EX_USAGE); } } } /** * change_user: * @program: The name of the program. Pass argv[0] here. * * This function tries to change UID and GID to the ones specified in * the config file. This function is typically called during * initialization when the effective user is root. **/ static void change_user (const char *program) { if (config.group && strlen (config.group) > 0) { int gid = get_id (config.group); if (gid < 0) { struct group *thisgroup = getgrnam (config.group); if (!thisgroup) { fprintf (stderr, "%s: Unable to find group \"%s\".\n", program, config.group); exit (EX_NOUSER); } gid = thisgroup->gr_gid; } if (setgid (gid) < 0) { fprintf (stderr, "%s: Unable to change to group \"%s\".\n", program, config.group); exit (EX_NOPERM); } log_message (LOG_INFO, "Now running as group \"%s\".", config.group); } if (config.user && strlen (config.user) > 0) { int uid = get_id (config.user); if (uid < 0) { struct passwd *thisuser = getpwnam (config.user); if (!thisuser) { fprintf (stderr, "%s: Unable to find user \"%s\".\n", program, config.user); exit (EX_NOUSER); } uid = thisuser->pw_uid; } if (setuid (uid) < 0) { fprintf (stderr, "%s: Unable to change to user \"%s\".\n", program, config.user); exit (EX_NOPERM); } log_message (LOG_INFO, "Now running as user \"%s\".", config.user); } } int main (int argc, char **argv) { FILE *config_file; /* Only allow u+rw bits. This may be required for some versions * of glibc so that mkstemp() doesn't make us vulnerable. */ umask (0177); config.config_file = DEFAULT_CONF_FILE; config.godaemon = TRUE; process_cmdline (argc, argv); log_message (LOG_INFO, "Initializing " PACKAGE " ..."); /* * Make sure the HTML error pages array is NULL to begin with. * (FIXME: Should have a better API for all this) */ config.errorpages = NULL; /* * Read in the settings from the config file. */ config_file = fopen (config.config_file, "r"); if (!config_file) { fprintf (stderr, "%s: Could not open configuration file \"%s\".\n", argv[0], config.config_file); exit (EX_SOFTWARE); } if (config_compile () || config_parse (&config, config_file)) { fprintf (stderr, "Unable to parse configuration file. Not starting.\n"); exit (EX_SOFTWARE); } fclose (config_file); /* * Write to a user supplied log file if it's defined. This * will override using the syslog even if syslog is defined. */ if (config.logf_name) { if (open_log_file (config.logf_name) < 0) { fprintf (stderr, "%s: Could not create log file.\n", argv[0]); exit (EX_SOFTWARE); } config.syslog = FALSE; /* disable syslog */ } else if (config.syslog) { if (config.godaemon == TRUE) openlog ("tinyproxy", LOG_PID, LOG_DAEMON); else openlog ("tinyproxy", LOG_PID, LOG_USER); } else { fprintf (stderr, "%s: Either define a logfile or enable syslog logging.\n", argv[0]); exit (EX_SOFTWARE); } processed_config_file = TRUE; send_stored_logs (); /* * Set the default values if they were not set in the config file. */ if (config.port == 0) { fprintf (stderr, "%s: You MUST set a Port in the configuration file.\n", argv[0]); exit (EX_SOFTWARE); } if (!config.stathost) { log_message (LOG_INFO, "Setting stathost to \"%s\".", DEFAULT_STATHOST); config.stathost = DEFAULT_STATHOST; } if (!config.user) { log_message (LOG_WARNING, "You SHOULD set a UserName in the configuration file. " "Using current user instead."); } if (config.idletimeout == 0) { log_message (LOG_WARNING, "Invalid idle time setting. Only values greater than zero " "allowed; therefore setting idle timeout to %u seconds.", MAX_IDLE_TIME); config.idletimeout = MAX_IDLE_TIME; } init_stats (); /* * If ANONYMOUS is turned on, make sure that Content-Length is * in the list of allowed headers, since it is required in a * HTTP/1.0 request. Also add the Content-Type header since it goes * hand in hand with Content-Length. * - rjkaes */ if (is_anonymous_enabled ()) { anonymous_insert ("Content-Length"); anonymous_insert ("Content-Type"); } if (config.godaemon == TRUE) makedaemon (); if (config.pidpath) { if (pidfile_create (config.pidpath) < 0) { fprintf (stderr, "%s: Could not create PID file.\n", argv[0]); exit (EX_OSERR); } } if (set_signal_handler (SIGPIPE, SIG_IGN) == SIG_ERR) { fprintf (stderr, "%s: Could not set the \"SIGPIPE\" signal.\n", argv[0]); exit (EX_OSERR); } #ifdef FILTER_ENABLE if (config.filter) filter_init (); #endif /* FILTER_ENABLE */ /* * Start listening on the selected port. */ if (child_listening_sock (config.port) < 0) { fprintf (stderr, "%s: Could not create listening socket.\n", argv[0]); exit (EX_OSERR); } /* Switch to a different user if we're running as root */ if (geteuid () == 0) change_user (argv[0]); else log_message (LOG_WARNING, "Not running as root, so not changing UID/GID."); if (child_pool_create () < 0) { fprintf (stderr, "%s: Could not create the pool of children.\n", argv[0]); exit (EX_SOFTWARE); } /* * These signals are only for the parent process. */ log_message (LOG_INFO, "Setting the various signals."); if (set_signal_handler (SIGCHLD, takesig) == SIG_ERR) { fprintf (stderr, "%s: Could not set the \"SIGCHLD\" signal.\n", argv[0]); exit (EX_OSERR); } if (set_signal_handler (SIGTERM, takesig) == SIG_ERR) { fprintf (stderr, "%s: Could not set the \"SIGTERM\" signal.\n", argv[0]); exit (EX_OSERR); } if (set_signal_handler (SIGHUP, takesig) == SIG_ERR) { fprintf (stderr, "%s: Could not set the \"SIGHUP\" signal.\n", argv[0]); exit (EX_OSERR); } /* * Start the main loop. */ log_message (LOG_INFO, "Starting main loop. Accepting connections."); child_main_loop (); log_message (LOG_INFO, "Shutting down."); child_kill_children (); child_close_sock (); /* * Remove the PID file. */ if (unlink (config.pidpath) < 0) { log_message (LOG_WARNING, "Could not remove PID file \"%s\": %s.", config.pidpath, strerror (errno)); } #ifdef FILTER_ENABLE if (config.filter) filter_destroy (); #endif /* FILTER_ENABLE */ if (config.syslog) closelog (); else close_log_file (); return EXIT_SUCCESS; }