xref: /illumos-gate/usr/src/cmd/ipf/svc/ipfd.c (revision eb9a1df2aeb866bf1de4494433b6d7e5fa07b3ae)
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 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * This file delivers svc.ipfd, the daemon that monitors changes to
29  * firewall capable services and requests IPfilter configuration update
30  * on behalf of the service. Essentially, the daemon listens for
31  * service changes and forks the program that update a service's
32  * IPfilter configuration.
33  *
34  * - A firewall capable SMF service can restrict network access to its
35  *   service by providing a firewall policy that can be translated into
36  *   a set of IPfilter rules. The mentioned firewall policy is stored in
37  *   firewall_config and firewall_context property groups. If one of these
38  *   two property groups exist, the service is considered to be firewall
39  *   capable.
40  *
41  * - A request to update service's IPfilter configuration is made for
42  *   actions that affect service's configuration or running state. The
43  *   actions are:
44  *	- enable/disable
45  *	- refresh/restart
46  *	- maintenance/clear maintenance
47  *
48  * Lacking a generic SMF mechanism to observe service state changes, the
49  * daemon observe change events by listening to changes to 'general',
50  * 'general_ovr', and 'restarter_actions' property groups. This is not a
51  * stable interface and should be replaced when a SMF supported mechanism
52  * becomes available.
53  *
54  * - The program responsible for updating service's IPfilter configuration
55  *   is /lib/svc/method/ipfilter. This program is called as:
56  *
57  *   /lib/svc/method/ipfilter fw_update fmri
58  *
59  *   where fmri the instance fmri of the service to be updated.
60  */
61 
62 #include <stdio.h>
63 #include <unistd.h>
64 #include <stdlib.h>
65 #include <assert.h>
66 #include <errno.h>
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <sys/wait.h>
70 #include <fcntl.h>
71 #include <umem.h>
72 #include <libscf.h>
73 #include <libscf_priv.h>
74 #include <signal.h>
75 #include <string.h>
76 #include <syslog.h>
77 
78 #define	IPFILTER_FMRI		"svc:/network/ipfilter:default"
79 #define	RPCBIND_FMRI		"svc:/network/rpc/bind:default"
80 #define	IPF_UPDATE_CMD		"/lib/svc/method/ipfilter"
81 
82 #define	SCF_SNAPSHOT_RUNNING	"running"
83 #define	SCF_PG_FW_CONTEXT	"firewall_context"
84 #define	SCF_PG_FW_CONFIG	"firewall_config"
85 #define	SCF_PG_REFRESH		"refresh"
86 #define	SCF_PG_INETD		"inetd"
87 
88 #define	SCF_PROPERTY_ISRPC	"isrpc"
89 
90 #define	MAX_RETRY		7
91 #define	DEV_NULL		"/dev/null"
92 
93 static scf_handle_t *h;
94 static ssize_t max_scf_fmri_size;
95 static ssize_t max_scf_name_size;
96 
97 static scf_instance_t *inst;
98 static scf_snapshot_t *snap;
99 static scf_propertygroup_t *scratch_pg;
100 static scf_property_t *scratch_prop;
101 static scf_value_t *scratch_v;
102 
103 static char *scratch_fmri;
104 static char *scratch_name;
105 
106 static const char *all_props[] = {
107 	SCF_PROPERTY_REFRESH, SCF_PROPERTY_RESTART, SCF_PROPERTY_MAINT_ON,
108 	SCF_PROPERTY_MAINT_ON_IMMEDIATE, SCF_PROPERTY_MAINT_ON_IMMTEMP,
109 	SCF_PROPERTY_MAINT_ON_TEMPORARY, SCF_PROPERTY_MAINT_OFF
110 };
111 #define	ALL_PROPS_CNT		7
112 
113 static const char *maint_props[] = {
114 	SCF_PROPERTY_REFRESH, SCF_PROPERTY_RESTART, SCF_PROPERTY_MAINT_OFF };
115 #define	MAINT_PROPS_CNT		3
116 
117 static int ipfilter_update(const char *);
118 
119 static int
120 daemonize_self(void)
121 {
122 	pid_t pid;
123 	int fd;
124 
125 	(void) close(STDIN_FILENO);
126 
127 	if ((fd = open(DEV_NULL, O_RDONLY)) == -1) {
128 		(void) printf("Could not open /dev/null: %s\n",
129 		    strerror(errno));
130 	} else if (fd != STDIN_FILENO) {
131 		(void) dup2(fd, STDIN_FILENO);
132 		(void) close(fd);
133 	}
134 	(void) dup2(STDERR_FILENO, STDOUT_FILENO);
135 	closefrom(3);
136 
137 	if ((pid = fork1()) < 0) {
138 		(void) printf("fork() failed: %s\n", strerror(errno));
139 		return (1);
140 	}
141 
142 	if (pid != 0)
143 		exit(0);
144 
145 	(void) setsid();
146 	(void) chdir("/");
147 
148 	return (0);
149 }
150 
151 static void
152 repository_rebind(scf_handle_t *hndl)
153 {
154 	int c = 0;
155 
156 	(void) scf_handle_unbind(hndl);
157 	while ((scf_handle_bind(hndl)) != 0) {
158 		if (c > MAX_RETRY) {
159 			syslog(LOG_ERR | LOG_DAEMON, "Repository access "
160 			    "unavailable. Couldn't bind handle: %s\n",
161 			    scf_strerror(scf_error()));
162 			syslog(LOG_ERR | LOG_DAEMON, "Service specific"
163 			    "IPfilter configuration may not be updated "
164 			    "properly\n");
165 
166 			exit(1);
167 		} else {
168 			c++;
169 		}
170 
171 		(void) sleep(1);
172 	}
173 }
174 
175 static void
176 repository_notify_setup(scf_handle_t *h)
177 {
178 	for (;;) {
179 		if (_scf_notify_add_pgtype(h, SCF_GROUP_FRAMEWORK) ==
180 		    SCF_SUCCESS)
181 			break;
182 
183 		switch (scf_error()) {
184 		case SCF_ERROR_CONNECTION_BROKEN:
185 			repository_rebind(h);
186 			break;
187 
188 		case SCF_ERROR_NO_RESOURCES:
189 			(void) sleep(1);
190 			break;
191 
192 		default:
193 			syslog(LOG_ERR | LOG_DAEMON,
194 			    "Abort: Couldn't set up repository notification "
195 			    "for pg type %s: %s\n", SCF_GROUP_FRAMEWORK,
196 			    scf_strerror(scf_error()));
197 			abort();
198 		}
199 	}
200 }
201 
202 /*
203  * If the repository connection is lost, rebind and re-setup repository
204  * notification. During the repository connection outage, services that
205  * changed states wouldn't get the corresponding firewall update. To make
206  * we're not out of sync, update the entire system firewall configuration,
207  * invoke ipfilter_update(IPFILTER_FMRI).
208  */
209 static void
210 repository_setup()
211 {
212 	repository_rebind(h);
213 	repository_notify_setup(h);
214 	if (ipfilter_update(IPFILTER_FMRI) == -1) {
215 		syslog(LOG_ERR | LOG_DAEMON,
216 		    "Failed to reconfigure system firewall.\n");
217 	}
218 }
219 
220 static int
221 pg_get_prop_value(const scf_propertygroup_t *pg, const char *pname,
222     scf_value_t *v)
223 {
224 	if (pg == NULL || pname == NULL || v == NULL)
225 		return (-1);
226 
227 	if (scf_pg_get_property(pg, pname, scratch_prop) == -1 ||
228 	    scf_property_get_value(scratch_prop, v) == -1) {
229 		switch (scf_error()) {
230 		case SCF_ERROR_NOT_FOUND:
231 		case SCF_ERROR_DELETED:
232 			break;
233 
234 		default:
235 			syslog(LOG_ERR | LOG_DAEMON,
236 			    "scf_pg_get_property failed for %s: %s\n",
237 			    pname, scf_strerror(scf_error()));
238 		}
239 		return (-1);
240 	}
241 	return (0);
242 }
243 
244 static int
245 is_correct_event(const char *fmri, const scf_propertygroup_t *pg,
246     const boolean_t isrpc)
247 {
248 	char *state = NULL;
249 	const char **proplist = all_props;
250 	int prop_cnt = ALL_PROPS_CNT;
251 
252 	int i, ret = 0;
253 
254 	if (scf_pg_get_name(pg, scratch_name, max_scf_name_size) < 0) {
255 		syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_name failed: %s\n",
256 		    scf_strerror(scf_error()));
257 		return (-1);
258 	}
259 
260 	/*
261 	 * We care about enable, disable, and refresh since that's
262 	 * when we activate, deactivate, or change firewall policy.
263 	 *
264 	 *  - enable/disable -> change in "general" or "general_ovr"
265 	 *  - refresh/restart -> change in "restarter_actions"
266 	 */
267 	if (strcmp(scratch_name, SCF_PG_GENERAL) == 0 ||
268 	    strcmp(scratch_name, SCF_PG_GENERAL_OVR) == 0) {
269 		syslog(LOG_DEBUG | LOG_DAEMON, "Action: %s", scratch_name);
270 		return (1);
271 	}
272 
273 	if ((state = smf_get_state(fmri)) == NULL) {
274 		syslog(LOG_ERR | LOG_DAEMON, "smf_get_state failed for %s: "
275 		    "%s\n", fmri, scf_strerror(scf_error()));
276 		return (-1);
277 	}
278 
279 	syslog(LOG_DEBUG | LOG_DAEMON, "%s STATE: %s \n", fmri, state);
280 	if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) {
281 		proplist = maint_props;
282 		prop_cnt = MAINT_PROPS_CNT;
283 	}
284 
285 	/*
286 	 * Only concerned with refresh, restart, and maint on|off actions.
287 	 * RPC services are restarted whenever rpc/bind restarts so it's
288 	 * an automatic valid event for RPC services.
289 	 */
290 	if (isrpc) {
291 		ret = 1;
292 		goto out;
293 	} else if (strcmp(scratch_name, SCF_PG_RESTARTER_ACTIONS) == 0) {
294 		for (i = 0; i < prop_cnt; i++) {
295 			if (pg_get_prop_value(pg, proplist[i],
296 			    scratch_v) == 0) {
297 				syslog(LOG_DEBUG | LOG_DAEMON, "Action: %s/%s",
298 				    scratch_name, proplist[i]);
299 
300 				ret = 1;
301 				goto out;
302 			}
303 		}
304 	}
305 
306 out:
307 	if (state)
308 		free(state);
309 
310 	return (ret);
311 }
312 
313 static int
314 ipfilter_update(const char *fmri)
315 {
316 	pid_t pid;
317 	int status, ret = 0;
318 
319 	syslog(LOG_DEBUG | LOG_DAEMON, "ipfilter_update: %s\n", fmri);
320 
321 	/*
322 	 * Start refresh in another process
323 	 */
324 	if ((pid = fork1()) < 0) {
325 		syslog(LOG_ERR | LOG_DAEMON, "Couldn't fork to refresh "
326 		    "ipfilter for %s: %s", fmri, strerror(errno));
327 		ret = 1;
328 		goto out;
329 	}
330 
331 	if (pid == 0) {
332 		if (execl(IPF_UPDATE_CMD, IPF_UPDATE_CMD, "fw_update", fmri,
333 		    NULL) == -1)
334 			syslog(LOG_ERR | LOG_DAEMON, "execl() failed for "
335 			    "%s: %s", fmri, strerror(errno));
336 
337 		exit(1);
338 	}
339 
340 	/*
341 	 * Parent - only one update at a time.
342 	 */
343 	(void) wait(&status);
344 	if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
345 		ret = 1;
346 
347 out:
348 	if (ret == 1)
349 		syslog(LOG_ERR | LOG_DAEMON, "Firewall update failed "
350 		    "for: %s\n", fmri);
351 
352 	return (ret);
353 }
354 
355 /*
356  * Determine whether a given instance is a RPC service. Repository and
357  * libscf errors are treated as if the service isn't an RPC service,
358  * returning B_FALSE to indicate validation failure.
359  */
360 static boolean_t
361 service_is_rpc(const scf_instance_t *inst)
362 {
363 	scf_snapshot_t *lsnap = NULL;
364 	uint8_t	isrpc;
365 
366 	if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) != 0) {
367 		syslog(LOG_DEBUG | LOG_DAEMON,
368 		    "Could not get running snapshot, using editing value\n");
369 	} else {
370 		lsnap = snap;
371 	}
372 
373 	if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_INETD,
374 	    scratch_pg) == -1) {
375 		switch (scf_error()) {
376 		case SCF_ERROR_NOT_FOUND:
377 		case SCF_ERROR_DELETED:
378 			break;
379 
380 		default:
381 			syslog(LOG_ERR | LOG_DAEMON,
382 			    "scf_instance_get_pg_composed failed: %s\n",
383 			    scf_strerror(scf_error()));
384 			return (B_FALSE);
385 		}
386 
387 		if (scf_instance_get_pg_composed(inst, lsnap,
388 		    SCF_PG_FW_CONTEXT, scratch_pg) == -1) {
389 			switch (scf_error()) {
390 			case SCF_ERROR_NOT_FOUND:
391 			case SCF_ERROR_DELETED:
392 				break;
393 
394 			default:
395 				syslog(LOG_ERR | LOG_DAEMON,
396 				    "scf_instance_get_pg_composed failed: %s\n",
397 				    scf_strerror(scf_error()));
398 			}
399 			return (B_FALSE);
400 		}
401 	}
402 
403 	if (pg_get_prop_value(scratch_pg, SCF_PROPERTY_ISRPC, scratch_v) == -1)
404 		return (B_FALSE);
405 
406 	if (scf_value_get_boolean(scratch_v, &isrpc) == -1) {
407 		syslog(LOG_ERR | LOG_DAEMON, "scf_value_get_boolean failed: "
408 		    "%s\n", scf_strerror(scf_error()));
409 		return (B_FALSE);
410 	}
411 
412 	if (isrpc)
413 		return (B_TRUE);
414 	else
415 		return (B_FALSE);
416 }
417 
418 static int
419 instance_has_firewall(scf_instance_t *inst)
420 {
421 	scf_snapshot_t *lsnap = NULL;
422 
423 	if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) == -1) {
424 		switch (scf_error()) {
425 		case SCF_ERROR_CONNECTION_BROKEN:
426 			syslog(LOG_ERR | LOG_DAEMON,
427 			    "scf_instance_get_snapshot failed: %s\n",
428 			    scf_strerror(scf_error()));
429 			repository_setup();
430 			return (-1);
431 
432 		case SCF_ERROR_DELETED:
433 		default:
434 			/*
435 			 * If running snapshot is not available for
436 			 * other reasons, fall back to current values.
437 			 */
438 			syslog(LOG_DEBUG | LOG_DAEMON, "Could not get "
439 			    "running snapshot, using current value\n");
440 		}
441 	} else {
442 		lsnap = snap;
443 	}
444 
445 	/*
446 	 * Update service's IPfilter configuration if either
447 	 * SCF_PG_FW_CONTEXT or SCF_PG_FW_CONFIG exists.
448 	 */
449 	if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONTEXT,
450 	    scratch_pg) == 0) {
451 		return (1);
452 	} else {
453 		switch (scf_error()) {
454 		case SCF_ERROR_NOT_FOUND:
455 		case SCF_ERROR_DELETED:
456 			break;
457 
458 		case SCF_ERROR_CONNECTION_BROKEN:
459 			repository_setup();
460 			/* FALLTHROUGH */
461 		default:
462 			syslog(LOG_ERR | LOG_DAEMON,
463 			    "scf_instance_get_pg_composed failed: %s\n",
464 			    scf_strerror(scf_error()));
465 			return (-1);
466 		}
467 	}
468 
469 	if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONFIG,
470 	    scratch_pg) == -1) {
471 		/*
472 		 * It's either a non-firewall service or a failure to
473 		 * read firewall pg, just continue and listen for
474 		 * future events.
475 		 */
476 		switch (scf_error()) {
477 		case SCF_ERROR_NOT_FOUND:
478 		case SCF_ERROR_DELETED:
479 			return (0);
480 
481 		case SCF_ERROR_CONNECTION_BROKEN:
482 			repository_setup();
483 			/* FALLTHROUGH */
484 		default:
485 			syslog(LOG_ERR | LOG_DAEMON,
486 			    "scf_instance_get_pg_composed failed: %s\n",
487 			    scf_strerror(scf_error()));
488 			return (-1);
489 		}
490 	}
491 	return (1);
492 }
493 
494 static int
495 repository_event_process(scf_propertygroup_t *pg)
496 {
497 	boolean_t isrpc = B_FALSE;
498 	int res;
499 
500 	/*
501 	 * Figure out it's a firewall capable instance and call ipfilter_update
502 	 * if it is.
503 	 */
504 	if (scf_pg_get_parent_instance(pg, inst) == -1) {
505 		/* Not an error if pg doesn't belong to a valid instance */
506 		if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) {
507 			return (0);
508 		}
509 
510 		syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_parent_instance "
511 		    "failed: %s\n", scf_strerror(scf_error()));
512 
513 		if (scf_error() == SCF_ERROR_CONNECTION_BROKEN)
514 			repository_setup();
515 
516 		return (1);
517 	}
518 
519 	if (scf_instance_to_fmri(inst, scratch_fmri, max_scf_fmri_size) == -1) {
520 		syslog(LOG_ERR | LOG_DAEMON, "scf_instance_to_fmri "
521 		    "failed: %s\n", scf_strerror(scf_error()));
522 
523 		if (scf_error() == SCF_ERROR_CONNECTION_BROKEN)
524 			repository_setup();
525 
526 		return (1);
527 	}
528 
529 	if (strcmp(scratch_fmri, IPFILTER_FMRI) == 0) {
530 		return (0);
531 	}
532 
533 	isrpc = service_is_rpc(inst);
534 
535 	/*
536 	 * If it's not an event we're interested in, returns success.
537 	 */
538 	res = is_correct_event(scratch_fmri, pg, isrpc);
539 	if (res == -1) {
540 		syslog(LOG_ERR | LOG_DAEMON,
541 		    "is_correct_event failed for %s.\n", scratch_fmri);
542 		return (1);
543 	} else if (res == 0) {
544 		return (0);
545 	}
546 
547 	/*
548 	 * Proceed only if instance has firewall policy.
549 	 */
550 	res = instance_has_firewall(inst);
551 	if (res == -1) {
552 		syslog(LOG_ERR | LOG_DAEMON,
553 		    "instance_has_firewall failed for %s.\n", scratch_fmri);
554 		return (1);
555 	} else if (res == 0) {
556 		return (0);
557 	}
558 
559 	if (ipfilter_update(scratch_fmri) == -1) {
560 		return (1);
561 	}
562 
563 	return (0);
564 }
565 
566 static int
567 repository_event_wait()
568 {
569 	scf_propertygroup_t *pg;
570 	char *fmri, *scratch;
571 	const char *inst_name, *pg_name;
572 	ssize_t res;
573 
574 	if ((fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) {
575 		syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
576 		return (1);
577 	}
578 
579 	if ((scratch = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) {
580 		syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
581 		return (1);
582 	}
583 
584 	if ((pg = scf_pg_create(h)) == NULL) {
585 		syslog(LOG_ERR | LOG_DAEMON, "scf_pg_create failed: %s\n",
586 		    scf_strerror(scf_error()));
587 		return (1);
588 	}
589 
590 	repository_notify_setup(h);
591 
592 	for (;;) {
593 		/*
594 		 * Calling _scf_notify_wait which will block this thread
595 		 * until it's notified of a framework event.
596 		 *
597 		 * Note: fmri is only set on delete events.
598 		 */
599 		res = _scf_notify_wait(pg, fmri, max_scf_fmri_size);
600 		if (res < 0) {
601 			syslog(LOG_ERR | LOG_DAEMON, "_scf_notify_wait "
602 			    "failed: %s\n", scf_strerror(scf_error()));
603 			repository_setup();
604 		} else if (res == 0) {
605 			if (repository_event_process(pg))
606 				syslog(LOG_ERR | LOG_DAEMON, "Service may have "
607 				    "incorrect IPfilter configuration\n");
608 		} else {
609 			/*
610 			 * The received event is a deletion of a service,
611 			 * instance or pg. If it's a deletion of an instance,
612 			 * update the instance's IPfilter configuration.
613 			 */
614 			syslog(LOG_DEBUG | LOG_DAEMON, "Deleted: %s", fmri);
615 
616 			(void) strlcpy(scratch, fmri, max_scf_fmri_size);
617 			if (scf_parse_svc_fmri(scratch, NULL, NULL, &inst_name,
618 			    &pg_name, NULL) != SCF_SUCCESS)
619 				continue;
620 
621 			if (inst_name != NULL && pg_name == NULL) {
622 				(void) ipfilter_update(fmri);
623 			}
624 		}
625 	}
626 
627 	/*NOTREACHED*/
628 }
629 
630 int
631 main()
632 {
633 	if (daemonize_self() == 1)
634 		return (1);
635 
636 	max_scf_fmri_size = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1;
637 	max_scf_name_size = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1;
638 
639 	assert(max_scf_fmri_size > 0);
640 	assert(max_scf_name_size > 0);
641 
642 	if ((h = scf_handle_create(SCF_VERSION)) == NULL) {
643 		syslog(LOG_ERR | LOG_DAEMON, "scf_handle_create failed: %s\n",
644 		    scf_strerror(scf_error()));
645 		return (1);
646 	}
647 
648 	repository_rebind(h);
649 
650 	scratch_fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT);
651 	scratch_name = umem_alloc(max_scf_name_size, UMEM_DEFAULT);
652 
653 	if (scratch_fmri == NULL || scratch_name == NULL) {
654 		syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
655 		return (1);
656 	}
657 
658 	inst = scf_instance_create(h);
659 	snap = scf_snapshot_create(h);
660 	scratch_pg = scf_pg_create(h);
661 	scratch_prop = scf_property_create(h);
662 	scratch_v = scf_value_create(h);
663 
664 	if (inst == NULL || snap == NULL || scratch_pg == NULL ||
665 	    scratch_prop == NULL || scratch_v == NULL) {
666 		syslog(LOG_ERR | LOG_DAEMON, "Initialization failed: %s\n",
667 		    scf_strerror(scf_error()));
668 		return (1);
669 	}
670 
671 	return (repository_event_wait());
672 }
673