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