1/* Copyright (c) 2008-2011 Apple Inc. 2 * 3 * Permission is hereby granted, free of charge, to any person 4 * obtaining a copy of this software and associated documentation files 5 * (the "Software"), to deal in the Software without restriction, 6 * including without limitation the rights to use, copy, modify, merge, 7 * publish, distribute, sublicense, and/or sell copies of the Software, 8 * and to permit persons to whom the Software is furnished to do so, 9 * subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be 12 * included in all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 * NONINFRINGEMENT. IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT 18 * HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 * DEALINGS IN THE SOFTWARE. 22 * 23 * Except as contained in this notice, the name(s) of the above 24 * copyright holders shall not be used in advertising or otherwise to 25 * promote the sale, use or other dealings in this Software without 26 * prior written authorization. 27 */ 28 29#ifdef HAVE_CONFIG_H 30# include "config.h" 31#endif 32 33#include <mach/mach.h> 34#include <mach/mach_error.h> 35#include <servers/bootstrap.h> 36#include <unistd.h> 37#include <stdio.h> 38#include <sys/types.h> 39#include <sys/stat.h> 40#include <fts.h> 41#include <limits.h> 42#include <stdlib.h> 43#include <stdbool.h> 44#include <sys/time.h> 45#include <launch.h> 46#include <asl.h> 47#include <errno.h> 48 49#include "privileged_startx.h" 50#include "privileged_startxServer.h" 51 52union MaxMsgSize { 53 union __RequestUnion__privileged_startx_subsystem req; 54 union __ReplyUnion__privileged_startx_subsystem rep; 55}; 56 57#ifdef LAUNCH_JOBKEY_MACHSERVICES 58#include <pthread.h> 59static void* idle_thread(void* param __attribute__((unused))); 60 61/* globals to trigger idle exit */ 62#define DEFAULT_IDLE_TIMEOUT 60 /* 60 second timeout, then the server exits */ 63 64struct idle_globals { 65 mach_port_t mp; 66 long timeout; 67 struct timeval lastmsg; 68}; 69 70struct idle_globals idle_globals; 71#endif 72 73#ifndef SCRIPTDIR 74#define SCRIPTDIR="/usr/X11/lib/X11/xinit/privileged_startx.d" 75#endif 76 77/* Default script dir */ 78const char *script_dir = SCRIPTDIR; 79 80#ifndef LAUNCH_JOBKEY_MACHSERVICES 81static mach_port_t checkin_or_register(char *bname) { 82 kern_return_t kr; 83 mach_port_t mp; 84 85 /* If we're started by launchd or the old mach_init */ 86 kr = bootstrap_check_in(bootstrap_port, bname, &mp); 87 if (kr == KERN_SUCCESS) 88 return mp; 89 90 /* We probably were not started by launchd or the old mach_init */ 91 kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp); 92 if (kr != KERN_SUCCESS) { 93 asl_log(NULL, NULL, ASL_LEVEL_ERR, "mach_port_allocate(): %s", mach_error_string(kr)); 94 exit(EXIT_FAILURE); 95 } 96 97 kr = mach_port_insert_right(mach_task_self(), mp, mp, MACH_MSG_TYPE_MAKE_SEND); 98 if (kr != KERN_SUCCESS) { 99 asl_log(NULL, NULL, ASL_LEVEL_ERR, "mach_port_insert_right(): %s", mach_error_string(kr)); 100 exit(EXIT_FAILURE); 101 } 102 103 kr = bootstrap_register(bootstrap_port, bname, mp); 104 if (kr != KERN_SUCCESS) { 105 asl_log(NULL, NULL, ASL_LEVEL_ERR, "bootstrap_register(): %s", mach_error_string(kr)); 106 exit(EXIT_FAILURE); 107 } 108 109 return mp; 110} 111#endif 112 113int server_main(const char *dir) { 114 mach_msg_size_t mxmsgsz = sizeof(union MaxMsgSize) + MAX_TRAILER_SIZE; 115 mach_port_t mp; 116 kern_return_t kr; 117#ifdef LAUNCH_JOBKEY_MACHSERVICES 118 long idle_timeout = DEFAULT_IDLE_TIMEOUT; 119#endif 120 121 launch_data_t config = NULL, checkin = NULL, label = NULL; 122 const char *labelstr = BUNDLE_ID_PREFIX".privileged_startx"; 123 aslclient aslc; 124 125 checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN); 126 if (!checkin) { 127 asl_log(NULL, NULL, ASL_LEVEL_ERR, "unable to create launchd checkin string"); 128 exit(EXIT_FAILURE); 129 } 130 131 config = launch_msg(checkin); 132 if (!config) { 133 asl_log(NULL, NULL, ASL_LEVEL_ERR, "could not send a message to launchd"); 134 exit(EXIT_FAILURE); 135 } 136 137 if (launch_data_get_type(config) == LAUNCH_DATA_ERRNO) { 138 asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed with error: %d %s", launch_data_get_errno(config), strerror(launch_data_get_errno(config))); 139 exit(EXIT_FAILURE); 140 } 141 142 if(dir) { 143 script_dir = dir; 144 asl_log(NULL, NULL, ASL_LEVEL_DEBUG, 145 "script directory set: %s", script_dir); 146 } 147 148 label = launch_data_dict_lookup(config, LAUNCH_JOBKEY_LABEL); 149 if (label) { 150 labelstr = launch_data_get_string(label); 151 } 152 153 aslc = asl_open(labelstr, BUNDLE_ID_PREFIX, ASL_OPT_NO_DELAY); 154 asl_log_descriptor(aslc, NULL, ASL_LEVEL_INFO, STDOUT_FILENO, ASL_LOG_DESCRIPTOR_WRITE); 155 asl_log_descriptor(aslc, NULL, ASL_LEVEL_NOTICE, STDERR_FILENO, ASL_LOG_DESCRIPTOR_WRITE); 156 157#ifdef LAUNCH_JOBKEY_MACHSERVICES 158 launch_data_t tmv; 159 tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT); 160 if (tmv) { 161 idle_timeout = launch_data_get_integer(tmv); 162 asl_log(NULL, NULL, ASL_LEVEL_DEBUG, 163 "idle timeout set: %ld seconds", idle_timeout); 164 } 165 166 launch_data_t svc; 167 svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES); 168 if (!svc) { 169 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services"); 170 exit(EXIT_FAILURE); 171 } 172 173 svc = launch_data_dict_lookup(svc, BOOTSTRAP_NAME); 174 if (!svc) { 175 asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s", 176 BOOTSTRAP_NAME); 177 exit(EXIT_FAILURE); 178 } 179 180 mp = launch_data_get_machport(svc); 181#else 182 mp = checkin_or_register(BUNDLE_ID_PREFIX".privileged_startx"); 183#endif 184 185 if (mp == MACH_PORT_NULL) { 186 asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s", 187 BOOTSTRAP_NAME); 188 exit(EXIT_FAILURE); 189 } 190 191 /* insert a send right so we can send our idle exit message */ 192 kr = mach_port_insert_right(mach_task_self(), mp, mp, 193 MACH_MSG_TYPE_MAKE_SEND); 194 if (kr != KERN_SUCCESS) { 195 asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s", 196 mach_error_string(kr)); 197 exit(EXIT_FAILURE); 198 } 199 200#ifdef LAUNCH_JOBKEY_MACHSERVICES 201 /* spawn a thread to monitor our idle timeout */ 202 pthread_t thread; 203 idle_globals.mp = mp; 204 idle_globals.timeout = idle_timeout; 205 gettimeofday(&idle_globals.lastmsg, NULL); 206 pthread_create(&thread, NULL, &idle_thread, NULL); 207#endif 208 209 /* Main event loop */ 210 kr = mach_msg_server(privileged_startx_server, mxmsgsz, mp, 0); 211 if (kr != KERN_SUCCESS) { 212 asl_log(NULL, NULL, ASL_LEVEL_ERR, 213 "mach_msg_server(mp): %s", mach_error_string(kr)); 214 exit(EXIT_FAILURE); 215 } 216 217 exit(EXIT_SUCCESS); 218} 219 220static int ftscmp(const FTSENT **a, const FTSENT **b) { 221 return strcmp((**a).fts_name, (**b).fts_name); 222} 223 224kern_return_t do_privileged_startx(mach_port_t test_port __attribute__((unused))) { 225 kern_return_t retval = KERN_SUCCESS; 226 char fn_buf[PATH_MAX + 1]; 227 char *s; 228 int error_code; 229 FTS *ftsp; 230 FTSENT *ftsent; 231 232 const char * path_argv[2] = {script_dir, NULL}; 233 234#ifdef LAUNCH_JOBKEY_MACHSERVICES 235 /* Store that we were called, so the idle timer will reset */ 236 gettimeofday(&idle_globals.lastmsg, NULL); 237#endif 238 239 /* script_dir contains a set of files to run with root privs when X11 starts */ 240 ftsp = fts_open((char * const *)path_argv, FTS_PHYSICAL, ftscmp); 241 if(!ftsp) { 242 asl_log(NULL, NULL, ASL_LEVEL_ERR, 243 "do_privileged_startx: fts_open(%s): %s", 244 script_dir, strerror(errno)); 245 return KERN_FAILURE; 246 } 247 248 /* Grab our dir */ 249 ftsent = fts_read(ftsp); 250 if(!ftsent) { 251 asl_log(NULL, NULL, ASL_LEVEL_ERR, 252 "do_privileged_startx: fts_read(): %s", strerror(errno)); 253 fts_close(ftsp); 254 return KERN_FAILURE; 255 } 256 257 /* Get a list of the files in this directory */ 258 ftsent = fts_children(ftsp, 0); 259 if(!ftsent) { 260 asl_log(NULL, NULL, ASL_LEVEL_ERR, 261 "do_privileged_startx: fts_children(): %s", strerror(errno)); 262 fts_close(ftsp); 263 return KERN_FAILURE; 264 } 265 266 /* Setup the buffer to have the path to the script dir */ 267 strncpy(fn_buf, script_dir, PATH_MAX-1); 268 strcat(fn_buf, "/"); 269 s = strrchr(fn_buf, 0); 270 271 /* Itterate over these files in alphabetical order */ 272 for(; ftsent; ftsent = ftsent->fts_link) { 273 /* We only source regular files that are executable */ 274 /* Note: This assumes we own them, which should always be the case */ 275 if((ftsent->fts_statp->st_mode & S_IFREG) && 276 (ftsent->fts_statp->st_mode & S_IXUSR)) { 277 278 /* Complete the full path filename in fn_buf */ 279 strcpy(s, ftsent->fts_name); 280 281 /* Run it */ 282 error_code = system(fn_buf); 283 if(error_code != 0) { 284 asl_log(NULL, NULL, ASL_LEVEL_ERR, 285 "do_privileged_startx: %s: exited with status %d", 286 fn_buf, error_code); 287 retval = KERN_FAILURE; 288 } 289 } 290 } 291 292 fts_close(ftsp); 293 return retval; 294} 295 296kern_return_t do_idle_exit(mach_port_t test_port __attribute__((unused))) { 297#ifdef LAUNCH_JOBKEY_MACHSERVICES 298 struct timeval now; 299 gettimeofday(&now, NULL); 300 301 long delta = now.tv_sec - idle_globals.lastmsg.tv_sec; 302 if (delta >= idle_globals.timeout) { 303 exit(EXIT_SUCCESS); 304 } 305 306 return KERN_SUCCESS; 307#else 308 return KERN_FAILURE; 309#endif 310} 311 312#ifdef LAUNCH_JOBKEY_MACHSERVICES 313static void *idle_thread(void* param __attribute__((unused))) { 314 for(;;) { 315 struct timeval now; 316 gettimeofday(&now, NULL); 317 long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec); 318 if (delta < idle_globals.timeout) { 319 /* sleep for remainder of timeout */ 320 sleep(idle_globals.timeout - delta); 321 } else { 322 /* timeout has elapsed, attempt to idle exit */ 323 idle_exit(idle_globals.mp); 324 } 325 } 326 return NULL; 327} 328#endif 329