/* cygserver.cc Written by Egor Duda This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #ifdef __OUTSIDE_CYGWIN__ #include "woutsup.h" #include #include #include #include #include #include #include #include #include #include #include "cygwin_version.h" #include "cygserver.h" #include "process.h" #include "transport.h" #include "cygserver_ipc.h" #include "cygserver_msg.h" #include "cygserver_sem.h" #define DEF_CONFIG_FILE "" SYSCONFDIR "/cygserver.conf" #define SERVER_VERSION "1.20" GENERIC_MAPPING access_mapping; static bool setup_privileges () { BOOL rc, ret_val; HANDLE hToken = NULL; TOKEN_PRIVILEGES sPrivileges; rc = OpenProcessToken (GetCurrentProcess () , TOKEN_ALL_ACCESS , &hToken) ; if (!rc) { debug ("error opening process token (err %u)", GetLastError ()); return false; } rc = LookupPrivilegeValue (NULL, SE_DEBUG_NAME, &sPrivileges.Privileges[0].Luid); if (!rc) { debug ("error getting privilege luid (err %u)", GetLastError ()); ret_val = false; goto out; } sPrivileges.PrivilegeCount = 1 ; sPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED ; rc = AdjustTokenPrivileges (hToken, FALSE, &sPrivileges, 0, NULL, NULL) ; if (!rc) { debug ("error adjusting privilege level. (err %u)", GetLastError ()); ret_val = false; goto out; } access_mapping.GenericRead = FILE_READ_DATA; access_mapping.GenericWrite = FILE_WRITE_DATA; access_mapping.GenericExecute = 0; access_mapping.GenericAll = FILE_READ_DATA | FILE_WRITE_DATA; ret_val = true; out: CloseHandle (hToken); return ret_val; } int check_and_dup_handle (HANDLE from_process, HANDLE to_process, HANDLE from_process_token, DWORD access, HANDLE from_handle, HANDLE *to_handle_ptr, BOOL bInheritHandle = FALSE) { HANDLE local_handle = NULL; int ret_val = EACCES; char sd_buf [1024]; PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR) &sd_buf; DWORD bytes_needed; PRIVILEGE_SET ps; DWORD ps_len = sizeof (ps); BOOL status; if (from_process != GetCurrentProcess ()) { if (!DuplicateHandle (from_process, from_handle, GetCurrentProcess (), &local_handle, 0, bInheritHandle, DUPLICATE_SAME_ACCESS)) { log (LOG_ERR, "error getting handle(%p) to server (err %u)", from_handle, GetLastError ()); goto out; } } else local_handle = from_handle; if (!GetKernelObjectSecurity (local_handle, (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION), sd, sizeof (sd_buf), &bytes_needed)) { log (LOG_ERR, "error getting handle SD (err %u)", GetLastError ()); goto out; } MapGenericMask (&access, &access_mapping); if (!AccessCheck (sd, from_process_token, access, &access_mapping, &ps, &ps_len, &access, &status)) { log (LOG_ERR, "error checking access rights (err %u)", GetLastError ()); goto out; } if (!status) { log (LOG_ERR, "access to object denied"); goto out; } if (!DuplicateHandle (from_process, from_handle, to_process, to_handle_ptr, access, bInheritHandle, 0)) { log (LOG_ERR, "error getting handle to client (err %u)", GetLastError ()); goto out; } debug ("Duplicated %p to %p", from_handle, *to_handle_ptr); ret_val = 0; out: if (local_handle && from_process != GetCurrentProcess ()) CloseHandle (local_handle); return (ret_val); } /* * client_request_attach_tty::serve () */ void client_request_attach_tty::serve (transport_layer_base *const conn, process_cache *) { assert (conn); assert (!error_code ()); if (msglen () != sizeof (req)) { log (LOG_ERR, "bad request body length: expecting %lu bytes, got %lu", sizeof (req), msglen ()); error_code (EINVAL); msglen (0); return; } msglen (0); // Until we fill in some fields. debug ("pid %d:(%p,%p) -> pid %d", req.master_pid, req.from_master, req.to_master, req.pid); debug ("opening process %d", req.master_pid); const HANDLE from_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.master_pid); if (!from_process_handle) { log (LOG_ERR, "error opening `from' process (err %u)", GetLastError ()); error_code (EACCES); return; } debug ("opening process %d", req.pid); const HANDLE to_process_handle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, req.pid); if (!to_process_handle) { log (LOG_ERR, "error opening `to' process (err %u)", GetLastError ()); CloseHandle (from_process_handle); error_code (EACCES); return; } debug ("Impersonating client"); if (!conn->impersonate_client ()) { CloseHandle (from_process_handle); CloseHandle (to_process_handle); error_code (EACCES); return; } HANDLE token_handle = NULL; debug ("about to open thread token"); const DWORD rc = OpenThreadToken (GetCurrentThread (), TOKEN_QUERY, TRUE, &token_handle); debug ("opened thread token, rc=%u", rc); if (!conn->revert_to_self ()) { CloseHandle (from_process_handle); CloseHandle (to_process_handle); error_code (EACCES); return; } if (!rc) { log (LOG_ERR, "error opening thread token (err %u)", GetLastError ()); CloseHandle (from_process_handle); CloseHandle (to_process_handle); error_code (EACCES); return; } // From this point on, a reply body is returned to the client. const HANDLE from_master = req.from_master; const HANDLE to_master = req.to_master; req.from_master = NULL; req.to_master = NULL; msglen (sizeof (req)); if (from_master) if (check_and_dup_handle (from_process_handle, to_process_handle, token_handle, GENERIC_READ, from_master, &req.from_master, TRUE) != 0) { log (LOG_ERR, "error duplicating from_master handle (err %u)", GetLastError ()); error_code (EACCES); } if (to_master) if (check_and_dup_handle (from_process_handle, to_process_handle, token_handle, GENERIC_WRITE, to_master, &req.to_master, TRUE) != 0) { log (LOG_ERR, "error duplicating to_master handle (err %u)", GetLastError ()); error_code (EACCES); } CloseHandle (from_process_handle); CloseHandle (to_process_handle); CloseHandle (token_handle); debug ("%u(%p, %p) -> %u(%p,%p)", req.master_pid, from_master, to_master, req.pid, req.from_master, req.to_master); return; } void client_request_get_version::serve (transport_layer_base *, process_cache *) { assert (!error_code ()); if (msglen ()) log (LOG_ERR, "unexpected request body ignored: %lu bytes", msglen ()); msglen (sizeof (version)); version.major = CYGWIN_SERVER_VERSION_MAJOR; version.api = CYGWIN_SERVER_VERSION_API; version.minor = CYGWIN_SERVER_VERSION_MINOR; version.patch = CYGWIN_SERVER_VERSION_PATCH; } class server_request : public queue_request { public: server_request (transport_layer_base *const conn, process_cache *const cache) : _conn (conn), _cache (cache) {} virtual ~server_request () { delete _conn; } virtual void process () { client_request::handle_request (_conn, _cache); } private: transport_layer_base *const _conn; process_cache *const _cache; }; class server_submission_loop : public queue_submission_loop { public: server_submission_loop (threaded_queue *const queue, transport_layer_base *const transport, process_cache *const cache) : queue_submission_loop (queue, false), _transport (transport), _cache (cache) { assert (_transport); assert (_cache); } private: transport_layer_base *const _transport; process_cache *const _cache; virtual void request_loop (); }; /* FIXME: this is a little ugly. What we really want is to wait on * two objects: one for the pipe/socket, and one for being told to * shutdown. Otherwise this will stay a problem (we won't actually * shutdown until the request _AFTER_ the shutdown request. And * sending ourselves a request is ugly */ void server_submission_loop::request_loop () { /* I'd like the accepting thread's priority to be above any "normal" * thread in the system to avoid overflowing the listen queue (for * sockets; similar issues exist for named pipes); but, for example, * a normal priority thread in a foregrounded process is boosted to * THREAD_PRIORITY_HIGHEST (AFAICT). Thus try to set the current * thread's priority to a level one above that. This fails on * win9x/ME so assume any failure in that call is due to that and * simply call again at one priority level lower. * FIXME: This looks weird and is an issue on NT, too. Per MSDN, * THREAD_PRIORITY_HIGHEST + 1 is only a valid priority level if * the priority class is set to REALTIME_PRIORITY_CLASS. */ if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST + 1)) if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST)) debug ("failed to raise accept thread priority (err %u)", GetLastError ()); while (_running) { bool recoverable = false; transport_layer_base *const conn = _transport->accept (&recoverable); if (!conn && !recoverable) { log (LOG_ERR, "fatal error on IPC transport: closing down"); return; } // EINTR probably implies a shutdown request; so back off for a // moment to let the main thread take control, otherwise the // server spins here receiving EINTR repeatedly since the signal // handler in the main thread doesn't get a chance to be called. if (!conn && errno == EINTR) { if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_NORMAL)) debug ("failed to reset thread priority (err %u)", GetLastError ()); Sleep (0); if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST + 1)) if (!SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_HIGHEST)) debug ("failed to raise thread priority (err %u)", GetLastError ()); } if (conn) _queue->add (new server_request (conn, _cache)); } } client_request_shutdown::client_request_shutdown () : client_request (CYGSERVER_REQUEST_SHUTDOWN) { } void client_request_shutdown::serve (transport_layer_base *, process_cache *) { assert (!error_code ()); if (msglen ()) log (LOG_ERR, "unexpected request body ignored: %lu bytes", msglen ()); /* FIXME: link upwards, and then this becomes a trivial method call to * only shutdown _this queue_ */ kill (getpid (), SIGINT); msglen (0); } static sig_atomic_t shutdown_server = false; static void handle_signal (const int signum) { /* any signal makes us die :} */ shutdown_server = true; } /* * print_usage () */ static void print_usage (const char *const pgm) { log (LOG_NOTICE, "Usage: %s [OPTIONS]\n" "\n" "Cygwin background service daemon\n" "\n" "Configuration option:\n" "\n" " -f, --config-file Use as config file. Default is\n" " " DEF_CONFIG_FILE "\n" "\n" "Performance options:\n" "\n" " -c, --cleanup-threads Number of cleanup threads to use.\n" " -p, --process-cache Size of process cache.\n" " -r, --request-threads Number of request threads to use.\n" "\n" "Logging options:\n" "\n" " -d, --debug Log debug messages to stderr.\n" " -e, --stderr Log to stderr (default if stderr is a tty).\n" " -E, --no-stderr Don't log to stderr (see -y, -Y options).\n" " -l, --log-level Verbosity of logging (1..7). Default: 6\n" " -y, --syslog Log to syslog (default if stderr is no tty).\n" " -Y, --no-syslog Don't log to syslog (See -e, -E options).\n" "\n" "Support options:\n" "\n" " -m, --no-sharedmem Don't start XSI Shared Memory support.\n" " -q, --no-msgqueues Don't start XSI Message Queue support.\n" " -s, --no-semaphores Don't start XSI Semaphore support.\n" "\n" "Miscellaneous:\n" "\n" " -S, --shutdown Shutdown the daemon.\n" " -h, --help Output usage information and exit.\n" " -V, --version Output version information and exit.\n" , pgm); } /* * print_version () */ static void print_version () { log (LOG_INFO, "cygserver (cygwin) %d.%d.%d\n" "Cygwin background service daemon\n" "Copyright (C) 2001 - %s Cygwin Authors\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.", CYGWIN_VERSION_DLL_MAJOR / 1000, CYGWIN_VERSION_DLL_MAJOR % 1000, CYGWIN_VERSION_DLL_MINOR, strrchr (__DATE__, ' ') + 1); } /* * main () */ int main (const int argc, char *argv[]) { const struct option longopts[] = { {"cleanup-threads", required_argument, NULL, 'c'}, {"debug", no_argument, NULL, 'd'}, {"stderr", no_argument, NULL, 'e'}, {"no-stderr", no_argument, NULL, 'E'}, {"config-file", required_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {"log-level", required_argument, NULL, 'l'}, {"no-sharedmem", no_argument, NULL, 'm'}, {"process-cache", required_argument, NULL, 'p'}, {"no-msgqueues", no_argument, NULL, 'q'}, {"request-threads", required_argument, NULL, 'r'}, {"no-semaphores", no_argument, NULL, 's'}, {"shutdown", no_argument, NULL, 'S'}, {"version", no_argument, NULL, 'V'}, {"syslog", no_argument, NULL, 'y'}, {"no-syslog", no_argument, NULL, 'Y'}, {0, no_argument, NULL, 0} }; const char opts[] = "c:deEf:hl:mp:qr:sSVyY"; int32_t cleanup_threads = 0; int32_t request_threads = 0; int32_t process_cache_size = 0; bool shutdown = false; const char *config_file = DEF_CONFIG_FILE; bool force_config_file = false; tun_bool_t option_log_stderr = TUN_UNDEF; tun_bool_t option_log_syslog = TUN_UNDEF; char *c = NULL; /* Check if we have a terminal. If so, default to stderr logging, otherwise default to syslog logging. This must be done early to allow default logging already in option processing state. */ openlog ("cygserver", LOG_PID, LOG_KERN); if (isatty (2)) log_stderr = TUN_TRUE; else log_syslog = TUN_TRUE; int opt; securityinit (); opterr = 0; while ((opt = getopt_long (argc, argv, opts, longopts, NULL)) != EOF) switch (opt) { case 'c': c = NULL; cleanup_threads = strtol (optarg, &c, 10); if (cleanup_threads <= 0 || cleanup_threads > 32 || (c && *c)) panic ("Number of cleanup threads must be between 1 and 32"); break; case 'd': log_debug = TUN_TRUE; break; case 'e': option_log_stderr = TUN_TRUE; break; case 'E': option_log_stderr = TUN_FALSE; break; case 'f': config_file = optarg; force_config_file = true; break; case 'h': print_usage (getprogname ()); return 0; case 'l': c = NULL; log_level = strtoul (optarg, &c, 10); if (!log_level || log_level > 7 || (c && *c)) panic ("Log level must be between 1 and 7"); break; case 'm': support_sharedmem = TUN_FALSE; break; case 'p': c = NULL; process_cache_size = strtol (optarg, &c, 10); if (process_cache_size <= 0 || process_cache_size > 310 || (c && *c)) panic ("Size of process cache must be between 1 and 310"); break; case 'q': support_msgqueues = TUN_FALSE; break; case 'r': c = NULL; request_threads = strtol (optarg, &c, 10); if (request_threads <= 0 || request_threads > 310 || (c && *c)) panic ("Number of request threads must be between 1 and 310"); break; case 's': support_semaphores = TUN_FALSE; break; case 'S': shutdown = true; break; case 'V': print_version (); return 0; case 'y': option_log_syslog = TUN_TRUE; break; case 'Y': option_log_syslog = TUN_FALSE; break; case '?': panic ("unknown option -- %c\n" "Try `%s --help' for more information.", optopt, getprogname ()); } if (optind != argc) panic ("Too many arguments"); if (shutdown) { /* Setting `cygserver_running' stops the request code making a * version request, which is not much to the point. */ cygserver_running = CYGSERVER_OK; client_request_shutdown req; if (req.make_request () == -1 || req.error_code ()) panic("Shutdown request failed: %s", strerror (req.error_code ())); // FIXME: It would be nice to wait here for the daemon to exit. return 0; } SIGHANDLE (SIGHUP); SIGHANDLE (SIGINT); SIGHANDLE (SIGTERM); tunable_param_init (config_file, force_config_file); loginit (option_log_stderr, option_log_syslog); log (LOG_INFO, "daemon starting up"); if (!cleanup_threads) TUNABLE_INT_FETCH ("kern.srv.cleanup_threads", &cleanup_threads); if (!cleanup_threads) cleanup_threads = 2; if (!request_threads) TUNABLE_INT_FETCH ("kern.srv.request_threads", &request_threads); if (!request_threads) request_threads = 10; if (!process_cache_size) TUNABLE_INT_FETCH ("kern.srv.process_cache_size", &process_cache_size); if (!process_cache_size) process_cache_size = 62; if (support_sharedmem == TUN_UNDEF) TUNABLE_BOOL_FETCH ("kern.srv.sharedmem", &support_sharedmem); if (support_sharedmem == TUN_UNDEF) support_sharedmem = TUN_TRUE; if (support_msgqueues == TUN_UNDEF) TUNABLE_BOOL_FETCH ("kern.srv.msgqueues", &support_msgqueues); if (support_msgqueues == TUN_UNDEF) support_msgqueues = TUN_TRUE; if (support_semaphores == TUN_UNDEF) TUNABLE_BOOL_FETCH ("kern.srv.semaphores", &support_semaphores); if (support_semaphores == TUN_UNDEF) support_semaphores = TUN_TRUE; if (!setup_privileges ()) panic ("Setting process privileges failed."); ipcinit (); /*XXXXX*/ threaded_queue request_queue (request_threads); transport_layer_base *const transport = create_server_transport (); assert (transport); if (transport->listen () == -1) return 1; process_cache cache (process_cache_size, cleanup_threads); server_submission_loop submission_loop (&request_queue, transport, &cache); request_queue.add_submission_loop (&submission_loop); cache.start (); request_queue.start (); log (LOG_NOTICE, "Initialization complete. Waiting for requests."); /* TODO: wait on multiple objects - the thread handle for each * request loop + all the process handles. This should be done by * querying the request_queue and the process cache for all their * handles, and then waiting for (say) 30 seconds. after that we * recreate the list of handles to wait on, and wait again. the * point of all this abstraction is that we can trivially server * both sockets and pipes simply by making a new transport, and then * calling request_queue.process_requests (transport2); */ /* WaitForMultipleObjects abort && request_queue && process_queue && signal -- if signal event then retrigger it */ while (!shutdown_server && request_queue.running () && cache.running ()) { pause (); if (ipcunload ()) { shutdown_server = false; log (LOG_WARNING, "Shutdown request received but ignored. " "Dependent processes still running."); } } log (LOG_INFO, "Shutdown request received - new requests will be denied"); request_queue.stop (); log (LOG_INFO, "All pending requests processed"); delete transport; log (LOG_INFO, "No longer accepting requests - cygwin will operate in daemonless mode"); cache.stop (); log (LOG_INFO, "All outstanding process-cache activities completed"); log (LOG_NOTICE, "Shutdown finished."); return 0; } #endif /* __OUTSIDE_CYGWIN__ */