2 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
4 * @APPLE_APACHE_LICENSE_HEADER_START@
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 * @APPLE_APACHE_LICENSE_HEADER_END@
24 #include <sys/socket.h>
25 #include <sys/types.h>
26 #include <sys/queue.h>
27 #include <sys/event.h>
29 #include <sys/ucred.h>
30 #include <sys/fcntl.h>
33 #include <sys/sysctl.h>
34 #include <sys/sockio.h>
36 #include <sys/resource.h>
37 #include <sys/ioctl.h>
48 #include <os/assumes.h>
51 #include "launch_priv.h"
56 extern char **environ
;
58 static LIST_HEAD(, conncb
) connections
;
60 static launch_data_t
adjust_rlimits(launch_data_t in
);
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
);
65 static void ipc_listen_callback(void *obj
__attribute__((unused
)), struct kevent
*kev
);
67 static kq_callback kqipc_listen_callback
= ipc_listen_callback
;
69 static pid_t ipc_self
= 0;
71 char *sockpath
= NULL
;
72 static char *sockdir
= NULL
;
74 static bool ipc_inited
= false;
79 if (ipc_self
!= getpid()) {
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
));
93 struct sockaddr_un sun
;
102 memset(&sun
, 0, sizeof(sun
));
103 sun
.sun_family
= AF_UNIX
;
106 strcpy(ourdir
, LAUNCHD_SOCK_PREFIX
);
107 strncpy(sun
.sun_path
, LAUNCHD_SOCK_PREFIX
"/sock", sizeof(sun
.sun_path
));
110 if (mkdir(ourdir
, S_IRWXU
) == -1) {
111 if (errno
== EROFS
) {
113 } else if (errno
== EEXIST
) {
116 if (!S_ISDIR(sb
.st_mode
)) {
118 launchd_syslog(LOG_ERR
, "mkdir(\"%s\"): %s", LAUNCHD_SOCK_PREFIX
, strerror(errno
));
122 launchd_syslog(LOG_ERR
, "mkdir(\"%s\"): %s", ourdir
, strerror(errno
));
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
));
132 snprintf(sun
.sun_path
, sizeof(sun
.sun_path
), "%s/sock", ourdir
);
135 if (unlink(sun
.sun_path
) == -1 && errno
!= ENOENT
) {
136 if (errno
!= EROFS
) {
137 launchd_syslog(LOG_ERR
, "unlink(\"thesocket\"): %s", strerror(errno
));
142 if (posix_assumes_zero(fd
= _fd(socket(AF_UNIX
, SOCK_STREAM
, 0))) == -1) {
146 oldmask
= umask(S_IRWXG
|S_IRWXO
);
147 r
= bind(fd
, (struct sockaddr
*)&sun
, sizeof(sun
));
151 if (errno
!= EROFS
) {
152 launchd_syslog(LOG_ERR
, "bind(\"thesocket\"): %s", strerror(errno
));
157 if (listen(fd
, SOMAXCONN
) == -1) {
158 launchd_syslog(LOG_ERR
, "listen(\"thesocket\"): %s", strerror(errno
));
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
));
169 sockdir
= strdup(ourdir
);
170 sockpath
= strdup(sun
.sun_path
);
172 atexit(ipc_clean_up
);
175 if (!ipc_inited
&& fd
!= -1) {
176 (void)runtime_close(fd
);
181 ipc_open(int fd
, job_t j
)
183 struct conncb
*c
= calloc(1, sizeof(struct conncb
));
185 fcntl(fd
, F_SETFL
, O_NONBLOCK
);
187 c
->kqconn_callback
= ipc_callback
;
189 c
->conn
= launchd_fdopen(-1, fd
);
191 c
->conn
= launchd_fdopen(fd
, -1);
195 LIST_INSERT_HEAD(&connections
, c
, sle
);
196 kevent_mod(fd
, EVFILT_READ
, EV_ADD
, 0, 0, &c
->kqconn_callback
);
200 ipc_listen_callback(void *obj
__attribute__((unused
)), struct kevent
*kev
)
202 struct sockaddr_un sun
;
203 socklen_t sl
= sizeof(sun
);
206 if ((cfd
= _fd(accept(kev
->ident
, (struct sockaddr
*)&sun
, &sl
))) == -1) {
210 if (geteuid() == 0) {
212 if (getpeereid(cfd
, &euid
, &guid
) == -1) {
213 launchd_syslog(LOG_NOTICE
| LOG_CONSOLE
, "*** launchd[%d] failed to getpeereid on incoming caller (%d)", getpid(), errno
);
214 (void)runtime_close(cfd
);
218 if (euid
!= geteuid()) {
219 launchd_syslog(LOG_NOTICE
| LOG_CONSOLE
, "*** launchd[%d] failed to euid check on incoming caller (%d != %d)", getpid(), euid
, geteuid());
220 (void)runtime_close(cfd
);
229 ipc_callback(void *obj
, struct kevent
*kev
)
231 struct conncb
*c
= obj
;
234 if (kev
->filter
== EVFILT_READ
) {
235 if (launchd_msg_recv(c
->conn
, ipc_readmsg
, c
) == -1 && errno
!= EAGAIN
) {
236 if (errno
!= ECONNRESET
) {
237 launchd_syslog(LOG_DEBUG
, "%s(): recv: %s", __func__
, strerror(errno
));
241 } else if (kev
->filter
== EVFILT_WRITE
) {
242 r
= launchd_msg_send(c
->conn
, NULL
);
244 if (errno
!= EAGAIN
) {
245 launchd_syslog(LOG_DEBUG
, "%s(): send: %s", __func__
, strerror(errno
));
249 kevent_mod(launchd_getfd(c
->conn
), EVFILT_WRITE
, EV_DELETE
, 0, 0, NULL
);
252 launchd_syslog(LOG_DEBUG
, "%s(): unknown filter type!", __func__
);
258 set_user_env(launch_data_t obj
, const char *key
, void *context
__attribute__((unused
)))
260 const char *v
= launch_data_get_string(obj
);
264 launchd_syslog(LOG_WARNING
, "Attempt to set NULL environment variable: %s (type = %d)", key
, launch_data_get_type(obj
));
269 ipc_close_all_with_job(job_t j
)
271 struct conncb
*ci
, *cin
;
273 LIST_FOREACH_SAFE(ci
, &connections
, sle
, cin
) {
281 ipc_close_fds(launch_data_t o
)
285 switch (launch_data_get_type(o
)) {
286 case LAUNCH_DATA_DICTIONARY
:
287 launch_data_dict_iterate(o
, (void (*)(launch_data_t
, const char *, void *))ipc_close_fds
, NULL
);
289 case LAUNCH_DATA_ARRAY
:
290 for (i
= 0; i
< launch_data_array_get_count(o
); i
++)
291 ipc_close_fds(launch_data_array_get_index(o
, i
));
294 if (launch_data_get_fd(o
) != -1) {
295 (void)runtime_close(launch_data_get_fd(o
));
304 ipc_revoke_fds(launch_data_t o
)
308 switch (launch_data_get_type(o
)) {
309 case LAUNCH_DATA_DICTIONARY
:
310 launch_data_dict_iterate(o
, (void (*)(launch_data_t
, const char *, void *))ipc_revoke_fds
, NULL
);
312 case LAUNCH_DATA_ARRAY
:
313 for (i
= 0; i
< launch_data_array_get_count(o
); i
++)
314 ipc_revoke_fds(launch_data_array_get_index(o
, i
));
317 launch_data_set_fd(o
, -1);
324 struct readmsg_context
{
330 ipc_readmsg(launch_data_t msg
, void *context
)
332 struct readmsg_context rmc
= { context
, NULL
};
334 if (LAUNCH_DATA_DICTIONARY
== launch_data_get_type(msg
)) {
335 launch_data_dict_iterate(msg
, ipc_readmsg2
, &rmc
);
336 } else if (LAUNCH_DATA_STRING
== launch_data_get_type(msg
)) {
337 ipc_readmsg2(NULL
, launch_data_get_string(msg
), &rmc
);
339 rmc
.resp
= launch_data_new_errno(EINVAL
);
342 if (NULL
== rmc
.resp
) {
343 rmc
.resp
= launch_data_new_errno(ENOSYS
);
348 if (launchd_msg_send(rmc
.c
->conn
, rmc
.resp
) == -1) {
349 if (errno
== EAGAIN
) {
350 kevent_mod(launchd_getfd(rmc
.c
->conn
), EVFILT_WRITE
, EV_ADD
, 0, 0, &rmc
.c
->kqconn_callback
);
352 launchd_syslog(LOG_DEBUG
, "launchd_msg_send() == -1: %s", strerror(errno
));
356 launch_data_free(rmc
.resp
);
360 ipc_readmsg2(launch_data_t data
, const char *cmd
, void *context
)
362 struct readmsg_context
*rmc
= context
;
363 launch_data_t resp
= NULL
;
370 /* Do not allow commands other than check-in to come over the trusted socket
371 * on the Desktop. On Embedded, allow all commands over the trusted socket
372 * if the job has the God Mode key set.
374 #if TARGET_OS_EMBEDDED
375 bool allow_privileged_ops
= (!rmc
->c
->j
|| job_is_god(rmc
->c
->j
));
377 bool allow_privileged_ops
= !rmc
->c
->j
;
380 if (rmc
->c
->j
&& strcmp(cmd
, LAUNCH_KEY_CHECKIN
) == 0) {
381 resp
= job_export(rmc
->c
->j
);
382 job_checkin(rmc
->c
->j
);
383 } else if (allow_privileged_ops
) {
384 #if TARGET_OS_EMBEDDED
385 launchd_embedded_handofgod
= rmc
->c
->j
&& job_is_god(rmc
->c
->j
);
388 if (!strcmp(cmd
, LAUNCH_KEY_SHUTDOWN
)) {
390 resp
= launch_data_new_errno(0);
391 } else if (!strcmp(cmd
, LAUNCH_KEY_GETJOBS
)) {
392 resp
= job_export_all();
393 ipc_revoke_fds(resp
);
394 } else if (!strcmp(cmd
, LAUNCH_KEY_GETRESOURCELIMITS
)) {
395 resp
= adjust_rlimits(NULL
);
396 } else if (!strcmp(cmd
, LAUNCH_KEY_GETRUSAGESELF
)) {
397 struct rusage rusage
;
398 getrusage(RUSAGE_SELF
, &rusage
);
399 resp
= launch_data_new_opaque(&rusage
, sizeof(rusage
));
400 } else if (!strcmp(cmd
, LAUNCH_KEY_GETRUSAGECHILDREN
)) {
401 struct rusage rusage
;
402 getrusage(RUSAGE_CHILDREN
, &rusage
);
403 resp
= launch_data_new_opaque(&rusage
, sizeof(rusage
));
406 if (!strcmp(cmd
, LAUNCH_KEY_STARTJOB
)) {
407 if ((j
= job_find(NULL
, launch_data_get_string(data
))) != NULL
) {
408 errno
= job_dispatch(j
, true) ? 0 : errno
;
410 resp
= launch_data_new_errno(errno
);
411 } else if (!strcmp(cmd
, LAUNCH_KEY_STOPJOB
)) {
412 if ((j
= job_find(NULL
, launch_data_get_string(data
))) != NULL
) {
416 resp
= launch_data_new_errno(errno
);
417 } else if (!strcmp(cmd
, LAUNCH_KEY_REMOVEJOB
)) {
418 if ((j
= job_find(NULL
, launch_data_get_string(data
))) != NULL
) {
422 resp
= launch_data_new_errno(errno
);
423 } else if (!strcmp(cmd
, LAUNCH_KEY_SUBMITJOB
)) {
424 if (launch_data_get_type(data
) == LAUNCH_DATA_ARRAY
) {
425 resp
= job_import_bulk(data
);
427 if (job_import(data
)) {
430 resp
= launch_data_new_errno(errno
);
432 } else if (!strcmp(cmd
, LAUNCH_KEY_UNSETUSERENVIRONMENT
)) {
433 unsetenv(launch_data_get_string(data
));
434 resp
= launch_data_new_errno(0);
435 } else if (!strcmp(cmd
, LAUNCH_KEY_SETUSERENVIRONMENT
)) {
436 launch_data_dict_iterate(data
, set_user_env
, NULL
);
437 resp
= launch_data_new_errno(0);
438 } else if (!strcmp(cmd
, LAUNCH_KEY_SETRESOURCELIMITS
)) {
439 resp
= adjust_rlimits(data
);
440 } else if (!strcmp(cmd
, LAUNCH_KEY_GETJOB
)) {
441 if ((j
= job_find(NULL
, launch_data_get_string(data
))) == NULL
) {
442 resp
= launch_data_new_errno(errno
);
444 resp
= job_export(j
);
445 ipc_revoke_fds(resp
);
449 #if TARGET_OS_EMBEDDED
450 launchd_embedded_handofgod
= false;
453 resp
= launch_data_new_errno(EACCES
);
460 close_abi_fixup(int fd
)
462 return runtime_close(fd
);
466 ipc_close(struct conncb
*c
)
469 launchd_close(c
->conn
, close_abi_fixup
);
474 adjust_rlimits(launch_data_t in
)
476 /* If I never have to deal with this rlimit nonsense again, I'll be a very
479 struct rlimit l
[RLIM_NLIMITS
];
483 for (i
= 0; i
< RLIM_NLIMITS
; i
++) {
484 (void)posix_assumes_zero(getrlimit(i
, l
+ i
));
488 ltmp
= launch_data_get_opaque(in
);
489 ltmpsz
= launch_data_get_opaque_size(in
);
491 if (ltmpsz
> sizeof(l
)) {
492 launchd_syslog(LOG_WARNING
, "Too much rlimit data sent!");
496 for (i
= 0; i
< (ltmpsz
/ sizeof(struct rlimit
)); i
++) {
497 if (ltmp
[i
].rlim_cur
== l
[i
].rlim_cur
&& ltmp
[i
].rlim_max
== l
[i
].rlim_max
) {
501 if (/* XXX readcfg_pid && */ pid1_magic
&& (i
== RLIMIT_NOFILE
|| i
== RLIMIT_NPROC
)) {
502 int gmib
[] = { CTL_KERN
, KERN_MAXPROC
};
503 int pmib
[] = { CTL_KERN
, KERN_MAXPROCPERUID
};
504 const char *gstr
= "kern.maxproc";
505 const char *pstr
= "kern.maxprocperuid";
506 int gval
= ltmp
[i
].rlim_max
;
507 int pval
= ltmp
[i
].rlim_cur
;
510 gmib
[1] = KERN_MAXFILES
;
511 pmib
[1] = KERN_MAXFILESPERPROC
;
512 gstr
= "kern.maxfiles";
513 pstr
= "kern.maxfilesperproc";
520 (void)posix_assumes_zero(sysctl(gmib
, 2, NULL
, NULL
, &gval
, sizeof(gval
)));
522 launchd_syslog(LOG_WARNING
, "sysctl(\"%s\"): can't be zero", gstr
);
525 (void)posix_assumes_zero(sysctl(pmib
, 2, NULL
, NULL
, &pval
, sizeof(pval
)));
527 launchd_syslog(LOG_WARNING
, "sysctl(\"%s\"): can't be zero", pstr
);
530 (void)posix_assumes_zero(setrlimit(i
, ltmp
+ i
));
531 /* the kernel may have clamped the values we gave it */
532 (void)posix_assumes_zero(getrlimit(i
, l
+ i
));
536 return launch_data_new_opaque(l
, sizeof(struct rlimit
) * RLIM_NLIMITS
);