1/*- 2 * Copyright (c) 2012 The FreeBSD Foundation 3 * Copyright (c) 2015 Mariusz Zaborski <oshogbo@FreeBSD.org> 4 * All rights reserved. 5 * 6 * This software was developed by Pawel Jakub Dawidek under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31#include <sys/cdefs.h> 32__FBSDID("$FreeBSD$"); 33 34#include <sys/types.h> 35#include <sys/queue.h> 36#include <sys/socket.h> 37#include <sys/nv.h> 38 39#include <assert.h> 40#include <errno.h> 41#include <stdbool.h> 42#include <stdio.h> 43#include <stdlib.h> 44#include <string.h> 45#include <unistd.h> 46 47#include "libcasper_impl.h" 48#include "zygote.h" 49 50struct casper_service { 51 struct service *cs_service; 52 TAILQ_ENTRY(casper_service) cs_next; 53}; 54 55static TAILQ_HEAD(, casper_service) casper_services = 56 TAILQ_HEAD_INITIALIZER(casper_services); 57 58#define CORE_CASPER_NAME "core.casper" 59#define CSERVICE_IS_CORE(service) \ 60 (strcmp(service_name(service->cs_service), CORE_CASPER_NAME) == 0) 61 62static struct casper_service * 63service_find(const char *name) 64{ 65 struct casper_service *casserv; 66 67 TAILQ_FOREACH(casserv, &casper_services, cs_next) { 68 if (strcmp(service_name(casserv->cs_service), name) == 0) 69 break; 70 } 71 return (casserv); 72} 73 74struct casper_service * 75service_register(const char *name, service_limit_func_t *limitfunc, 76 service_command_func_t *commandfunc, uint64_t flags) 77{ 78 struct casper_service *casserv; 79 80 if (commandfunc == NULL) 81 return (NULL); 82 if (name == NULL || name[0] == '\0') 83 return (NULL); 84 if (service_find(name) != NULL) 85 return (NULL); 86 87 casserv = malloc(sizeof(*casserv)); 88 if (casserv == NULL) 89 return (NULL); 90 91 casserv->cs_service = service_alloc(name, limitfunc, commandfunc, 92 flags); 93 if (casserv->cs_service == NULL) { 94 free(casserv); 95 return (NULL); 96 } 97 TAILQ_INSERT_TAIL(&casper_services, casserv, cs_next); 98 99 return (casserv); 100} 101 102static bool 103casper_allowed_service(const nvlist_t *limits, const char *service) 104{ 105 106 if (limits == NULL) 107 return (true); 108 109 if (nvlist_exists_null(limits, service)) 110 return (true); 111 112 return (false); 113} 114 115static int 116casper_limit(const nvlist_t *oldlimits, const nvlist_t *newlimits) 117{ 118 const char *name; 119 int type; 120 void *cookie; 121 122 cookie = NULL; 123 while ((name = nvlist_next(newlimits, &type, &cookie)) != NULL) { 124 if (type != NV_TYPE_NULL) 125 return (EINVAL); 126 if (!casper_allowed_service(oldlimits, name)) 127 return (ENOTCAPABLE); 128 } 129 130 return (0); 131} 132 133static void 134service_execute(int chanfd) 135{ 136 struct service *service; 137 nvlist_t *nvl; 138 int procfd; 139 140 nvl = nvlist_recv(chanfd, 0); 141 if (nvl == NULL) 142 exit(1); 143 service = (struct service *)(uintptr_t)nvlist_take_number(nvl, 144 "service"); 145 procfd = nvlist_take_descriptor(nvl, "procfd"); 146 nvlist_destroy(nvl); 147 148 service_start(service, chanfd, procfd); 149 /* Not reached. */ 150 exit(1); 151} 152 153static int 154casper_command(const char *cmd, const nvlist_t *limits, nvlist_t *nvlin, 155 nvlist_t *nvlout) 156{ 157 struct casper_service *casserv; 158 const char *servname; 159 nvlist_t *nvl; 160 int chanfd, procfd, error; 161 162 if (strcmp(cmd, "open") != 0) 163 return (EINVAL); 164 if (!nvlist_exists_string(nvlin, "service")) 165 return (EINVAL); 166 167 servname = nvlist_get_string(nvlin, "service"); 168 casserv = service_find(servname); 169 if (casserv == NULL) 170 return (ENOENT); 171 172 if (!casper_allowed_service(limits, servname)) 173 return (ENOTCAPABLE); 174 175 if (zygote_clone(service_execute, &chanfd, &procfd) == -1) 176 return (errno); 177 178 nvl = nvlist_create(0); 179 nvlist_add_number(nvl, "service", 180 (uint64_t)(uintptr_t)casserv->cs_service); 181 nvlist_move_descriptor(nvl, "procfd", procfd); 182 if (nvlist_send(chanfd, nvl) == -1) { 183 error = errno; 184 nvlist_destroy(nvl); 185 close(chanfd); 186 return (error); 187 } 188 nvlist_destroy(nvl); 189 190 nvlist_move_descriptor(nvlout, "chanfd", chanfd); 191 192 return (0); 193} 194 195static void 196service_register_core(int fd) 197{ 198 struct casper_service *casserv; 199 struct service_connection *sconn; 200 201 casserv = service_register(CORE_CASPER_NAME, casper_limit, 202 casper_command, 0); 203 sconn = service_connection_add(casserv->cs_service, fd, NULL); 204 if (sconn == NULL) { 205 close(fd); 206 abort(); 207 } 208} 209 210void 211casper_main_loop(int fd) 212{ 213 fd_set fds; 214 struct casper_service *casserv; 215 struct service_connection *sconn, *sconntmp; 216 int sock, maxfd, ret; 217 218 if (zygote_init() < 0) 219 exit(1); 220 221 /* 222 * Register core services. 223 */ 224 service_register_core(fd); 225 226 for (;;) { 227 FD_ZERO(&fds); 228 FD_SET(fd, &fds); 229 maxfd = -1; 230 TAILQ_FOREACH(casserv, &casper_services, cs_next) { 231 /* We handle only core services. */ 232 if (!CSERVICE_IS_CORE(casserv)) 233 continue; 234 for (sconn = service_connection_first(casserv->cs_service); 235 sconn != NULL; 236 sconn = service_connection_next(sconn)) { 237 sock = service_connection_get_sock(sconn); 238 FD_SET(sock, &fds); 239 maxfd = sock > maxfd ? sock : maxfd; 240 } 241 } 242 if (maxfd == -1) { 243 /* Nothing to do. */ 244 exit(0); 245 } 246 maxfd++; 247 248 249 assert(maxfd <= (int)FD_SETSIZE); 250 ret = select(maxfd, &fds, NULL, NULL, NULL); 251 assert(ret == -1 || ret > 0); /* select() cannot timeout */ 252 if (ret == -1) { 253 if (errno == EINTR) 254 continue; 255 exit(1); 256 } 257 258 TAILQ_FOREACH(casserv, &casper_services, cs_next) { 259 /* We handle only core services. */ 260 if (!CSERVICE_IS_CORE(casserv)) 261 continue; 262 for (sconn = service_connection_first(casserv->cs_service); 263 sconn != NULL; sconn = sconntmp) { 264 /* 265 * Prepare for connection to be removed from 266 * the list on failure. 267 */ 268 sconntmp = service_connection_next(sconn); 269 sock = service_connection_get_sock(sconn); 270 if (FD_ISSET(sock, &fds)) { 271 service_message(casserv->cs_service, 272 sconn); 273 } 274 } 275 } 276 } 277} 278