1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright (c) 2016 by Delphix. All rights reserved.
25 */
26
27 /*
28 * The core of ilbd daemon is a single-threaded event loop using
29 * event completion framework; it receives requests from client using
30 * the libilb functions, handles timeouts, initiates health checks, and
31 * populates the kernel state.
32 *
33 * The daemon has the following privileges (in addition to the basic ones):
34 *
35 * PRIV_PROC_OWNER, PRIV_NET_ICMPACCESS,
36 * PRIV_SYS_IP_CONFIG, PRIV_PROC_AUDIT
37 *
38 * The aforementioned privileges will be specified in the SMF manifest.
39 *
40 * AF_UNIX socket is used for IPC between libilb and this daemon as
41 * both processes will run on the same machine.
42 *
43 * To do health check, the daemon will create a timer for every health
44 * check probe. Each of these timers will be associated with the
45 * event port. When a timer goes off, the daemon will initiate a
46 * pipe to a separate process to execute the specific health check
47 * probe. This new process will run with the same user-id as that of
48 * ilbd daemon and will inherit all the privileges from the ilbd
49 * daemon parent process except the following:
50 *
51 * PRIV_PROC_OWNER, PRIV_PROC_AUDIT
52 *
53 * All health checks, will be implemented as external methods
54 * (binary or script). The following arguments will be passed
55 * to external methods:
56 *
57 * $1 VIP (literal IPv4 or IPv6 address)
58 * $2 Server IP (literal IPv4 or IPv6 address)
59 * $3 Protocol (UDP, TCP as a string)
60 * $4 The load balance mode, "DSR", "NAT", "HALF_NAT"
61 * $5 Numeric port range
62 * $6 maximum time (in seconds) the method
63 * should wait before returning failure. If the method runs for
64 * longer, it may be killed, and the test considered failed.
65 *
66 * Upon success, a health check method should print the RTT to the
67 * it finds to its STDOUT for ilbd to consume. The implicit unit
68 * is microseconds but only the number needs to be printed. If it
69 * cannot find the RTT, it should print 0. If the method decides
70 * that the server is dead, it should print -1 to its STDOUT.
71 *
72 * By default, an user-supplied health check probe process will
73 * also run with the same set of privileges as ILB's built-in
74 * probes. If the administrator has an user-supplied health check
75 * program that requires a larger privilege set, they will have
76 * to implement setuid program.
77 *
78 * Each health check will have a timeout, such that if the health
79 * check process is hung, it will be killed after the timeout interval
80 * and the daemon will notify the kernel ILB engine of the server's
81 * unresponsiveness, so that load distribution can be appropriately
82 * adjusted. If on the other hand the health check is successful
83 * the timeout timer is cancelled.
84 */
85
86 #include <stdio.h>
87 #include <stdlib.h>
88 #include <strings.h>
89 #include <libgen.h>
90 #include <fcntl.h>
91 #include <stddef.h>
92 #include <signal.h>
93 #include <port.h>
94 #include <ctype.h>
95 #include <sys/types.h>
96 #include <sys/wait.h>
97 #include <sys/stat.h>
98 #include <sys/note.h>
99 #include <sys/resource.h>
100 #include <unistd.h>
101 #include <sys/socket.h>
102 #include <errno.h>
103 #include <ucred.h>
104 #include <priv_utils.h>
105 #include <net/if.h>
106 #include <libilb.h>
107 #include <assert.h>
108 #include <inet/ilb.h>
109 #include <libintl.h>
110 #include <fcntl.h>
111 #include <rpcsvc/daemon_utils.h>
112 #include "libilb_impl.h"
113 #include "ilbd.h"
114
115 /*
116 * NOTE: The following needs to be kept up to date.
117 */
118 #define ILBD_VERSION "1.0"
119 #define ILBD_COPYRIGHT \
120 "Copyright (c) 2005, 2010, Oracle and/or its affiliates. " \
121 "All rights reserved.\n"
122
123 /*
124 * Global reply buffer to client request. Note that ilbd is single threaded,
125 * so a global buffer is OK. If ilbd becomes multi-threaded, this needs to
126 * be changed.
127 */
128 static uint32_t reply_buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
129
130 static void
ilbd_free_cli(ilbd_client_t * cli)131 ilbd_free_cli(ilbd_client_t *cli)
132 {
133 (void) close(cli->cli_sd);
134 if (cli->cli_cmd == ILBD_SHOW_NAT)
135 ilbd_show_nat_cleanup();
136 if (cli->cli_cmd == ILBD_SHOW_PERSIST)
137 ilbd_show_sticky_cleanup();
138 if (cli->cli_saved_reply != NULL)
139 free(cli->cli_saved_reply);
140 if (cli->cli_peer_ucredp != NULL)
141 ucred_free(cli->cli_peer_ucredp);
142 free(cli->cli_pw_buf);
143 free(cli);
144 }
145
146 static void
ilbd_reset_kernel_state(void)147 ilbd_reset_kernel_state(void)
148 {
149 ilb_status_t rc;
150 ilb_name_cmd_t kcmd;
151
152 kcmd.cmd = ILB_DESTROY_RULE;
153 kcmd.flags = ILB_RULE_ALLRULES;
154 kcmd.name[0] = '\0';
155
156 rc = do_ioctl(&kcmd, 0);
157 if (rc != ILB_STATUS_OK)
158 logdebug("ilbd_reset_kernel_state: do_ioctl failed: %s",
159 strerror(errno));
160 }
161
162 /* Signal handler to do clean up. */
163 /* ARGSUSED */
164 static void
ilbd_cleanup(int sig)165 ilbd_cleanup(int sig)
166 {
167 (void) remove(SOCKET_PATH);
168 ilbd_reset_kernel_state();
169 exit(0);
170 }
171
172 /*
173 * Create a socket and return it to caller. If there is a failure, this
174 * function calls exit(2). Hence it always returns a valid listener socket.
175 *
176 * Note that this function is called before ilbd becomes a daemon. So
177 * we call perror(3C) to print out error message directly so that SMF can
178 * catch them.
179 */
180 static int
ilbd_create_client_socket(void)181 ilbd_create_client_socket(void)
182 {
183 int s;
184 mode_t omask;
185 struct sockaddr_un sa;
186 int sobufsz;
187
188 s = socket(PF_UNIX, SOCK_SEQPACKET, 0);
189 if (s == -1) {
190 perror("ilbd_create_client_socket: socket to"
191 " client failed");
192 exit(errno);
193 }
194 if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
195 perror("ilbd_create_client_socket: fcntl(FD_CLOEXEC)");
196 exit(errno);
197 }
198
199 sobufsz = ILBD_MSG_SIZE;
200 if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobufsz,
201 sizeof (sobufsz)) != 0) {
202 perror("ilbd_creat_client_socket: setsockopt(SO_SNDBUF) "
203 "failed");
204 exit(errno);
205 }
206 if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobufsz,
207 sizeof (sobufsz)) != 0) {
208 perror("ilbd_creat_client_socket: setsockopt(SO_RCVBUF) "
209 "failed");
210 exit(errno);
211 }
212
213 /*
214 * since everybody can talk to us, we need to open up permissions
215 * we check peer privileges on a per-operation basis.
216 * This is no security issue as long as we're single-threaded.
217 */
218 omask = umask(0);
219
220 /* just in case we didn't clean up properly after last exit */
221 (void) remove(SOCKET_PATH);
222
223 bzero(&sa, sizeof (sa));
224 sa.sun_family = AF_UNIX;
225 (void) strlcpy(sa.sun_path, SOCKET_PATH, sizeof (sa.sun_path));
226
227 if (bind(s, (struct sockaddr *)&sa, sizeof (sa)) != 0) {
228 perror("ilbd_create_client_socket(): bind to client"
229 " socket failed");
230 exit(errno);
231 }
232
233 /* re-instate old umask */
234 (void) umask(omask);
235
236 #define QLEN 16
237
238 if (listen(s, QLEN) != 0) {
239 perror("ilbd_create_client_socket: listen to client"
240 " socket failed");
241 exit(errno);
242 }
243
244 (void) signal(SIGHUP, SIG_IGN);
245 (void) signal(SIGPIPE, SIG_IGN);
246 (void) signal(SIGSTOP, SIG_IGN);
247 (void) signal(SIGTSTP, SIG_IGN);
248 (void) signal(SIGTTIN, SIG_IGN);
249 (void) signal(SIGTTOU, SIG_IGN);
250
251 (void) signal(SIGINT, ilbd_cleanup);
252 (void) signal(SIGTERM, ilbd_cleanup);
253 (void) signal(SIGQUIT, ilbd_cleanup);
254
255 return (s);
256 }
257
258 /*
259 * Return the minimum size of a given request. The returned size does not
260 * include the variable part of a request.
261 */
262 static size_t
ilbd_cmd_size(const ilb_comm_t * ic)263 ilbd_cmd_size(const ilb_comm_t *ic)
264 {
265 size_t cmd_sz;
266
267 cmd_sz = sizeof (*ic);
268 switch (ic->ic_cmd) {
269 case ILBD_RETRIEVE_SG_NAMES:
270 case ILBD_RETRIEVE_RULE_NAMES:
271 case ILBD_RETRIEVE_HC_NAMES:
272 case ILBD_CMD_OK:
273 break;
274 case ILBD_CMD_ERROR:
275 cmd_sz += sizeof (ilb_status_t);
276 break;
277 case ILBD_RETRIEVE_SG_HOSTS:
278 case ILBD_CREATE_SERVERGROUP:
279 case ILBD_DESTROY_SERVERGROUP:
280 case ILBD_DESTROY_RULE:
281 case ILBD_ENABLE_RULE:
282 case ILBD_DISABLE_RULE:
283 case ILBD_RETRIEVE_RULE:
284 case ILBD_DESTROY_HC:
285 case ILBD_GET_HC_INFO:
286 case ILBD_GET_HC_SRVS:
287 cmd_sz += sizeof (ilbd_name_t);
288 break;
289 case ILBD_ENABLE_SERVER:
290 case ILBD_DISABLE_SERVER:
291 case ILBD_ADD_SERVER_TO_GROUP:
292 case ILBD_REM_SERVER_FROM_GROUP:
293 cmd_sz += sizeof (ilb_sg_info_t);
294 break;
295 case ILBD_SRV_ADDR2ID:
296 case ILBD_SRV_ID2ADDR:
297 cmd_sz += sizeof (ilb_sg_info_t) + sizeof (ilb_sg_srv_t);
298 break;
299 case ILBD_CREATE_RULE:
300 cmd_sz += sizeof (ilb_rule_info_t);
301 break;
302 case ILBD_CREATE_HC:
303 cmd_sz += sizeof (ilb_hc_info_t);
304 break;
305 case ILBD_SHOW_NAT:
306 case ILBD_SHOW_PERSIST:
307 cmd_sz += sizeof (ilb_show_info_t);
308 break;
309 }
310
311 return (cmd_sz);
312 }
313
314 /*
315 * Given a request and its size, check that the size is big enough to
316 * contain the variable part of a request.
317 */
318 static ilb_status_t
ilbd_check_req_size(ilb_comm_t * ic,size_t ic_sz)319 ilbd_check_req_size(ilb_comm_t *ic, size_t ic_sz)
320 {
321 ilb_status_t rc = ILB_STATUS_OK;
322 ilb_sg_info_t *sg_info;
323 ilbd_namelist_t *nlist;
324
325 switch (ic->ic_cmd) {
326 case ILBD_CREATE_SERVERGROUP:
327 case ILBD_ENABLE_SERVER:
328 case ILBD_DISABLE_SERVER:
329 case ILBD_ADD_SERVER_TO_GROUP:
330 case ILBD_REM_SERVER_FROM_GROUP:
331 sg_info = (ilb_sg_info_t *)&ic->ic_data;
332
333 if (ic_sz < ilbd_cmd_size(ic) + sg_info->sg_srvcount *
334 sizeof (ilb_sg_srv_t)) {
335 rc = ILB_STATUS_EINVAL;
336 }
337 break;
338 case ILBD_ENABLE_RULE:
339 case ILBD_DISABLE_RULE:
340 case ILBD_DESTROY_RULE:
341 nlist = (ilbd_namelist_t *)&ic->ic_data;
342
343 if (ic_sz < ilbd_cmd_size(ic) + nlist->ilbl_count *
344 sizeof (ilbd_name_t)) {
345 rc = ILB_STATUS_EINVAL;
346 }
347 break;
348 }
349 return (rc);
350 }
351
352 /*
353 * this function *relies* on a complete message/data struct
354 * being passed in (currently via the SOCK_SEQPACKET socket type).
355 *
356 * Note that the size of ip is at most ILBD_MSG_SIZE.
357 */
358 static ilb_status_t
consume_common_struct(ilb_comm_t * ic,size_t ic_sz,ilbd_client_t * cli,int ev_port)359 consume_common_struct(ilb_comm_t *ic, size_t ic_sz, ilbd_client_t *cli,
360 int ev_port)
361 {
362 ilb_status_t rc;
363 struct passwd *ps;
364 size_t rbufsz;
365 ssize_t ret;
366 boolean_t standard_reply = B_TRUE;
367 ilbd_name_t name;
368
369 /*
370 * cli_ev must be overridden during handling of individual commands,
371 * if there's a special need; otherwise, leave this for
372 * the "default" case
373 */
374 cli->cli_ev = ILBD_EVENT_REQ;
375
376 ps = &cli->cli_pw;
377 rbufsz = ILBD_MSG_SIZE;
378
379 /* Sanity check on the size of the static part of a request. */
380 if (ic_sz < ilbd_cmd_size(ic)) {
381 rc = ILB_STATUS_EINVAL;
382 goto out;
383 }
384
385 switch (ic->ic_cmd) {
386 case ILBD_CREATE_SERVERGROUP: {
387 ilb_sg_info_t sg_info;
388
389 /*
390 * ilbd_create_sg() only needs the sg_name field. But it
391 * takes in a ilb_sg_info_t because it is used as a callback
392 * in ilbd_walk_sg_pgs().
393 */
394 (void) strlcpy(sg_info.sg_name, (char *)&(ic->ic_data),
395 sizeof (sg_info.sg_name));
396 rc = ilbd_create_sg(&sg_info, ev_port, ps,
397 cli->cli_peer_ucredp);
398 break;
399 }
400
401 case ILBD_DESTROY_SERVERGROUP:
402 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
403 rc = ilbd_destroy_sg(name, ps, cli->cli_peer_ucredp);
404 break;
405
406 case ILBD_ADD_SERVER_TO_GROUP:
407 if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
408 break;
409 rc = ilbd_add_server_to_group((ilb_sg_info_t *)&ic->ic_data,
410 ev_port, ps, cli->cli_peer_ucredp);
411 break;
412
413 case ILBD_REM_SERVER_FROM_GROUP:
414 if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
415 break;
416 rc = ilbd_rem_server_from_group((ilb_sg_info_t *)&ic->ic_data,
417 ev_port, ps, cli->cli_peer_ucredp);
418 break;
419
420 case ILBD_ENABLE_SERVER:
421 if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
422 break;
423 rc = ilbd_enable_server((ilb_sg_info_t *)&ic->ic_data, ps,
424 cli->cli_peer_ucredp);
425 break;
426
427 case ILBD_DISABLE_SERVER:
428 if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
429 break;
430 rc = ilbd_disable_server((ilb_sg_info_t *)&ic->ic_data, ps,
431 cli->cli_peer_ucredp);
432 break;
433
434 case ILBD_SRV_ADDR2ID:
435 rc = ilbd_address_to_srvID((ilb_sg_info_t *)&ic->ic_data,
436 reply_buf, &rbufsz);
437 if (rc == ILB_STATUS_OK)
438 standard_reply = B_FALSE;
439 break;
440
441 case ILBD_SRV_ID2ADDR:
442 rc = ilbd_srvID_to_address((ilb_sg_info_t *)&ic->ic_data,
443 reply_buf, &rbufsz);
444 if (rc == ILB_STATUS_OK)
445 standard_reply = B_FALSE;
446 break;
447
448 case ILBD_RETRIEVE_SG_HOSTS:
449 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
450 rc = ilbd_retrieve_sg_hosts(name, reply_buf, &rbufsz);
451 if (rc == ILB_STATUS_OK)
452 standard_reply = B_FALSE;
453 break;
454
455 case ILBD_RETRIEVE_SG_NAMES:
456 case ILBD_RETRIEVE_RULE_NAMES:
457 case ILBD_RETRIEVE_HC_NAMES:
458 rc = ilbd_retrieve_names(ic->ic_cmd, reply_buf, &rbufsz);
459 if (rc == ILB_STATUS_OK)
460 standard_reply = B_FALSE;
461 break;
462
463 case ILBD_CREATE_RULE:
464 rc = ilbd_create_rule((ilb_rule_info_t *)&ic->ic_data, ev_port,
465 ps, cli->cli_peer_ucredp);
466 break;
467
468 case ILBD_DESTROY_RULE:
469 /* Copy the name to ensure that name is NULL terminated. */
470 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
471 rc = ilbd_destroy_rule(name, ps, cli->cli_peer_ucredp);
472 break;
473
474 case ILBD_ENABLE_RULE:
475 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
476 rc = ilbd_enable_rule(name, ps, cli->cli_peer_ucredp);
477 break;
478
479 case ILBD_DISABLE_RULE:
480 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
481 rc = ilbd_disable_rule(name, ps, cli->cli_peer_ucredp);
482 break;
483
484 case ILBD_RETRIEVE_RULE:
485 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
486 rc = ilbd_retrieve_rule(name, reply_buf, &rbufsz);
487 if (rc == ILB_STATUS_OK)
488 standard_reply = B_FALSE;
489 break;
490
491 case ILBD_CREATE_HC:
492 rc = ilbd_create_hc((ilb_hc_info_t *)&ic->ic_data, ev_port, ps,
493 cli->cli_peer_ucredp);
494 break;
495
496 case ILBD_DESTROY_HC:
497 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
498 rc = ilbd_destroy_hc(name, ps, cli->cli_peer_ucredp);
499 break;
500
501 case ILBD_GET_HC_INFO:
502 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
503 rc = ilbd_get_hc_info(name, reply_buf, &rbufsz);
504 if (rc == ILB_STATUS_OK)
505 standard_reply = B_FALSE;
506 break;
507
508 case ILBD_GET_HC_SRVS:
509 (void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
510 rc = ilbd_get_hc_srvs(name, reply_buf, &rbufsz);
511 if (rc == ILB_STATUS_OK)
512 standard_reply = B_FALSE;
513 break;
514
515 case ILBD_SHOW_NAT:
516 rc = ilbd_show_nat(cli, ic, reply_buf, &rbufsz);
517 if (rc == ILB_STATUS_OK)
518 standard_reply = B_FALSE;
519 break;
520
521 case ILBD_SHOW_PERSIST:
522 rc = ilbd_show_sticky(cli, ic, reply_buf, &rbufsz);
523 if (rc == ILB_STATUS_OK)
524 standard_reply = B_FALSE;
525 break;
526
527 default:
528 logdebug("consume_common_struct: unknown command");
529 rc = ILB_STATUS_INVAL_CMD;
530 break;
531 }
532
533 out:
534 /*
535 * The message exchange is always in pairs, request/response. If
536 * a transaction requires multiple exchanges, the client will send
537 * in multiple requests to get multiple responses. The show-nat and
538 * show-persist request are examples of this. The end of transaction
539 * is marked with ic_flags set to ILB_COMM_END.
540 */
541
542 /* This is the standard reply. */
543 if (standard_reply) {
544 if (rc == ILB_STATUS_OK)
545 ilbd_reply_ok(reply_buf, &rbufsz);
546 else
547 ilbd_reply_err(reply_buf, &rbufsz, rc);
548 }
549
550 if ((ret = send(cli->cli_sd, reply_buf, rbufsz, 0)) != rbufsz) {
551 if (ret == -1) {
552 if (errno != EWOULDBLOCK) {
553 logdebug("consume_common_struct: send: %s",
554 strerror(errno));
555 rc = ILB_STATUS_SEND;
556 goto err_out;
557 }
558 /*
559 * The reply is blocked, save the reply. handle_req()
560 * will associate the event port for the re-send.
561 */
562 assert(cli->cli_saved_reply == NULL);
563 if ((cli->cli_saved_reply = malloc(rbufsz)) == NULL) {
564 /*
565 * Set the error to ILB_STATUS_SEND so that
566 * handle_req() will free the client.
567 */
568 logdebug("consume_common_struct: failure to "
569 "allocate memory to save reply");
570 rc = ILB_STATUS_SEND;
571 goto err_out;
572 }
573 bcopy(reply_buf, cli->cli_saved_reply, rbufsz);
574 cli->cli_saved_size = rbufsz;
575 return (ILB_STATUS_EWOULDBLOCK);
576 }
577 }
578 err_out:
579 return (rc);
580 }
581
582 /*
583 * Accept a new client request. A struct ilbd_client_t is allocated to
584 * store the client info. The accepted socket is port_associate() with
585 * the given port. And the allocated ilbd_client_t struct is passed as
586 * the user pointer.
587 */
588 static void
new_req(int ev_port,int listener,void * ev_obj)589 new_req(int ev_port, int listener, void *ev_obj)
590 {
591 struct sockaddr sa;
592 int sa_len;
593 int new_sd;
594 int sflags;
595 ilbd_client_t *cli = NULL;
596 int res;
597 uid_t uid;
598
599 sa_len = sizeof (sa);
600 if ((new_sd = accept(listener, &sa, &sa_len)) == -1) {
601 /* don't log if we're out of file descriptors */
602 if (errno != EINTR && errno != EMFILE)
603 logperror("new_req: accept failed");
604 goto done;
605 }
606
607 /* Set the new socket to be non-blocking. */
608 if ((sflags = fcntl(new_sd, F_GETFL, 0)) == -1) {
609 logperror("new_req: fcntl(F_GETFL)");
610 goto clean_up;
611 }
612 if (fcntl(new_sd, F_SETFL, sflags | O_NONBLOCK) == -1) {
613 logperror("new_req: fcntl(F_SETFL)");
614 goto clean_up;
615 }
616 if (fcntl(new_sd, F_SETFD, FD_CLOEXEC) == -1) {
617 logperror("new_req: fcntl(FD_CLOEXEC)");
618 goto clean_up;
619 }
620 if ((cli = calloc(1, sizeof (ilbd_client_t))) == NULL) {
621 logerr("new_req: malloc(ilbd_client_t)");
622 goto clean_up;
623 }
624 res = getpeerucred(new_sd, &cli->cli_peer_ucredp);
625 if (res == -1) {
626 logperror("new_req: getpeerucred failed");
627 goto clean_up;
628 }
629 if ((uid = ucred_getruid(cli->cli_peer_ucredp)) == (uid_t)-1) {
630 logperror("new_req: ucred_getruid failed");
631 goto clean_up;
632 }
633 cli->cli_pw_bufsz = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX);
634 if ((cli->cli_pw_buf = malloc(cli->cli_pw_bufsz)) == NULL) {
635 logerr("new_req: malloc(cli_pw_buf)");
636 goto clean_up;
637 }
638 if (getpwuid_r(uid, &cli->cli_pw, cli->cli_pw_buf,
639 cli->cli_pw_bufsz) == NULL) {
640 logperror("new_req: invalid user");
641 goto clean_up;
642 }
643 cli->cli_ev = ILBD_EVENT_REQ;
644 cli->cli_sd = new_sd;
645 cli->cli_cmd = ILBD_BAD_CMD;
646 cli->cli_saved_reply = NULL;
647 cli->cli_saved_size = 0;
648 if (port_associate(ev_port, PORT_SOURCE_FD, new_sd, POLLRDNORM,
649 cli) == -1) {
650 logperror("new_req: port_associate(cli) failed");
651 clean_up:
652 if (cli != NULL) {
653 if (cli->cli_peer_ucredp != NULL)
654 ucred_free(cli->cli_peer_ucredp);
655 free(cli->cli_pw_buf);
656 free(cli);
657 }
658 (void) close(new_sd);
659 }
660
661 done:
662 /* Re-associate the listener with the event port. */
663 if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
664 ev_obj) == -1) {
665 logperror("new_req: port_associate(listener) failed");
666 exit(1);
667 }
668 }
669
670 static void
handle_req(int ev_port,ilbd_event_t event,ilbd_client_t * cli)671 handle_req(int ev_port, ilbd_event_t event, ilbd_client_t *cli)
672 {
673 /* All request should be smaller than ILBD_MSG_SIZE */
674 union {
675 ilb_comm_t ic;
676 uint32_t buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
677 } ic_u;
678 int rc = ILB_STATUS_OK;
679 ssize_t r;
680
681 if (event == ILBD_EVENT_REQ) {
682 /*
683 * Something is wrong with the client since there is a
684 * pending reply, the client should not send us another
685 * request. Kill this client.
686 */
687 if (cli->cli_saved_reply != NULL) {
688 logerr("handle_req: misbehaving client, more than one "
689 "outstanding request");
690 rc = ILB_STATUS_INTERNAL;
691 goto err_out;
692 }
693
694 /*
695 * Our socket is message based so we should be able
696 * to get the request in one single read.
697 */
698 r = recv(cli->cli_sd, (void *)ic_u.buf, sizeof (ic_u.buf), 0);
699 if (r < 0) {
700 if (errno != EINTR) {
701 logperror("handle_req: read failed");
702 rc = ILB_STATUS_READ;
703 goto err_out;
704 }
705 /*
706 * If interrupted, just re-associate the cli_sd
707 * with the port.
708 */
709 goto done;
710 }
711 cli->cli_cmd = ic_u.ic.ic_cmd;
712
713 rc = consume_common_struct(&ic_u.ic, r, cli, ev_port);
714 if (rc == ILB_STATUS_EWOULDBLOCK)
715 goto blocked;
716 /* Fatal error communicating with client, free it. */
717 if (rc == ILB_STATUS_SEND)
718 goto err_out;
719 } else {
720 assert(event == ILBD_EVENT_REP_OK);
721 assert(cli->cli_saved_reply != NULL);
722
723 /*
724 * The reply to client was previously blocked, we will
725 * send again.
726 */
727 if (send(cli->cli_sd, cli->cli_saved_reply,
728 cli->cli_saved_size, 0) != cli->cli_saved_size) {
729 if (errno != EWOULDBLOCK) {
730 logdebug("handle_req: send: %s",
731 strerror(errno));
732 rc = ILB_STATUS_SEND;
733 goto err_out;
734 }
735 goto blocked;
736 }
737 free(cli->cli_saved_reply);
738 cli->cli_saved_reply = NULL;
739 cli->cli_saved_size = 0;
740 }
741 done:
742 /* Re-associate with the event port for more requests. */
743 cli->cli_ev = ILBD_EVENT_REQ;
744 if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd,
745 POLLRDNORM, cli) == -1) {
746 logperror("handle_req: port_associate(POLLRDNORM)");
747 rc = ILB_STATUS_INTERNAL;
748 goto err_out;
749 }
750 return;
751
752 blocked:
753 /* Re-associate with the event port. */
754 cli->cli_ev = ILBD_EVENT_REP_OK;
755 if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd, POLLWRNORM,
756 cli) == -1) {
757 logperror("handle_req: port_associate(POLLWRNORM)");
758 rc = ILB_STATUS_INTERNAL;
759 goto err_out;
760 }
761 return;
762
763 err_out:
764 ilbd_free_cli(cli);
765 }
766
767 static void
i_ilbd_read_config(int ev_port)768 i_ilbd_read_config(int ev_port)
769 {
770 logdebug("i_ilbd_read_config: port %d", ev_port);
771 (void) ilbd_walk_sg_pgs(ilbd_create_sg, &ev_port, NULL);
772 (void) ilbd_walk_hc_pgs(ilbd_create_hc, &ev_port, NULL);
773 (void) ilbd_walk_rule_pgs(ilbd_create_rule, &ev_port, NULL);
774 }
775
776 /*
777 * main event loop for ilbd
778 * asserts that argument 'listener' is a server socket ready to accept() on.
779 */
780 static void
main_loop(int listener)781 main_loop(int listener)
782 {
783 port_event_t p_ev;
784 int ev_port, ev_port_obj;
785 ilbd_event_obj_t ev_obj;
786 ilbd_timer_event_obj_t timer_ev_obj;
787
788 ev_port = port_create();
789 if (ev_port == -1) {
790 logperror("main_loop: port_create failed");
791 exit(-1);
792 }
793 ilbd_hc_timer_init(ev_port, &timer_ev_obj);
794
795 ev_obj.ev = ILBD_EVENT_NEW_REQ;
796 if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
797 &ev_obj) == -1) {
798 logperror("main_loop: port_associate failed");
799 exit(1);
800 }
801
802 i_ilbd_read_config(ev_port);
803 ilbd_hc_timer_update(&timer_ev_obj);
804
805 _NOTE(CONSTCOND)
806 while (B_TRUE) {
807 int r;
808 ilbd_event_t event;
809 ilbd_client_t *cli;
810
811 r = port_get(ev_port, &p_ev, NULL);
812 if (r == -1) {
813 if (errno == EINTR)
814 continue;
815 logperror("main_loop: port_get failed");
816 break;
817 }
818
819 ev_port_obj = p_ev.portev_object;
820 event = ((ilbd_event_obj_t *)p_ev.portev_user)->ev;
821
822 switch (event) {
823 case ILBD_EVENT_TIMER:
824 ilbd_hc_timeout();
825 break;
826
827 case ILBD_EVENT_PROBE:
828 ilbd_hc_probe_return(ev_port, ev_port_obj,
829 p_ev.portev_events,
830 (ilbd_hc_probe_event_t *)p_ev.portev_user);
831 break;
832
833 case ILBD_EVENT_NEW_REQ:
834 assert(ev_port_obj == listener);
835 /*
836 * An error happens in the listener. Exit
837 * for now....
838 */
839 if (p_ev.portev_events & (POLLHUP|POLLERR)) {
840 logerr("main_loop: listener error");
841 exit(1);
842 }
843 new_req(ev_port, ev_port_obj, &ev_obj);
844 break;
845
846 case ILBD_EVENT_REP_OK:
847 case ILBD_EVENT_REQ:
848 cli = (ilbd_client_t *)p_ev.portev_user;
849 assert(ev_port_obj == cli->cli_sd);
850
851 /*
852 * An error happens in the newly accepted
853 * client request. Clean up the client.
854 * this also happens when client closes socket,
855 * so not necessarily a reason for alarm
856 */
857 if (p_ev.portev_events & (POLLHUP|POLLERR)) {
858 ilbd_free_cli(cli);
859 break;
860 }
861
862 handle_req(ev_port, event, cli);
863 break;
864
865 default:
866 logerr("main_loop: unknown event %d", event);
867 exit(EXIT_FAILURE);
868 break;
869 }
870
871 ilbd_hc_timer_update(&timer_ev_obj);
872 }
873 }
874
875 static void
i_ilbd_setup_lists(void)876 i_ilbd_setup_lists(void)
877 {
878 i_setup_sg_hlist();
879 i_setup_rule_hlist();
880 i_ilbd_setup_hc_list();
881 }
882
883 /*
884 * Usage message - call only during startup. it will print its
885 * message on stderr and exit
886 */
887 static void
Usage(char * name)888 Usage(char *name)
889 {
890 (void) fprintf(stderr, gettext("Usage: %s [-d|--debug]\n"), name);
891 exit(1);
892 }
893
894 static void
print_version(char * name)895 print_version(char *name)
896 {
897 (void) printf("%s %s\n", basename(name), ILBD_VERSION);
898 (void) printf(gettext(ILBD_COPYRIGHT));
899 exit(0);
900 }
901
902 /*
903 * Increase the file descriptor limit for handling a lot of health check
904 * processes (each requires a pipe).
905 *
906 * Note that this function is called before ilbd becomes a daemon. So
907 * we call perror(3C) to print out error message directly so that SMF
908 * can catch them.
909 */
910 static void
set_rlim(void)911 set_rlim(void)
912 {
913 struct rlimit rlp;
914
915 if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) {
916 perror("ilbd: getrlimit");
917 exit(errno);
918 }
919 rlp.rlim_cur = rlp.rlim_max;
920 if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) {
921 perror("ilbd: setrlimit");
922 exit(errno);
923 }
924 }
925
926 int
main(int argc,char ** argv)927 main(int argc, char **argv)
928 {
929 int s;
930 int c;
931
932 (void) setlocale(LC_ALL, "");
933 #if !defined(TEXT_DOMAIN)
934 #define TEXT_DOMAIN "SYS_TEST"
935 #endif
936 static const char daemon_dir[] = DAEMON_DIR;
937
938 (void) textdomain(TEXT_DOMAIN);
939
940 while ((c = getopt(argc, argv, ":V?d(debug)")) != -1) {
941 switch ((char)c) {
942 case '?': Usage(argv[0]);
943 /* not reached */
944 break;
945 case 'V': print_version(argv[0]);
946 /* not reached */
947 break;
948 case 'd': ilbd_enable_debug();
949 break;
950 default: Usage(argv[0]);
951 /* not reached */
952 break;
953 }
954 }
955
956 /*
957 * Whenever the daemon starts, it needs to start with a clean
958 * slate in the kernel. We need sys_ip_config privilege for
959 * this.
960 */
961 ilbd_reset_kernel_state();
962
963 /* Increase the limit on the number of file descriptors. */
964 set_rlim();
965
966 /*
967 * ilbd daemon starts off as root, just so it can create
968 * /var/run/daemon if one does not exist. After that is done
969 * the daemon switches to "daemon" uid. This is similar to what
970 * rpcbind does.
971 */
972 if (mkdir(daemon_dir, DAEMON_DIR_MODE) == 0 || errno == EEXIST) {
973 (void) chmod(daemon_dir, DAEMON_DIR_MODE);
974 (void) chown(daemon_dir, DAEMON_UID, DAEMON_GID);
975 } else {
976 perror("main: mkdir failed");
977 exit(errno);
978 }
979 /*
980 * Now lets switch ilbd as uid = daemon, gid = daemon with a
981 * trimmed down privilege set
982 */
983 if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
984 DAEMON_UID, DAEMON_GID, PRIV_PROC_OWNER, PRIV_PROC_AUDIT,
985 PRIV_NET_ICMPACCESS, PRIV_SYS_IP_CONFIG, NULL) == -1) {
986 (void) fprintf(stderr, "Insufficient privileges\n");
987 exit(EXIT_FAILURE);
988 }
989
990 /*
991 * Opens a PF_UNIX socket to the client. No privilege needed
992 * for this.
993 */
994 s = ilbd_create_client_socket();
995
996 /*
997 * Daemonify if ilbd is not running with -d option
998 * Need proc_fork privilege for this
999 */
1000 if (!is_debugging_on()) {
1001 logdebug("daemonizing...");
1002 if (daemon(0, 0) != 0) {
1003 logperror("daemon failed");
1004 exit(EXIT_FAILURE);
1005 }
1006 }
1007 (void) priv_set(PRIV_OFF, PRIV_INHERITABLE, PRIV_PROC_OWNER,
1008 PRIV_PROC_AUDIT, NULL);
1009
1010 /* if daemonified then set up syslog */
1011 if (!is_debugging_on())
1012 openlog("ilbd", LOG_PID, LOG_DAEMON);
1013
1014 i_ilbd_setup_lists();
1015
1016 main_loop(s);
1017
1018 /*
1019 * if we come here, then we experienced an error or a shutdown
1020 * indicator, so clean up after ourselves.
1021 */
1022 logdebug("main(): terminating");
1023
1024 (void) remove(SOCKET_PATH);
1025 ilbd_reset_kernel_state();
1026
1027 return (0);
1028 }
1029