server.c revision c81d8f5e
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 "console_redirect.h"
50
51#include "privileged_startx.h"
52#include "privileged_startxServer.h"
53
54union MaxMsgSize {
55    union __RequestUnion__privileged_startx_subsystem req;
56    union __ReplyUnion__privileged_startx_subsystem rep;
57};
58
59#ifdef LAUNCH_JOBKEY_MACHSERVICES
60#include <pthread.h>
61static void* idle_thread(void* param __attribute__((unused)));
62
63/* globals to trigger idle exit */
64#define DEFAULT_IDLE_TIMEOUT 60 /* 60 second timeout, then the server exits */
65
66struct idle_globals {
67	mach_port_t      mp;
68	long    timeout;
69	struct timeval   lastmsg;
70};
71
72struct idle_globals idle_globals;
73#endif
74
75#ifndef SCRIPTDIR
76#define SCRIPTDIR="/usr/X11/lib/X11/xinit/privileged_startx.d"
77#endif
78
79/* Default script dir */
80const char *script_dir = SCRIPTDIR;
81
82#ifndef LAUNCH_JOBKEY_MACHSERVICES
83static mach_port_t checkin_or_register(char *bname) {
84    kern_return_t kr;
85    mach_port_t mp;
86
87    /* If we're started by launchd or the old mach_init */
88    kr = bootstrap_check_in(bootstrap_port, bname, &mp);
89    if (kr == KERN_SUCCESS)
90        return mp;
91
92    /* We probably were not started by launchd or the old mach_init */
93    kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &mp);
94    if (kr != KERN_SUCCESS) {
95        asl_log(NULL, NULL, ASL_LEVEL_ERR, "mach_port_allocate(): %s", mach_error_string(kr));
96        exit(EXIT_FAILURE);
97    }
98
99    kr = mach_port_insert_right(mach_task_self(), mp, mp, MACH_MSG_TYPE_MAKE_SEND);
100    if (kr != KERN_SUCCESS) {
101        asl_log(NULL, NULL, ASL_LEVEL_ERR, "mach_port_insert_right(): %s", mach_error_string(kr));
102        exit(EXIT_FAILURE);
103    }
104
105    kr = bootstrap_register(bootstrap_port, bname, mp);
106    if (kr != KERN_SUCCESS) {
107        asl_log(NULL, NULL, ASL_LEVEL_ERR, "bootstrap_register(): %s", mach_error_string(kr));
108        exit(EXIT_FAILURE);
109    }
110
111    return mp;
112}
113#endif
114
115int server_main(const char *dir) {
116    mach_msg_size_t mxmsgsz = sizeof(union MaxMsgSize) + MAX_TRAILER_SIZE;
117    mach_port_t mp;
118    kern_return_t kr;
119#ifdef LAUNCH_JOBKEY_MACHSERVICES
120    long idle_timeout = DEFAULT_IDLE_TIMEOUT;
121#endif
122
123    launch_data_t config = NULL, checkin = NULL, label = NULL;
124    const char *labelstr = BUNDLE_ID_PREFIX".privileged_startx";
125    aslclient aslc;
126
127    checkin = launch_data_new_string(LAUNCH_KEY_CHECKIN);
128    config = launch_msg(checkin);
129    if (!config || launch_data_get_type(config) == LAUNCH_DATA_ERRNO) {
130        asl_log(NULL, NULL, ASL_LEVEL_ERR, "launchd checkin failed");
131        exit(EXIT_FAILURE);
132    }
133
134    if(dir) {
135        script_dir = dir;
136        asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
137                "script directory set: %s", script_dir);
138    }
139
140    label = launch_data_dict_lookup(config, LAUNCH_JOBKEY_LABEL);
141    if (label) {
142        labelstr = launch_data_get_string(label);
143    }
144
145    aslc = asl_open(labelstr, BUNDLE_ID_PREFIX, ASL_OPT_NO_DELAY);
146    xi_asl_capture_fd(aslc, NULL, ASL_LEVEL_INFO, STDOUT_FILENO);
147    xi_asl_capture_fd(aslc, NULL, ASL_LEVEL_NOTICE, STDERR_FILENO);
148
149#ifdef LAUNCH_JOBKEY_MACHSERVICES
150    launch_data_t tmv;
151    tmv = launch_data_dict_lookup(config, LAUNCH_JOBKEY_TIMEOUT);
152    if (tmv) {
153        idle_timeout = launch_data_get_integer(tmv);
154        asl_log(NULL, NULL, ASL_LEVEL_DEBUG,
155                "idle timeout set: %ld seconds", idle_timeout);
156    }
157
158    launch_data_t svc;
159    svc = launch_data_dict_lookup(config, LAUNCH_JOBKEY_MACHSERVICES);
160    if (!svc) {
161        asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach services");
162        exit(EXIT_FAILURE);
163    }
164
165    svc = launch_data_dict_lookup(svc, BOOTSTRAP_NAME);
166    if (!svc) {
167        asl_log(NULL, NULL, ASL_LEVEL_ERR, "no mach service: %s",
168                BOOTSTRAP_NAME);
169        exit(EXIT_FAILURE);
170    }
171
172    mp = launch_data_get_machport(svc);
173#else
174    mp = checkin_or_register(BUNDLE_ID_PREFIX".privileged_startx");
175#endif
176
177    if (mp == MACH_PORT_NULL) {
178        asl_log(NULL, NULL, ASL_LEVEL_ERR, "NULL mach service: %s",
179                BOOTSTRAP_NAME);
180        exit(EXIT_FAILURE);
181    }
182
183    /* insert a send right so we can send our idle exit message */
184    kr = mach_port_insert_right(mach_task_self(), mp, mp,
185                                MACH_MSG_TYPE_MAKE_SEND);
186    if (kr != KERN_SUCCESS) {
187        asl_log(NULL, NULL, ASL_LEVEL_ERR, "send right failed: %s",
188                mach_error_string(kr));
189        exit(EXIT_FAILURE);
190    }
191
192#ifdef LAUNCH_JOBKEY_MACHSERVICES
193    /* spawn a thread to monitor our idle timeout */
194    pthread_t thread;
195    idle_globals.mp = mp;
196    idle_globals.timeout = idle_timeout;
197    gettimeofday(&idle_globals.lastmsg, NULL);
198    pthread_create(&thread, NULL, &idle_thread, NULL);
199#endif
200
201    /* Main event loop */
202    kr = mach_msg_server(privileged_startx_server, mxmsgsz, mp, 0);
203    if (kr != KERN_SUCCESS) {
204        asl_log(NULL, NULL, ASL_LEVEL_ERR,
205                "mach_msg_server(mp): %s", mach_error_string(kr));
206        exit(EXIT_FAILURE);
207    }
208
209    exit(EXIT_SUCCESS);
210}
211
212static int ftscmp(const FTSENT **a, const FTSENT **b) {
213    return strcmp((**a).fts_name, (**b).fts_name);
214}
215
216kern_return_t do_privileged_startx(mach_port_t test_port __attribute__((unused))) {
217    kern_return_t retval = KERN_SUCCESS;
218    char fn_buf[PATH_MAX + 1];
219    char *s;
220    int error_code;
221    FTS *ftsp;
222    FTSENT *ftsent;
223
224    const char * path_argv[2] = {script_dir, NULL};
225
226#ifdef LAUNCH_JOBKEY_MACHSERVICES
227    /* Store that we were called, so the idle timer will reset */
228    gettimeofday(&idle_globals.lastmsg, NULL);
229#endif
230
231    /* script_dir contains a set of files to run with root privs when X11 starts */
232    ftsp = fts_open((char * const *)path_argv, FTS_PHYSICAL, ftscmp);
233    if(!ftsp) {
234        asl_log(NULL, NULL, ASL_LEVEL_ERR,
235                "do_privileged_startx: fts_open(%s): %s",
236                script_dir, strerror(errno));
237        return KERN_FAILURE;
238    }
239
240    /* Grab our dir */
241    ftsent = fts_read(ftsp);
242    if(!ftsent) {
243        asl_log(NULL, NULL, ASL_LEVEL_ERR,
244                "do_privileged_startx: fts_read(): %s", strerror(errno));
245        fts_close(ftsp);
246        return KERN_FAILURE;
247    }
248
249    /* Get a list of the files in this directory */
250    ftsent = fts_children(ftsp, 0);
251    if(!ftsent) {
252        asl_log(NULL, NULL, ASL_LEVEL_ERR,
253                "do_privileged_startx: fts_children(): %s", strerror(errno));
254        fts_close(ftsp);
255        return KERN_FAILURE;
256    }
257
258    /* Setup the buffer to have the path to the script dir */
259    strncpy(fn_buf, script_dir, PATH_MAX-1);
260    strcat(fn_buf, "/");
261    s = strrchr(fn_buf, 0);
262
263    /* Itterate over these files in alphabetical order */
264    for(; ftsent; ftsent = ftsent->fts_link) {
265        /* We only source regular files that are executable */
266        /* Note: This assumes we own them, which should always be the case */
267        if((ftsent->fts_statp->st_mode & S_IFREG) &&
268           (ftsent->fts_statp->st_mode & S_IXUSR)) {
269
270            /* Complete the full path filename in fn_buf */
271            strcpy(s, ftsent->fts_name);
272
273            /* Run it */
274            error_code = system(fn_buf);
275            if(error_code != 0) {
276                asl_log(NULL, NULL, ASL_LEVEL_ERR,
277                        "do_privileged_startx: %s: exited with status %d",
278                        fn_buf, error_code);
279                retval = KERN_FAILURE;
280            }
281        }
282    }
283
284    fts_close(ftsp);
285    return retval;
286}
287
288kern_return_t do_idle_exit(mach_port_t test_port __attribute__((unused))) {
289#ifdef LAUNCH_JOBKEY_MACHSERVICES
290    struct timeval now;
291    gettimeofday(&now, NULL);
292
293    long delta = now.tv_sec - idle_globals.lastmsg.tv_sec;
294    if (delta >= idle_globals.timeout) {
295        exit(EXIT_SUCCESS);
296    }
297
298    return KERN_SUCCESS;
299#else
300    return KERN_FAILURE;
301#endif
302}
303
304#ifdef LAUNCH_JOBKEY_MACHSERVICES
305static void *idle_thread(void* param __attribute__((unused))) {
306    for(;;) {
307        struct timeval now;
308        gettimeofday(&now, NULL);
309        long delta = (now.tv_sec - idle_globals.lastmsg.tv_sec);
310        if (delta < idle_globals.timeout) {
311            /* sleep for remainder of timeout */
312            sleep(idle_globals.timeout - delta);
313        } else {
314            /* timeout has elapsed, attempt to idle exit */
315            idle_exit(idle_globals.mp);
316        }
317    }
318    return NULL;
319}
320#endif
321