Merge remote-tracking branch 'origin/master'
[unleashed/lotheac.git] / usr / src / cmd / ipf / svc / ipfd.c
blob4a17fd94e0897660a052b5276e6f30c244cccb59
1 /*
2 * CDDL HEADER START
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.
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.
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]
19 * CDDL HEADER END
23 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
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.
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.
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
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.
54 * - The program responsible for updating service's IPfilter configuration
55 * is /lib/svc/method/ipfilter. This program is called as:
57 * /lib/svc/method/ipfilter fw_update fmri
59 * where fmri the instance fmri of the service to be updated.
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>
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"
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"
88 #define SCF_PROPERTY_ISRPC "isrpc"
90 #define MAX_RETRY 7
91 #define DEV_NULL "/dev/null"
93 static scf_handle_t *h;
94 static ssize_t max_scf_fmri_size;
95 static ssize_t max_scf_name_size;
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;
103 static char *scratch_fmri;
104 static char *scratch_name;
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
111 #define ALL_PROPS_CNT 7
113 static const char *maint_props[] = {
114 SCF_PROPERTY_REFRESH, SCF_PROPERTY_RESTART, SCF_PROPERTY_MAINT_OFF };
115 #define MAINT_PROPS_CNT 3
117 static int ipfilter_update(const char *);
119 static int
120 daemonize_self(void)
122 pid_t pid;
123 int fd;
125 (void) close(STDIN_FILENO);
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);
134 (void) dup2(STDERR_FILENO, STDOUT_FILENO);
135 closefrom(3);
137 if ((pid = fork1()) < 0) {
138 (void) printf("fork() failed: %s\n", strerror(errno));
139 return (1);
142 if (pid != 0)
143 exit(0);
145 (void) setsid();
146 (void) chdir("/");
148 return (0);
151 static void
152 repository_rebind(scf_handle_t *hndl)
154 int c = 0;
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");
166 exit(1);
167 } else {
168 c++;
171 (void) sleep(1);
175 static void
176 repository_notify_setup(scf_handle_t *h)
178 for (;;) {
179 if (_scf_notify_add_pgtype(h, SCF_GROUP_FRAMEWORK) ==
180 SCF_SUCCESS)
181 break;
183 switch (scf_error()) {
184 case SCF_ERROR_CONNECTION_BROKEN:
185 repository_rebind(h);
186 break;
188 case SCF_ERROR_NO_RESOURCES:
189 (void) sleep(1);
190 break;
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();
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).
209 static void
210 repository_setup()
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");
220 static int
221 pg_get_prop_value(const scf_propertygroup_t *pg, const char *pname,
222 scf_value_t *v)
224 if (pg == NULL || pname == NULL || v == NULL)
225 return (-1);
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;
234 default:
235 syslog(LOG_ERR | LOG_DAEMON,
236 "scf_pg_get_property failed for %s: %s\n",
237 pname, scf_strerror(scf_error()));
239 return (-1);
241 return (0);
244 static int
245 is_correct_event(const char *fmri, const scf_propertygroup_t *pg,
246 const boolean_t isrpc)
248 char *state = NULL;
249 const char **proplist = all_props;
250 int prop_cnt = ALL_PROPS_CNT;
252 int i, ret = 0;
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);
261 * We care about enable, disable, and refresh since that's
262 * when we activate, deactivate, or change firewall policy.
264 * - enable/disable -> change in "general" or "general_ovr"
265 * - refresh/restart -> change in "restarter_actions"
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);
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);
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;
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.
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]);
300 ret = 1;
301 goto out;
306 out:
307 free(state);
309 return (ret);
312 static int
313 ipfilter_update(const char *fmri)
315 pid_t pid;
316 int status, ret = 0;
318 syslog(LOG_DEBUG | LOG_DAEMON, "ipfilter_update: %s\n", fmri);
321 * Start refresh in another process
323 if ((pid = fork1()) < 0) {
324 syslog(LOG_ERR | LOG_DAEMON, "Couldn't fork to refresh "
325 "ipfilter for %s: %s", fmri, strerror(errno));
326 ret = 1;
327 goto out;
330 if (pid == 0) {
331 if (execl(IPF_UPDATE_CMD, IPF_UPDATE_CMD, "fw_update", fmri,
332 NULL) == -1)
333 syslog(LOG_ERR | LOG_DAEMON, "execl() failed for "
334 "%s: %s", fmri, strerror(errno));
336 exit(1);
340 * Parent - only one update at a time.
342 (void) wait(&status);
343 if (!WIFEXITED(status) || WEXITSTATUS(status) != 0)
344 ret = 1;
346 out:
347 if (ret == 1)
348 syslog(LOG_ERR | LOG_DAEMON, "Firewall update failed "
349 "for: %s\n", fmri);
351 return (ret);
355 * Determine whether a given instance is a RPC service. Repository and
356 * libscf errors are treated as if the service isn't an RPC service,
357 * returning B_FALSE to indicate validation failure.
359 static boolean_t
360 service_is_rpc(const scf_instance_t *inst)
362 scf_snapshot_t *lsnap = NULL;
363 uint8_t isrpc;
365 if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) != 0) {
366 syslog(LOG_DEBUG | LOG_DAEMON,
367 "Could not get running snapshot, using editing value\n");
368 } else {
369 lsnap = snap;
372 if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_INETD,
373 scratch_pg) == -1) {
374 switch (scf_error()) {
375 case SCF_ERROR_NOT_FOUND:
376 case SCF_ERROR_DELETED:
377 break;
379 default:
380 syslog(LOG_ERR | LOG_DAEMON,
381 "scf_instance_get_pg_composed failed: %s\n",
382 scf_strerror(scf_error()));
383 return (B_FALSE);
386 if (scf_instance_get_pg_composed(inst, lsnap,
387 SCF_PG_FW_CONTEXT, scratch_pg) == -1) {
388 switch (scf_error()) {
389 case SCF_ERROR_NOT_FOUND:
390 case SCF_ERROR_DELETED:
391 break;
393 default:
394 syslog(LOG_ERR | LOG_DAEMON,
395 "scf_instance_get_pg_composed failed: %s\n",
396 scf_strerror(scf_error()));
398 return (B_FALSE);
402 if (pg_get_prop_value(scratch_pg, SCF_PROPERTY_ISRPC, scratch_v) == -1)
403 return (B_FALSE);
405 if (scf_value_get_boolean(scratch_v, &isrpc) == -1) {
406 syslog(LOG_ERR | LOG_DAEMON, "scf_value_get_boolean failed: "
407 "%s\n", scf_strerror(scf_error()));
408 return (B_FALSE);
411 if (isrpc)
412 return (B_TRUE);
413 else
414 return (B_FALSE);
417 static int
418 instance_has_firewall(scf_instance_t *inst)
420 scf_snapshot_t *lsnap = NULL;
422 if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) == -1) {
423 switch (scf_error()) {
424 case SCF_ERROR_CONNECTION_BROKEN:
425 syslog(LOG_ERR | LOG_DAEMON,
426 "scf_instance_get_snapshot failed: %s\n",
427 scf_strerror(scf_error()));
428 repository_setup();
429 return (-1);
431 case SCF_ERROR_DELETED:
432 default:
434 * If running snapshot is not available for
435 * other reasons, fall back to current values.
437 syslog(LOG_DEBUG | LOG_DAEMON, "Could not get "
438 "running snapshot, using current value\n");
440 } else {
441 lsnap = snap;
445 * Update service's IPfilter configuration if either
446 * SCF_PG_FW_CONTEXT or SCF_PG_FW_CONFIG exists.
448 if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONTEXT,
449 scratch_pg) == 0) {
450 return (1);
451 } else {
452 switch (scf_error()) {
453 case SCF_ERROR_NOT_FOUND:
454 case SCF_ERROR_DELETED:
455 break;
457 case SCF_ERROR_CONNECTION_BROKEN:
458 repository_setup();
459 /* FALLTHROUGH */
460 default:
461 syslog(LOG_ERR | LOG_DAEMON,
462 "scf_instance_get_pg_composed failed: %s\n",
463 scf_strerror(scf_error()));
464 return (-1);
468 if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONFIG,
469 scratch_pg) == -1) {
471 * It's either a non-firewall service or a failure to
472 * read firewall pg, just continue and listen for
473 * future events.
475 switch (scf_error()) {
476 case SCF_ERROR_NOT_FOUND:
477 case SCF_ERROR_DELETED:
478 return (0);
480 case SCF_ERROR_CONNECTION_BROKEN:
481 repository_setup();
482 /* FALLTHROUGH */
483 default:
484 syslog(LOG_ERR | LOG_DAEMON,
485 "scf_instance_get_pg_composed failed: %s\n",
486 scf_strerror(scf_error()));
487 return (-1);
490 return (1);
493 static int
494 repository_event_process(scf_propertygroup_t *pg)
496 boolean_t isrpc = B_FALSE;
497 int res;
500 * Figure out it's a firewall capable instance and call ipfilter_update
501 * if it is.
503 if (scf_pg_get_parent_instance(pg, inst) == -1) {
504 /* Not an error if pg doesn't belong to a valid instance */
505 if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) {
506 return (0);
509 syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_parent_instance "
510 "failed: %s\n", scf_strerror(scf_error()));
512 if (scf_error() == SCF_ERROR_CONNECTION_BROKEN)
513 repository_setup();
515 return (1);
518 if (scf_instance_to_fmri(inst, scratch_fmri, max_scf_fmri_size) == -1) {
519 syslog(LOG_ERR | LOG_DAEMON, "scf_instance_to_fmri "
520 "failed: %s\n", scf_strerror(scf_error()));
522 if (scf_error() == SCF_ERROR_CONNECTION_BROKEN)
523 repository_setup();
525 return (1);
528 if (strcmp(scratch_fmri, IPFILTER_FMRI) == 0) {
529 return (0);
532 isrpc = service_is_rpc(inst);
535 * If it's not an event we're interested in, returns success.
537 res = is_correct_event(scratch_fmri, pg, isrpc);
538 if (res == -1) {
539 syslog(LOG_ERR | LOG_DAEMON,
540 "is_correct_event failed for %s.\n", scratch_fmri);
541 return (1);
542 } else if (res == 0) {
543 return (0);
547 * Proceed only if instance has firewall policy.
549 res = instance_has_firewall(inst);
550 if (res == -1) {
551 syslog(LOG_ERR | LOG_DAEMON,
552 "instance_has_firewall failed for %s.\n", scratch_fmri);
553 return (1);
554 } else if (res == 0) {
555 return (0);
558 if (ipfilter_update(scratch_fmri) == -1) {
559 return (1);
562 return (0);
565 static int
566 repository_event_wait()
568 scf_propertygroup_t *pg;
569 char *fmri, *scratch;
570 const char *inst_name, *pg_name;
571 ssize_t res;
573 if ((fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) {
574 syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
575 return (1);
578 if ((scratch = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) {
579 syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
580 return (1);
583 if ((pg = scf_pg_create(h)) == NULL) {
584 syslog(LOG_ERR | LOG_DAEMON, "scf_pg_create failed: %s\n",
585 scf_strerror(scf_error()));
586 return (1);
589 repository_notify_setup(h);
591 for (;;) {
593 * Calling _scf_notify_wait which will block this thread
594 * until it's notified of a framework event.
596 * Note: fmri is only set on delete events.
598 res = _scf_notify_wait(pg, fmri, max_scf_fmri_size);
599 if (res < 0) {
600 syslog(LOG_ERR | LOG_DAEMON, "_scf_notify_wait "
601 "failed: %s\n", scf_strerror(scf_error()));
602 repository_setup();
603 } else if (res == 0) {
604 if (repository_event_process(pg))
605 syslog(LOG_ERR | LOG_DAEMON, "Service may have "
606 "incorrect IPfilter configuration\n");
607 } else {
609 * The received event is a deletion of a service,
610 * instance or pg. If it's a deletion of an instance,
611 * update the instance's IPfilter configuration.
613 syslog(LOG_DEBUG | LOG_DAEMON, "Deleted: %s", fmri);
615 (void) strlcpy(scratch, fmri, max_scf_fmri_size);
616 if (scf_parse_svc_fmri(scratch, NULL, NULL, &inst_name,
617 &pg_name, NULL) != SCF_SUCCESS)
618 continue;
620 if (inst_name != NULL && pg_name == NULL) {
621 (void) ipfilter_update(fmri);
626 /*NOTREACHED*/
630 main()
632 if (daemonize_self() == 1)
633 return (1);
635 max_scf_fmri_size = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1;
636 max_scf_name_size = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1;
638 assert(max_scf_fmri_size > 0);
639 assert(max_scf_name_size > 0);
641 if ((h = scf_handle_create(SCF_VERSION)) == NULL) {
642 syslog(LOG_ERR | LOG_DAEMON, "scf_handle_create failed: %s\n",
643 scf_strerror(scf_error()));
644 return (1);
647 repository_rebind(h);
649 scratch_fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT);
650 scratch_name = umem_alloc(max_scf_name_size, UMEM_DEFAULT);
652 if (scratch_fmri == NULL || scratch_name == NULL) {
653 syslog(LOG_ERR | LOG_DAEMON, "Out of memory");
654 return (1);
657 inst = scf_instance_create(h);
658 snap = scf_snapshot_create(h);
659 scratch_pg = scf_pg_create(h);
660 scratch_prop = scf_property_create(h);
661 scratch_v = scf_value_create(h);
663 if (inst == NULL || snap == NULL || scratch_pg == NULL ||
664 scratch_prop == NULL || scratch_v == NULL) {
665 syslog(LOG_ERR | LOG_DAEMON, "Initialization failed: %s\n",
666 scf_strerror(scf_error()));
667 return (1);
670 return (repository_event_wait());