/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file delivers svc.ipfd, the daemon that monitors changes to * firewall capable services and requests IPfilter configuration update * on behalf of the service. Essentially, the daemon listens for * service changes and forks the program that update a service's * IPfilter configuration. * * - A firewall capable SMF service can restrict network access to its * service by providing a firewall policy that can be translated into * a set of IPfilter rules. The mentioned firewall policy is stored in * firewall_config and firewall_context property groups. If one of these * two property groups exist, the service is considered to be firewall * capable. * * - A request to update service's IPfilter configuration is made for * actions that affect service's configuration or running state. The * actions are: * - enable/disable * - refresh/restart * - maintenance/clear maintenance * * Lacking a generic SMF mechanism to observe service state changes, the * daemon observe change events by listening to changes to 'general', * 'general_ovr', and 'restarter_actions' property groups. This is not a * stable interface and should be replaced when a SMF supported mechanism * becomes available. * * - The program responsible for updating service's IPfilter configuration * is /lib/svc/method/ipfilter. This program is called as: * * /lib/svc/method/ipfilter fw_update fmri * * where fmri the instance fmri of the service to be updated. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define IPFILTER_FMRI "svc:/network/ipfilter:default" #define RPCBIND_FMRI "svc:/network/rpc/bind:default" #define IPF_UPDATE_CMD "/lib/svc/method/ipfilter" #define SCF_SNAPSHOT_RUNNING "running" #define SCF_PG_FW_CONTEXT "firewall_context" #define SCF_PG_FW_CONFIG "firewall_config" #define SCF_PG_REFRESH "refresh" #define SCF_PG_INETD "inetd" #define SCF_PROPERTY_ISRPC "isrpc" #define MAX_RETRY 7 #define DEV_NULL "/dev/null" static scf_handle_t *h; static ssize_t max_scf_fmri_size; static ssize_t max_scf_name_size; static scf_instance_t *inst; static scf_snapshot_t *snap; static scf_propertygroup_t *scratch_pg; static scf_property_t *scratch_prop; static scf_value_t *scratch_v; static char *scratch_fmri; static char *scratch_name; static const char *all_props[] = { SCF_PROPERTY_REFRESH, SCF_PROPERTY_RESTART, SCF_PROPERTY_MAINT_ON, SCF_PROPERTY_MAINT_ON_IMMEDIATE, SCF_PROPERTY_MAINT_ON_IMMTEMP, SCF_PROPERTY_MAINT_ON_TEMPORARY, SCF_PROPERTY_MAINT_OFF }; #define ALL_PROPS_CNT 7 static const char *maint_props[] = { SCF_PROPERTY_REFRESH, SCF_PROPERTY_RESTART, SCF_PROPERTY_MAINT_OFF }; #define MAINT_PROPS_CNT 3 static int ipfilter_update(const char *); static int daemonize_self(void) { pid_t pid; int fd; (void) close(STDIN_FILENO); if ((fd = open(DEV_NULL, O_RDONLY)) == -1) { (void) printf("Could not open /dev/null: %s\n", strerror(errno)); } else if (fd != STDIN_FILENO) { (void) dup2(fd, STDIN_FILENO); (void) close(fd); } (void) dup2(STDERR_FILENO, STDOUT_FILENO); closefrom(3); if ((pid = fork1()) < 0) { (void) printf("fork() failed: %s\n", strerror(errno)); return (1); } if (pid != 0) exit(0); (void) setsid(); (void) chdir("/"); return (0); } static void repository_rebind(scf_handle_t *hndl) { int c = 0; (void) scf_handle_unbind(hndl); while ((scf_handle_bind(hndl)) != 0) { if (c > MAX_RETRY) { syslog(LOG_ERR | LOG_DAEMON, "Repository access " "unavailable. Couldn't bind handle: %s\n", scf_strerror(scf_error())); syslog(LOG_ERR | LOG_DAEMON, "Service specific" "IPfilter configuration may not be updated " "properly\n"); exit(1); } else { c++; } (void) sleep(1); } } static void repository_notify_setup(scf_handle_t *h) { for (;;) { if (_scf_notify_add_pgtype(h, SCF_GROUP_FRAMEWORK) == SCF_SUCCESS) break; switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: repository_rebind(h); break; case SCF_ERROR_NO_RESOURCES: (void) sleep(1); break; default: syslog(LOG_ERR | LOG_DAEMON, "Abort: Couldn't set up repository notification " "for pg type %s: %s\n", SCF_GROUP_FRAMEWORK, scf_strerror(scf_error())); abort(); } } } /* * If the repository connection is lost, rebind and re-setup repository * notification. During the repository connection outage, services that * changed states wouldn't get the corresponding firewall update. To make * we're not out of sync, update the entire system firewall configuration, * invoke ipfilter_update(IPFILTER_FMRI). */ static void repository_setup() { repository_rebind(h); repository_notify_setup(h); if (ipfilter_update(IPFILTER_FMRI) == -1) { syslog(LOG_ERR | LOG_DAEMON, "Failed to reconfigure system firewall.\n"); } } static int pg_get_prop_value(const scf_propertygroup_t *pg, const char *pname, scf_value_t *v) { if (pg == NULL || pname == NULL || v == NULL) return (-1); if (scf_pg_get_property(pg, pname, scratch_prop) == -1 || scf_property_get_value(scratch_prop, v) == -1) { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: break; default: syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_property failed for %s: %s\n", pname, scf_strerror(scf_error())); } return (-1); } return (0); } static int is_correct_event(const char *fmri, const scf_propertygroup_t *pg, const boolean_t isrpc) { char *state = NULL; const char **proplist = all_props; int prop_cnt = ALL_PROPS_CNT; int i, ret = 0; if (scf_pg_get_name(pg, scratch_name, max_scf_name_size) < 0) { syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_name failed: %s\n", scf_strerror(scf_error())); return (-1); } /* * We care about enable, disable, and refresh since that's * when we activate, deactivate, or change firewall policy. * * - enable/disable -> change in "general" or "general_ovr" * - refresh/restart -> change in "restarter_actions" */ if (strcmp(scratch_name, SCF_PG_GENERAL) == 0 || strcmp(scratch_name, SCF_PG_GENERAL_OVR) == 0) { syslog(LOG_DEBUG | LOG_DAEMON, "Action: %s", scratch_name); return (1); } if ((state = smf_get_state(fmri)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "smf_get_state failed for %s: " "%s\n", fmri, scf_strerror(scf_error())); return (-1); } syslog(LOG_DEBUG | LOG_DAEMON, "%s STATE: %s \n", fmri, state); if (strcmp(state, SCF_STATE_STRING_MAINT) == 0) { proplist = maint_props; prop_cnt = MAINT_PROPS_CNT; } /* * Only concerned with refresh, restart, and maint on|off actions. * RPC services are restarted whenever rpc/bind restarts so it's * an automatic valid event for RPC services. */ if (isrpc) { ret = 1; goto out; } else if (strcmp(scratch_name, SCF_PG_RESTARTER_ACTIONS) == 0) { for (i = 0; i < prop_cnt; i++) { if (pg_get_prop_value(pg, proplist[i], scratch_v) == 0) { syslog(LOG_DEBUG | LOG_DAEMON, "Action: %s/%s", scratch_name, proplist[i]); ret = 1; goto out; } } } out: if (state) free(state); return (ret); } static int ipfilter_update(const char *fmri) { pid_t pid; int status, ret = 0; syslog(LOG_DEBUG | LOG_DAEMON, "ipfilter_update: %s\n", fmri); /* * Start refresh in another process */ if ((pid = fork1()) < 0) { syslog(LOG_ERR | LOG_DAEMON, "Couldn't fork to refresh " "ipfilter for %s: %s", fmri, strerror(errno)); ret = 1; goto out; } if (pid == 0) { if (execl(IPF_UPDATE_CMD, IPF_UPDATE_CMD, "fw_update", fmri, NULL) == -1) syslog(LOG_ERR | LOG_DAEMON, "execl() failed for " "%s: %s", fmri, strerror(errno)); exit(1); } /* * Parent - only one update at a time. */ (void) wait(&status); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) ret = 1; out: if (ret == 1) syslog(LOG_ERR | LOG_DAEMON, "Firewall update failed " "for: %s\n", fmri); return (ret); } /* * Determine whether a given instance is a RPC service. Repository and * libscf errors are treated as if the service isn't an RPC service, * returning B_FALSE to indicate validation failure. */ static boolean_t service_is_rpc(const scf_instance_t *inst) { scf_snapshot_t *lsnap = NULL; uint8_t isrpc; if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) != 0) { syslog(LOG_DEBUG | LOG_DAEMON, "Could not get running snapshot, using editing value\n"); } else { lsnap = snap; } if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_INETD, scratch_pg) == -1) { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: break; default: syslog(LOG_ERR | LOG_DAEMON, "scf_instance_get_pg_composed failed: %s\n", scf_strerror(scf_error())); return (B_FALSE); } if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONTEXT, scratch_pg) == -1) { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: break; default: syslog(LOG_ERR | LOG_DAEMON, "scf_instance_get_pg_composed failed: %s\n", scf_strerror(scf_error())); } return (B_FALSE); } } if (pg_get_prop_value(scratch_pg, SCF_PROPERTY_ISRPC, scratch_v) == -1) return (B_FALSE); if (scf_value_get_boolean(scratch_v, &isrpc) == -1) { syslog(LOG_ERR | LOG_DAEMON, "scf_value_get_boolean failed: " "%s\n", scf_strerror(scf_error())); return (B_FALSE); } if (isrpc) return (B_TRUE); else return (B_FALSE); } static int instance_has_firewall(scf_instance_t *inst) { scf_snapshot_t *lsnap = NULL; if (scf_instance_get_snapshot(inst, SCF_SNAPSHOT_RUNNING, snap) == -1) { switch (scf_error()) { case SCF_ERROR_CONNECTION_BROKEN: syslog(LOG_ERR | LOG_DAEMON, "scf_instance_get_snapshot failed: %s\n", scf_strerror(scf_error())); repository_setup(); return (-1); case SCF_ERROR_DELETED: default: /* * If running snapshot is not available for * other reasons, fall back to current values. */ syslog(LOG_DEBUG | LOG_DAEMON, "Could not get " "running snapshot, using current value\n"); } } else { lsnap = snap; } /* * Update service's IPfilter configuration if either * SCF_PG_FW_CONTEXT or SCF_PG_FW_CONFIG exists. */ if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONTEXT, scratch_pg) == 0) { return (1); } else { switch (scf_error()) { case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: break; case SCF_ERROR_CONNECTION_BROKEN: repository_setup(); /* FALLTHROUGH */ default: syslog(LOG_ERR | LOG_DAEMON, "scf_instance_get_pg_composed failed: %s\n", scf_strerror(scf_error())); return (-1); } } if (scf_instance_get_pg_composed(inst, lsnap, SCF_PG_FW_CONFIG, scratch_pg) == -1) { /* * It's either a non-firewall service or a failure to * read firewall pg, just continue and listen for * future events. */ switch (scf_error()) { case SCF_ERROR_NOT_FOUND: case SCF_ERROR_DELETED: return (0); case SCF_ERROR_CONNECTION_BROKEN: repository_setup(); /* FALLTHROUGH */ default: syslog(LOG_ERR | LOG_DAEMON, "scf_instance_get_pg_composed failed: %s\n", scf_strerror(scf_error())); return (-1); } } return (1); } static int repository_event_process(scf_propertygroup_t *pg) { boolean_t isrpc = B_FALSE; int res; /* * Figure out it's a firewall capable instance and call ipfilter_update * if it is. */ if (scf_pg_get_parent_instance(pg, inst) == -1) { /* Not an error if pg doesn't belong to a valid instance */ if (scf_error() == SCF_ERROR_CONSTRAINT_VIOLATED) { return (0); } syslog(LOG_ERR | LOG_DAEMON, "scf_pg_get_parent_instance " "failed: %s\n", scf_strerror(scf_error())); if (scf_error() == SCF_ERROR_CONNECTION_BROKEN) repository_setup(); return (1); } if (scf_instance_to_fmri(inst, scratch_fmri, max_scf_fmri_size) == -1) { syslog(LOG_ERR | LOG_DAEMON, "scf_instance_to_fmri " "failed: %s\n", scf_strerror(scf_error())); if (scf_error() == SCF_ERROR_CONNECTION_BROKEN) repository_setup(); return (1); } if (strcmp(scratch_fmri, IPFILTER_FMRI) == 0) { return (0); } isrpc = service_is_rpc(inst); /* * If it's not an event we're interested in, returns success. */ res = is_correct_event(scratch_fmri, pg, isrpc); if (res == -1) { syslog(LOG_ERR | LOG_DAEMON, "is_correct_event failed for %s.\n", scratch_fmri); return (1); } else if (res == 0) { return (0); } /* * Proceed only if instance has firewall policy. */ res = instance_has_firewall(inst); if (res == -1) { syslog(LOG_ERR | LOG_DAEMON, "instance_has_firewall failed for %s.\n", scratch_fmri); return (1); } else if (res == 0) { return (0); } if (ipfilter_update(scratch_fmri) == -1) { return (1); } return (0); } static int repository_event_wait() { scf_propertygroup_t *pg; char *fmri, *scratch; const char *inst_name, *pg_name; ssize_t res; if ((fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "Out of memory"); return (1); } if ((scratch = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "Out of memory"); return (1); } if ((pg = scf_pg_create(h)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "scf_pg_create failed: %s\n", scf_strerror(scf_error())); return (1); } repository_notify_setup(h); for (;;) { /* * Calling _scf_notify_wait which will block this thread * until it's notified of a framework event. * * Note: fmri is only set on delete events. */ res = _scf_notify_wait(pg, fmri, max_scf_fmri_size); if (res < 0) { syslog(LOG_ERR | LOG_DAEMON, "_scf_notify_wait " "failed: %s\n", scf_strerror(scf_error())); repository_setup(); } else if (res == 0) { if (repository_event_process(pg)) syslog(LOG_ERR | LOG_DAEMON, "Service may have " "incorrect IPfilter configuration\n"); } else { /* * The received event is a deletion of a service, * instance or pg. If it's a deletion of an instance, * update the instance's IPfilter configuration. */ syslog(LOG_DEBUG | LOG_DAEMON, "Deleted: %s", fmri); (void) strlcpy(scratch, fmri, max_scf_fmri_size); if (scf_parse_svc_fmri(scratch, NULL, NULL, &inst_name, &pg_name, NULL) != SCF_SUCCESS) continue; if (inst_name != NULL && pg_name == NULL) { (void) ipfilter_update(fmri); } } } /*NOTREACHED*/ } int main() { if (daemonize_self() == 1) return (1); max_scf_fmri_size = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH) + 1; max_scf_name_size = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1; assert(max_scf_fmri_size > 0); assert(max_scf_name_size > 0); if ((h = scf_handle_create(SCF_VERSION)) == NULL) { syslog(LOG_ERR | LOG_DAEMON, "scf_handle_create failed: %s\n", scf_strerror(scf_error())); return (1); } repository_rebind(h); scratch_fmri = umem_alloc(max_scf_fmri_size, UMEM_DEFAULT); scratch_name = umem_alloc(max_scf_name_size, UMEM_DEFAULT); if (scratch_fmri == NULL || scratch_name == NULL) { syslog(LOG_ERR | LOG_DAEMON, "Out of memory"); return (1); } inst = scf_instance_create(h); snap = scf_snapshot_create(h); scratch_pg = scf_pg_create(h); scratch_prop = scf_property_create(h); scratch_v = scf_value_create(h); if (inst == NULL || snap == NULL || scratch_pg == NULL || scratch_prop == NULL || scratch_v == NULL) { syslog(LOG_ERR | LOG_DAEMON, "Initialization failed: %s\n", scf_strerror(scf_error())); return (1); } return (repository_event_wait()); }