]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2005 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_APACHE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
7 | * you may not use this file except in compliance with the License. | |
8 | * You may obtain a copy of the License at | |
9 | * | |
10 | * http://www.apache.org/licenses/LICENSE-2.0 | |
11 | * | |
12 | * Unless required by applicable law or agreed to in writing, software | |
13 | * distributed under the License is distributed on an "AS IS" BASIS, | |
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 | * See the License for the specific language governing permissions and | |
16 | * limitations under the License. | |
17 | * | |
18 | * @APPLE_APACHE_LICENSE_HEADER_END@ | |
19 | */ | |
20 | ||
21 | #include "config.h" | |
22 | #include "ipc.h" | |
23 | ||
24 | #include <sys/socket.h> | |
25 | #include <sys/types.h> | |
26 | #include <sys/queue.h> | |
27 | #include <sys/event.h> | |
28 | #include <sys/stat.h> | |
29 | #include <sys/ucred.h> | |
30 | #include <sys/fcntl.h> | |
31 | #include <sys/un.h> | |
32 | #include <sys/wait.h> | |
33 | #include <sys/sysctl.h> | |
34 | #include <sys/sockio.h> | |
35 | #include <sys/time.h> | |
36 | #include <sys/resource.h> | |
37 | #include <sys/ioctl.h> | |
38 | #include <unistd.h> | |
39 | #include <signal.h> | |
40 | #include <errno.h> | |
41 | #include <libgen.h> | |
42 | #include <stdio.h> | |
43 | #include <stdlib.h> | |
44 | #include <stdarg.h> | |
45 | #include <stdbool.h> | |
46 | #include <paths.h> | |
47 | #include <string.h> | |
48 | #include <assumes.h> | |
49 | ||
50 | #include "launch.h" | |
51 | #include "launch_priv.h" | |
52 | #include "launchd.h" | |
53 | #include "runtime.h" | |
54 | #include "core.h" | |
55 | ||
56 | extern char **environ; | |
57 | ||
58 | static LIST_HEAD(, conncb) connections; | |
59 | ||
60 | static launch_data_t adjust_rlimits(launch_data_t in); | |
61 | ||
62 | static void ipc_readmsg2(launch_data_t data, const char *cmd, void *context); | |
63 | static void ipc_readmsg(launch_data_t msg, void *context); | |
64 | ||
65 | static void ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev); | |
66 | ||
67 | static kq_callback kqipc_listen_callback = ipc_listen_callback; | |
68 | ||
69 | static pid_t ipc_self = 0; | |
70 | ||
71 | char *sockpath = NULL; | |
72 | static char *sockdir = NULL; | |
73 | ||
74 | static bool ipc_inited = false; | |
75 | ||
76 | static void | |
77 | ipc_clean_up(void) | |
78 | { | |
79 | if (ipc_self != getpid()) { | |
80 | return; | |
81 | } | |
82 | ||
83 | if (-1 == unlink(sockpath)) { | |
84 | launchd_syslog(LOG_WARNING, "unlink(\"%s\"): %s", sockpath, strerror(errno)); | |
85 | } else if (-1 == rmdir(sockdir)) { | |
86 | launchd_syslog(LOG_WARNING, "rmdir(\"%s\"): %s", sockdir, strerror(errno)); | |
87 | } | |
88 | } | |
89 | ||
90 | void | |
91 | ipc_server_init(void) | |
92 | { | |
93 | struct sockaddr_un sun; | |
94 | mode_t oldmask; | |
95 | int r, fd = -1; | |
96 | char ourdir[1024]; | |
97 | ||
98 | if (ipc_inited) { | |
99 | return; | |
100 | } | |
101 | ||
102 | memset(&sun, 0, sizeof(sun)); | |
103 | sun.sun_family = AF_UNIX; | |
104 | ||
105 | if (pid1_magic) { | |
106 | strcpy(ourdir, LAUNCHD_SOCK_PREFIX); | |
107 | strncpy(sun.sun_path, LAUNCHD_SOCK_PREFIX "/sock", sizeof(sun.sun_path)); | |
108 | ||
109 | unlink(ourdir); | |
110 | if (mkdir(ourdir, S_IRWXU) == -1) { | |
111 | if (errno == EROFS) { | |
112 | goto out_bad; | |
113 | } else if (errno == EEXIST) { | |
114 | struct stat sb; | |
115 | stat(ourdir, &sb); | |
116 | if (!S_ISDIR(sb.st_mode)) { | |
117 | errno = EEXIST; | |
118 | launchd_syslog(LOG_ERR, "mkdir(\"%s\"): %s", LAUNCHD_SOCK_PREFIX, strerror(errno)); | |
119 | goto out_bad; | |
120 | } | |
121 | } else { | |
122 | launchd_syslog(LOG_ERR, "mkdir(\"%s\"): %s", ourdir, strerror(errno)); | |
123 | goto out_bad; | |
124 | } | |
125 | } | |
126 | } else { | |
127 | snprintf(ourdir, sizeof(ourdir), _PATH_TMP "launchd-%u.XXXXXX", getpid()); | |
128 | if (mkdtemp(ourdir) == NULL) { | |
129 | launchd_syslog(LOG_ERR, "Could not create critical directory \"%s\": %s", ourdir, strerror(errno)); | |
130 | goto out_bad; | |
131 | } | |
132 | snprintf(sun.sun_path, sizeof(sun.sun_path), "%s/sock", ourdir); | |
133 | } | |
134 | ||
135 | if (unlink(sun.sun_path) == -1 && errno != ENOENT) { | |
136 | if (errno != EROFS) { | |
137 | launchd_syslog(LOG_ERR, "unlink(\"thesocket\"): %s", strerror(errno)); | |
138 | } | |
139 | goto out_bad; | |
140 | } | |
141 | ||
142 | if (posix_assumes_zero(fd = _fd(socket(AF_UNIX, SOCK_STREAM, 0))) == -1) { | |
143 | goto out_bad; | |
144 | } | |
145 | ||
146 | oldmask = umask(S_IRWXG|S_IRWXO); | |
147 | r = bind(fd, (struct sockaddr *)&sun, sizeof(sun)); | |
148 | umask(oldmask); | |
149 | ||
150 | if (r == -1) { | |
151 | if (errno != EROFS) { | |
152 | launchd_syslog(LOG_ERR, "bind(\"thesocket\"): %s", strerror(errno)); | |
153 | } | |
154 | goto out_bad; | |
155 | } | |
156 | ||
157 | if (listen(fd, SOMAXCONN) == -1) { | |
158 | launchd_syslog(LOG_ERR, "listen(\"thesocket\"): %s", strerror(errno)); | |
159 | goto out_bad; | |
160 | } | |
161 | ||
162 | if (kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &kqipc_listen_callback) == -1) { | |
163 | launchd_syslog(LOG_ERR, "kevent_mod(\"thesocket\", EVFILT_READ): %s", strerror(errno)); | |
164 | goto out_bad; | |
165 | } | |
166 | ||
167 | ipc_inited = true; | |
168 | ||
169 | sockdir = strdup(ourdir); | |
170 | sockpath = strdup(sun.sun_path); | |
171 | ipc_self = getpid(); | |
172 | atexit(ipc_clean_up); | |
173 | ||
174 | out_bad: | |
175 | if (!ipc_inited && fd != -1) { | |
176 | (void)runtime_close(fd); | |
177 | } | |
178 | } | |
179 | ||
180 | void | |
181 | ipc_open(int fd, job_t j) | |
182 | { | |
183 | struct conncb *c = calloc(1, sizeof(struct conncb)); | |
184 | ||
185 | fcntl(fd, F_SETFL, O_NONBLOCK); | |
186 | ||
187 | c->kqconn_callback = ipc_callback; | |
188 | if (j) { | |
189 | c->conn = launchd_fdopen(-1, fd); | |
190 | } else { | |
191 | c->conn = launchd_fdopen(fd, -1); | |
192 | } | |
193 | ||
194 | c->j = j; | |
195 | LIST_INSERT_HEAD(&connections, c, sle); | |
196 | kevent_mod(fd, EVFILT_READ, EV_ADD, 0, 0, &c->kqconn_callback); | |
197 | } | |
198 | ||
199 | void | |
200 | ipc_listen_callback(void *obj __attribute__((unused)), struct kevent *kev) | |
201 | { | |
202 | struct sockaddr_un sun; | |
203 | socklen_t sl = sizeof(sun); | |
204 | int cfd; | |
205 | ||
206 | if ((cfd = _fd(accept(kev->ident, (struct sockaddr *)&sun, &sl))) == -1) { | |
207 | return; | |
208 | } | |
209 | ||
210 | ipc_open(cfd, NULL); | |
211 | } | |
212 | ||
213 | void | |
214 | ipc_callback(void *obj, struct kevent *kev) | |
215 | { | |
216 | struct conncb *c = obj; | |
217 | int r; | |
218 | ||
219 | if (kev->filter == EVFILT_READ) { | |
220 | if (launchd_msg_recv(c->conn, ipc_readmsg, c) == -1 && errno != EAGAIN) { | |
221 | if (errno != ECONNRESET) { | |
222 | launchd_syslog(LOG_DEBUG, "%s(): recv: %s", __func__, strerror(errno)); | |
223 | } | |
224 | ipc_close(c); | |
225 | } | |
226 | } else if (kev->filter == EVFILT_WRITE) { | |
227 | r = launchd_msg_send(c->conn, NULL); | |
228 | if (r == -1) { | |
229 | if (errno != EAGAIN) { | |
230 | launchd_syslog(LOG_DEBUG, "%s(): send: %s", __func__, strerror(errno)); | |
231 | ipc_close(c); | |
232 | } | |
233 | } else if (r == 0) { | |
234 | kevent_mod(launchd_getfd(c->conn), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); | |
235 | } | |
236 | } else { | |
237 | launchd_syslog(LOG_DEBUG, "%s(): unknown filter type!", __func__); | |
238 | ipc_close(c); | |
239 | } | |
240 | } | |
241 | ||
242 | static void | |
243 | set_user_env(launch_data_t obj, const char *key, void *context __attribute__((unused))) | |
244 | { | |
245 | const char *v = launch_data_get_string(obj); | |
246 | if (v) { | |
247 | setenv(key, v, 1); | |
248 | } else { | |
249 | launchd_syslog(LOG_WARNING, "Attempt to set NULL environment variable: %s (type = %d)", key, launch_data_get_type(obj)); | |
250 | } | |
251 | } | |
252 | ||
253 | void | |
254 | ipc_close_all_with_job(job_t j) | |
255 | { | |
256 | struct conncb *ci, *cin; | |
257 | ||
258 | LIST_FOREACH_SAFE(ci, &connections, sle, cin) { | |
259 | if (ci->j == j) { | |
260 | ipc_close(ci); | |
261 | } | |
262 | } | |
263 | } | |
264 | ||
265 | void | |
266 | ipc_close_fds(launch_data_t o) | |
267 | { | |
268 | size_t i; | |
269 | ||
270 | switch (launch_data_get_type(o)) { | |
271 | case LAUNCH_DATA_DICTIONARY: | |
272 | launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_close_fds, NULL); | |
273 | break; | |
274 | case LAUNCH_DATA_ARRAY: | |
275 | for (i = 0; i < launch_data_array_get_count(o); i++) | |
276 | ipc_close_fds(launch_data_array_get_index(o, i)); | |
277 | break; | |
278 | case LAUNCH_DATA_FD: | |
279 | if (launch_data_get_fd(o) != -1) { | |
280 | (void)runtime_close(launch_data_get_fd(o)); | |
281 | } | |
282 | break; | |
283 | default: | |
284 | break; | |
285 | } | |
286 | } | |
287 | ||
288 | void | |
289 | ipc_revoke_fds(launch_data_t o) | |
290 | { | |
291 | size_t i; | |
292 | ||
293 | switch (launch_data_get_type(o)) { | |
294 | case LAUNCH_DATA_DICTIONARY: | |
295 | launch_data_dict_iterate(o, (void (*)(launch_data_t, const char *, void *))ipc_revoke_fds, NULL); | |
296 | break; | |
297 | case LAUNCH_DATA_ARRAY: | |
298 | for (i = 0; i < launch_data_array_get_count(o); i++) | |
299 | ipc_revoke_fds(launch_data_array_get_index(o, i)); | |
300 | break; | |
301 | case LAUNCH_DATA_FD: | |
302 | launch_data_set_fd(o, -1); | |
303 | break; | |
304 | default: | |
305 | break; | |
306 | } | |
307 | } | |
308 | ||
309 | struct readmsg_context { | |
310 | struct conncb *c; | |
311 | launch_data_t resp; | |
312 | }; | |
313 | ||
314 | void | |
315 | ipc_readmsg(launch_data_t msg, void *context) | |
316 | { | |
317 | struct readmsg_context rmc = { context, NULL }; | |
318 | ||
319 | if (LAUNCH_DATA_DICTIONARY == launch_data_get_type(msg)) { | |
320 | launch_data_dict_iterate(msg, ipc_readmsg2, &rmc); | |
321 | } else if (LAUNCH_DATA_STRING == launch_data_get_type(msg)) { | |
322 | ipc_readmsg2(NULL, launch_data_get_string(msg), &rmc); | |
323 | } else { | |
324 | rmc.resp = launch_data_new_errno(EINVAL); | |
325 | } | |
326 | ||
327 | if (NULL == rmc.resp) { | |
328 | rmc.resp = launch_data_new_errno(ENOSYS); | |
329 | } | |
330 | ||
331 | ipc_close_fds(msg); | |
332 | ||
333 | if (launchd_msg_send(rmc.c->conn, rmc.resp) == -1) { | |
334 | if (errno == EAGAIN) { | |
335 | kevent_mod(launchd_getfd(rmc.c->conn), EVFILT_WRITE, EV_ADD, 0, 0, &rmc.c->kqconn_callback); | |
336 | } else { | |
337 | launchd_syslog(LOG_DEBUG, "launchd_msg_send() == -1: %s", strerror(errno)); | |
338 | ipc_close(rmc.c); | |
339 | } | |
340 | } | |
341 | launch_data_free(rmc.resp); | |
342 | } | |
343 | ||
344 | void | |
345 | ipc_readmsg2(launch_data_t data, const char *cmd, void *context) | |
346 | { | |
347 | struct readmsg_context *rmc = context; | |
348 | launch_data_t resp = NULL; | |
349 | job_t j; | |
350 | ||
351 | if (rmc->resp) { | |
352 | return; | |
353 | } | |
354 | ||
355 | /* Do not allow commands other than check-in to come over the trusted socket | |
356 | * on the Desktop. On Embedded, allow all commands over the trusted socket | |
357 | * if the job has the God Mode key set. | |
358 | */ | |
359 | #if TARGET_OS_EMBEDDED | |
360 | bool allow_privileged_ops = (!rmc->c->j || job_is_god(rmc->c->j)); | |
361 | #else | |
362 | bool allow_privileged_ops = !rmc->c->j; | |
363 | #endif | |
364 | ||
365 | if (rmc->c->j && strcmp(cmd, LAUNCH_KEY_CHECKIN) == 0) { | |
366 | resp = job_export(rmc->c->j); | |
367 | job_checkin(rmc->c->j); | |
368 | } else if (allow_privileged_ops) { | |
369 | #if TARGET_OS_EMBEDDED | |
370 | launchd_embedded_handofgod = rmc->c->j && job_is_god(rmc->c->j); | |
371 | #endif | |
372 | if (data == NULL) { | |
373 | if (!strcmp(cmd, LAUNCH_KEY_SHUTDOWN)) { | |
374 | launchd_shutdown(); | |
375 | resp = launch_data_new_errno(0); | |
376 | } else if (!strcmp(cmd, LAUNCH_KEY_GETJOBS)) { | |
377 | resp = job_export_all(); | |
378 | ipc_revoke_fds(resp); | |
379 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRESOURCELIMITS)) { | |
380 | resp = adjust_rlimits(NULL); | |
381 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGESELF)) { | |
382 | struct rusage rusage; | |
383 | getrusage(RUSAGE_SELF, &rusage); | |
384 | resp = launch_data_new_opaque(&rusage, sizeof(rusage)); | |
385 | } else if (!strcmp(cmd, LAUNCH_KEY_GETRUSAGECHILDREN)) { | |
386 | struct rusage rusage; | |
387 | getrusage(RUSAGE_CHILDREN, &rusage); | |
388 | resp = launch_data_new_opaque(&rusage, sizeof(rusage)); | |
389 | } | |
390 | } else { | |
391 | if (!strcmp(cmd, LAUNCH_KEY_STARTJOB)) { | |
392 | if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) { | |
393 | errno = job_dispatch(j, true) ? 0 : errno; | |
394 | } | |
395 | resp = launch_data_new_errno(errno); | |
396 | } else if (!strcmp(cmd, LAUNCH_KEY_STOPJOB)) { | |
397 | if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) { | |
398 | errno = 0; | |
399 | job_stop(j); | |
400 | } | |
401 | resp = launch_data_new_errno(errno); | |
402 | } else if (!strcmp(cmd, LAUNCH_KEY_REMOVEJOB)) { | |
403 | if ((j = job_find(NULL, launch_data_get_string(data))) != NULL) { | |
404 | errno = 0; | |
405 | job_remove(j); | |
406 | } | |
407 | resp = launch_data_new_errno(errno); | |
408 | } else if (!strcmp(cmd, LAUNCH_KEY_SUBMITJOB)) { | |
409 | if (launch_data_get_type(data) == LAUNCH_DATA_ARRAY) { | |
410 | resp = job_import_bulk(data); | |
411 | } else { | |
412 | if (job_import(data)) { | |
413 | errno = 0; | |
414 | } | |
415 | resp = launch_data_new_errno(errno); | |
416 | } | |
417 | } else if (!strcmp(cmd, LAUNCH_KEY_UNSETUSERENVIRONMENT)) { | |
418 | unsetenv(launch_data_get_string(data)); | |
419 | resp = launch_data_new_errno(0); | |
420 | } else if (!strcmp(cmd, LAUNCH_KEY_SETUSERENVIRONMENT)) { | |
421 | launch_data_dict_iterate(data, set_user_env, NULL); | |
422 | resp = launch_data_new_errno(0); | |
423 | } else if (!strcmp(cmd, LAUNCH_KEY_SETRESOURCELIMITS)) { | |
424 | resp = adjust_rlimits(data); | |
425 | } else if (!strcmp(cmd, LAUNCH_KEY_GETJOB)) { | |
426 | if ((j = job_find(NULL, launch_data_get_string(data))) == NULL) { | |
427 | resp = launch_data_new_errno(errno); | |
428 | } else { | |
429 | resp = job_export(j); | |
430 | ipc_revoke_fds(resp); | |
431 | } | |
432 | } else if (!strcmp(cmd, LAUNCH_KEY_SETPRIORITYLIST)) { | |
433 | #if TARGET_OS_EMBEDDED | |
434 | resp = launch_data_new_errno(launchd_set_jetsam_priorities(data)); | |
435 | #else | |
436 | resp = launch_data_new_errno(ENOTSUP); | |
437 | #endif | |
438 | } | |
439 | } | |
440 | #if TARGET_OS_EMBEDDED | |
441 | launchd_embedded_handofgod = false; | |
442 | #endif | |
443 | } else { | |
444 | resp = launch_data_new_errno(EACCES); | |
445 | } | |
446 | ||
447 | rmc->resp = resp; | |
448 | } | |
449 | ||
450 | static int | |
451 | close_abi_fixup(int fd) | |
452 | { | |
453 | return runtime_close(fd); | |
454 | } | |
455 | ||
456 | void | |
457 | ipc_close(struct conncb *c) | |
458 | { | |
459 | LIST_REMOVE(c, sle); | |
460 | launchd_close(c->conn, close_abi_fixup); | |
461 | free(c); | |
462 | } | |
463 | ||
464 | launch_data_t | |
465 | adjust_rlimits(launch_data_t in) | |
466 | { | |
467 | /* If I never have to deal with this rlimit nonsense again, I'll be a very | |
468 | * happy man. | |
469 | */ | |
470 | struct rlimit l[RLIM_NLIMITS]; | |
471 | struct rlimit *ltmp; | |
472 | size_t i,ltmpsz; | |
473 | ||
474 | for (i = 0; i < RLIM_NLIMITS; i++) { | |
475 | (void)posix_assumes_zero(getrlimit(i, l + i)); | |
476 | } | |
477 | ||
478 | if (in) { | |
479 | ltmp = launch_data_get_opaque(in); | |
480 | ltmpsz = launch_data_get_opaque_size(in); | |
481 | ||
482 | if (ltmpsz > sizeof(l)) { | |
483 | launchd_syslog(LOG_WARNING, "Too much rlimit data sent!"); | |
484 | ltmpsz = sizeof(l); | |
485 | } | |
486 | ||
487 | for (i = 0; i < (ltmpsz / sizeof(struct rlimit)); i++) { | |
488 | if (ltmp[i].rlim_cur == l[i].rlim_cur && ltmp[i].rlim_max == l[i].rlim_max) { | |
489 | continue; | |
490 | } | |
491 | ||
492 | if (/* XXX readcfg_pid && */ pid1_magic && (i == RLIMIT_NOFILE || i == RLIMIT_NPROC)) { | |
493 | int gmib[] = { CTL_KERN, KERN_MAXPROC }; | |
494 | int pmib[] = { CTL_KERN, KERN_MAXPROCPERUID }; | |
495 | const char *gstr = "kern.maxproc"; | |
496 | const char *pstr = "kern.maxprocperuid"; | |
497 | int gval = ltmp[i].rlim_max; | |
498 | int pval = ltmp[i].rlim_cur; | |
499 | switch (i) { | |
500 | case RLIMIT_NOFILE: | |
501 | gmib[1] = KERN_MAXFILES; | |
502 | pmib[1] = KERN_MAXFILESPERPROC; | |
503 | gstr = "kern.maxfiles"; | |
504 | pstr = "kern.maxfilesperproc"; | |
505 | break; | |
506 | default: | |
507 | break; | |
508 | } | |
509 | ||
510 | if (gval > 0) { | |
511 | (void)posix_assumes_zero(sysctl(gmib, 2, NULL, NULL, &gval, sizeof(gval))); | |
512 | } else { | |
513 | launchd_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", gstr); | |
514 | } | |
515 | if (pval > 0) { | |
516 | (void)posix_assumes_zero(sysctl(pmib, 2, NULL, NULL, &pval, sizeof(pval))); | |
517 | } else { | |
518 | launchd_syslog(LOG_WARNING, "sysctl(\"%s\"): can't be zero", pstr); | |
519 | } | |
520 | } | |
521 | (void)posix_assumes_zero(setrlimit(i, ltmp + i)); | |
522 | /* the kernel may have clamped the values we gave it */ | |
523 | (void)posix_assumes_zero(getrlimit(i, l + i)); | |
524 | } | |
525 | } | |
526 | ||
527 | return launch_data_new_opaque(l, sizeof(struct rlimit) * RLIM_NLIMITS); | |
528 | } |