/* * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * conditions.c - contains routines which check state to see if activation * conditions for NWAM objects are satisfied and rates activation conditions to * help determine which is most specific. * * If the activation-mode is CONDITIONAL_ANY or CONDITIONAL_ALL, the conditions * property is set to a string made up of conditional expressions. Each * expression is made up of a condition that can be assigned a boolean value, * e.g. "system-domain is sun.com" or "ncu ip:bge0 is-not active". If the * activation-mode is CONDITIONAL_ANY, the condition will be satisfied if any * one of the conditions is true; if the activation-mode is CONDITIONAL_ALL, * the condition is satisfied only if all of the conditions are true. */ uint64_t condition_check_interval = CONDITION_CHECK_INTERVAL_DEFAULT; extern int getdomainname(char *, int); /* NCP, NCU, ENM and location conditions */ static boolean_t test_condition_ncp(nwam_condition_t condition, const char *ncp_name); static boolean_t test_condition_ncu(nwam_condition_t condition, const char *ncu_name); static boolean_t test_condition_enm(nwam_condition_t condition, const char *enm_name); static boolean_t test_condition_loc(nwam_condition_t condition, const char *loc_name); /* IP address conditions */ static boolean_t test_condition_ip_address(nwam_condition_t condition, const char *ip_address); /* domainname conditions */ static boolean_t test_condition_sys_domain(nwam_condition_t condition, const char *domainname); static boolean_t test_condition_adv_domain(nwam_condition_t condition, const char *domainname); /* WLAN conditions */ static boolean_t test_condition_wireless_essid(nwam_condition_t condition, const char *essid); static boolean_t test_condition_wireless_bssid(nwam_condition_t condition, const char *essid); struct nwamd_condition_map { nwam_condition_object_type_t object_type; boolean_t (*condition_func)(nwam_condition_t, const char *); } condition_map[] = { { NWAM_CONDITION_OBJECT_TYPE_NCP, test_condition_ncp }, { NWAM_CONDITION_OBJECT_TYPE_NCU, test_condition_ncu }, { NWAM_CONDITION_OBJECT_TYPE_ENM, test_condition_enm }, { NWAM_CONDITION_OBJECT_TYPE_LOC, test_condition_loc }, { NWAM_CONDITION_OBJECT_TYPE_IP_ADDRESS, test_condition_ip_address }, { NWAM_CONDITION_OBJECT_TYPE_SYS_DOMAIN, test_condition_sys_domain }, { NWAM_CONDITION_OBJECT_TYPE_ADV_DOMAIN, test_condition_adv_domain }, { NWAM_CONDITION_OBJECT_TYPE_ESSID, test_condition_wireless_essid }, { NWAM_CONDITION_OBJECT_TYPE_BSSID, test_condition_wireless_bssid } }; /* * This function takes which kind of conditions (is or is not) we are testing * the object against and an object and applies the conditon to the object. */ static boolean_t test_condition_object_state(nwam_condition_t condition, nwam_object_type_t object_type, const char *object_name) { nwamd_object_t object; nwam_state_t state; object = nwamd_object_find(object_type, object_name); if (object == NULL) return (B_FALSE); state = object->nwamd_object_state; nwamd_object_release(object); switch (condition) { case NWAM_CONDITION_IS: return (state == NWAM_STATE_ONLINE); case NWAM_CONDITION_IS_NOT: return (state != NWAM_STATE_ONLINE); default: return (B_FALSE); } } static boolean_t test_condition_ncp(nwam_condition_t condition, const char *name) { boolean_t active; (void) pthread_mutex_lock(&active_ncp_mutex); active = (strcasecmp(active_ncp, name) == 0); (void) pthread_mutex_unlock(&active_ncp_mutex); switch (condition) { case NWAM_CONDITION_IS: return (active); case NWAM_CONDITION_IS_NOT: return (active != B_TRUE); default: return (B_FALSE); } } static boolean_t test_condition_ncu(nwam_condition_t condition, const char *name) { char *real_name, *ncu_name; nwam_ncu_handle_t ncuh; nwam_ncu_type_t ncu_type; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_ncu_read(active_ncph, name, NWAM_NCU_TYPE_INTERFACE, 0, &ncuh) == NWAM_SUCCESS) { ncu_type = NWAM_NCU_TYPE_INTERFACE; } else if (nwam_ncu_read(active_ncph, name, NWAM_NCU_TYPE_LINK, 0, &ncuh) == NWAM_SUCCESS) { ncu_type = NWAM_NCU_TYPE_LINK; } else { return (B_FALSE); } if (nwam_ncu_get_name(ncuh, &real_name) != NWAM_SUCCESS) { nwam_ncu_free(ncuh); return (B_FALSE); } nwam_ncu_free(ncuh); /* * Name may be either unqualified or qualified by NCU type * (interface:/link:). Need to translate unqualified names * to qualified, specifying interface:name if an interface * NCU is present, otherwise link:ncu. */ if (nwam_ncu_name_to_typed_name(real_name, ncu_type, &ncu_name) != NWAM_SUCCESS) { free(real_name); return (B_FALSE); } free(real_name); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_NCU, ncu_name); free(ncu_name); return (rv); } static boolean_t test_condition_enm(nwam_condition_t condition, const char *enm_name) { nwam_enm_handle_t enmh; char *real_name; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_enm_read(enm_name, 0, &enmh) != NWAM_SUCCESS) return (B_FALSE); if (nwam_enm_get_name(enmh, &real_name) != NWAM_SUCCESS) { nwam_enm_free(enmh); return (B_FALSE); } nwam_enm_free(enmh); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_ENM, real_name); free(real_name); return (rv); } static boolean_t test_condition_loc(nwam_condition_t condition, const char *loc_name) { nwam_loc_handle_t loch; char *real_name; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_loc_read(loc_name, 0, &loch) != NWAM_SUCCESS) return (B_FALSE); if (nwam_loc_get_name(loch, &real_name) != NWAM_SUCCESS) { nwam_loc_free(loch); return (B_FALSE); } nwam_loc_free(loch); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_LOC, real_name); free(real_name); return (rv); } static boolean_t test_condition_domain(nwam_condition_t condition, const char *target_domain, const char *found_domain) { int i, len_t, len_f; char target[MAXHOSTNAMELEN], found[MAXHOSTNAMELEN]; len_t = target_domain == NULL ? 0 : strlen(target_domain); len_f = found_domain == NULL ? 0 : strlen(found_domain); /* convert target_domain and found_domain to lowercase for strstr() */ for (i = 0; i < len_t; i++) target[i] = tolower(target_domain[i]); target[len_t] = '\0'; for (i = 0; i < len_f; i++) found[i] = tolower(found_domain[i]); found[len_f] = '\0'; switch (condition) { case NWAM_CONDITION_IS: return (found_domain != NULL && strcmp(found, target) == 0); case NWAM_CONDITION_IS_NOT: return (found_domain == NULL || strcmp(found, target) != 0); case NWAM_CONDITION_CONTAINS: return (found_domain != NULL && strstr(found, target) != NULL); case NWAM_CONDITION_DOES_NOT_CONTAIN: return (found_domain == NULL || strstr(found, target) == NULL); default: return (B_FALSE); } } struct ncu_adv_domains { struct ncu_adv_domains *next; char *dns_domain; char *nis_domain; }; static int get_adv_domains(nwamd_object_t obj, void *arg) { nwamd_ncu_t *ncu = (nwamd_ncu_t *)obj->nwamd_object_data; struct ncu_adv_domains **headpp = (struct ncu_adv_domains **)arg; struct ncu_adv_domains *adp; char *dns, *nis; if (ncu->ncu_type != NWAM_NCU_TYPE_INTERFACE) return (0); dns = nwamd_get_dhcpinfo_data("DNSdmain", ncu->ncu_name); nis = nwamd_get_dhcpinfo_data("NISdmain", ncu->ncu_name); if (dns != NULL || nis != NULL) { adp = (struct ncu_adv_domains *)malloc(sizeof (*adp)); if (adp == NULL) return (1); adp->dns_domain = dns; adp->nis_domain = nis; adp->next = *headpp; *headpp = adp; } return (0); } static boolean_t test_condition_sys_domain(nwam_condition_t condition, const char *domainname) { char cur_domainname[MAXHOSTNAMELEN]; if (getdomainname(cur_domainname, MAXHOSTNAMELEN) != 0) return (B_FALSE); return (test_condition_domain(condition, domainname, cur_domainname)); } static boolean_t test_condition_adv_domain(nwam_condition_t condition, const char *domainname) { struct ncu_adv_domains *adv_domains = NULL; struct ncu_adv_domains *adp, *prev; boolean_t positive, rtn; (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, get_adv_domains, &adv_domains); positive = (condition == NWAM_CONDITION_IS || condition == NWAM_CONDITION_CONTAINS); /* * Walk the advertised domain list. Our test function tests one * single domain, but we're dealing with a list: if our condition * is positive ('is' or 'contains'), the test function for each * domain results are or'd together; if our condition is negative * ('is-not' or 'does-not-contain'), the test function results must * be and'd. Thus our short-circuit exit value depends on our * condition: if the test function returns TRUE it implies immediate * success for a positive condition; if it returns FALSE it implies * immediate failure for a negative condition. */ adp = adv_domains; while (adp != NULL) { if ((test_condition_domain(condition, domainname, adp->dns_domain) == positive) || (test_condition_domain(condition, domainname, adp->nis_domain) == positive)) { rtn = positive; break; } adp = adp->next; } if (adp == NULL) { /* * We did not short-circuit; we therefore failed if our * condition was positive, and succeeded if our condition * was negative. */ rtn = !positive; } /* now free the domain list */ adp = adv_domains; while (adp != NULL) { prev = adp; adp = prev->next; free(prev->dns_domain); free(prev->nis_domain); free(prev); } return (rtn); } /* * Returns true if prefixlen bits of addr1 match prefixlen bits of addr2. */ static boolean_t prefixmatch(uchar_t *addr1, uchar_t *addr2, int prefixlen) { uchar_t mask[IPV6_ABITS/8]; int i, j = 0; if (prefixlen == 0) return (B_TRUE); while (prefixlen > 0) { if (prefixlen >= 8) { mask[j++] = 0xFF; prefixlen -= 8; } else { mask[j] |= 1 << (8 - prefixlen); prefixlen--; } } /* Ensure at least one byte is tested */ if (j == 0) j++; for (i = 0; i < j; i++) { if ((addr1[i] & mask[i]) != (addr2[i] & mask[i])) return (B_FALSE); } return (B_TRUE); } /* * Given a string representation of an IPv4 or IPv6 address returns the * sockaddr representation. Note that 'sockaddr' should point at the correct * sockaddr structure for the address family (sockaddr_in for AF_INET or * sockaddr_in6 for AF_INET6) or alternatively at a sockaddr_storage * structure. */ static struct sockaddr_storage * nwamd_str2sockaddr(sa_family_t af, const char *straddr, struct sockaddr_storage *addr) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int err; if (af == AF_INET) { sin = (struct sockaddr_in *)addr; sin->sin_family = AF_INET; err = inet_pton(AF_INET, straddr, &sin->sin_addr); } else if (af == AF_INET6) { sin6 = (struct sockaddr_in6 *)addr; sin6->sin6_family = AF_INET6; err = inet_pton(AF_INET6, straddr, &sin6->sin6_addr); } else { errno = EINVAL; return (NULL); } return (err == 1 ? addr : NULL); } struct nwamd_ipaddr_condition_walk_arg { nwam_condition_t condition; struct sockaddr_storage sockaddr; int prefixlen; boolean_t res; }; static int check_ipaddr(sa_family_t family, struct ifaddrs *ifa, void *arg) { struct nwamd_ipaddr_condition_walk_arg *wa = arg; struct sockaddr_in6 addr6; struct sockaddr_in addr; boolean_t match = B_FALSE; uchar_t *addr1, *addr2; if (family == AF_INET) { (void) memcpy(&addr, ifa->ifa_addr, sizeof (addr)); addr1 = (uchar_t *)(&addr.sin_addr.s_addr); addr2 = (uchar_t *)&(((struct sockaddr_in *) &(wa->sockaddr))->sin_addr.s_addr); } else { (void) memcpy(&addr6, ifa->ifa_addr, sizeof (addr6)); addr1 = (uchar_t *)(&addr6.sin6_addr.s6_addr); addr2 = (uchar_t *)&(((struct sockaddr_in6 *) &(wa->sockaddr))->sin6_addr.s6_addr); } match = prefixmatch(addr1, addr2, wa->prefixlen); nlog(LOG_DEBUG, "check_ipaddr: match %d\n", match); switch (wa->condition) { case NWAM_CONDITION_IS: case NWAM_CONDITION_IS_IN_RANGE: wa->res = match; if (match) return (1); return (0); case NWAM_CONDITION_IS_NOT: case NWAM_CONDITION_IS_NOT_IN_RANGE: wa->res = !match; return (0); default: return (0); } } static boolean_t test_condition_ip_address(nwam_condition_t condition, const char *ip_address_string) { sa_family_t family; char *copy, *ip_address, *prefixlen_string, *lasts; struct nwamd_ipaddr_condition_walk_arg wa; struct ifaddrs *ifap, *ifa; if ((copy = strdup(ip_address_string)) == NULL) return (B_FALSE); if ((ip_address = strtok_r(copy, " \t/", &lasts)) == NULL) { free(copy); return (B_FALSE); } prefixlen_string = strtok_r(NULL, " \t", &lasts); if (nwamd_str2sockaddr(AF_INET, ip_address, &wa.sockaddr) != NULL) { family = AF_INET; wa.prefixlen = IP_ABITS; } else if (nwamd_str2sockaddr(AF_INET6, ip_address, &wa.sockaddr) != NULL) { family = AF_INET6; wa.prefixlen = IPV6_ABITS; } else { nlog(LOG_ERR, "test_condition_ip_address: " "nwamd_str2sockaddr failed for %s: %s", ip_address, strerror(errno)); free(copy); return (B_FALSE); } if (prefixlen_string != NULL) wa.prefixlen = atoi(prefixlen_string); wa.condition = condition; switch (condition) { case NWAM_CONDITION_IS: case NWAM_CONDITION_IS_IN_RANGE: wa.res = B_FALSE; break; case NWAM_CONDITION_IS_NOT: case NWAM_CONDITION_IS_NOT_IN_RANGE: wa.res = B_TRUE; break; default: free(copy); return (B_FALSE); } free(copy); if (getifaddrs(&ifa) == -1) { nlog(LOG_ERR, "test_condition_ip_address: " "getifaddrs failed: %s", strerror(errno)); return (wa.res); } for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { if (ifap->ifa_addr->sa_family != family) continue; if (check_ipaddr(family, ifap, &wa) == 1) break; } freeifaddrs(ifa); return (wa.res); } struct nwamd_wlan_condition_walk_arg { nwam_condition_t condition; const char *exp_essid; const char *exp_bssid; uint_t num_connected; boolean_t res; }; static int check_wlan(const char *linkname, void *arg) { struct nwamd_wlan_condition_walk_arg *wa = arg; datalink_id_t linkid; dladm_wlan_linkattr_t attr; dladm_status_t status; char cur_essid[DLADM_STRSIZE]; char cur_bssid[DLADM_STRSIZE]; char errmsg[DLADM_STRSIZE]; if ((status = dladm_name2info(dld_handle, linkname, &linkid, NULL, NULL, NULL)) != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "check_wlan: dladm_name2info() for %s " "failed: %s", linkname, dladm_status2str(status, errmsg)); return (DLADM_WALK_CONTINUE); } status = dladm_wlan_get_linkattr(dld_handle, linkid, &attr); if (status != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "check_wlan: dladm_wlan_get_linkattr() for %s " "failed: %s", linkname, dladm_status2str(status, errmsg)); return (DLADM_WALK_CONTINUE); } if (attr.la_status == DLADM_WLAN_LINK_DISCONNECTED) return (DLADM_WALK_TERMINATE); wa->num_connected++; if (wa->exp_essid != NULL) { /* Is the NIC associated with the expected access point? */ (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = strcmp(cur_essid, wa->exp_essid) == 0; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_IS_NOT: wa->res = strcmp(cur_essid, wa->exp_essid) != 0; if (!wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_CONTAINS: wa->res = strstr(cur_essid, wa->exp_essid) != NULL; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_DOES_NOT_CONTAIN: wa->res = strstr(cur_essid, wa->exp_essid) == NULL; if (!wa->res) return (DLADM_WALK_TERMINATE); break; default: return (DLADM_WALK_TERMINATE); } return (DLADM_WALK_CONTINUE); } if (wa->exp_bssid != NULL) { /* Is the NIC associated with the expected access point? */ (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, cur_bssid); switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = strcmp(cur_bssid, wa->exp_bssid) == 0; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_IS_NOT: wa->res = strcmp(cur_bssid, wa->exp_bssid) != 0; if (!wa->res) return (DLADM_WALK_TERMINATE); break; default: return (DLADM_WALK_TERMINATE); } return (DLADM_WALK_CONTINUE); } /* * Neither an ESSID or BSSID match is required - being connected to a * WLAN is enough. */ switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = B_TRUE; return (DLADM_WALK_TERMINATE); default: wa->res = B_FALSE; return (DLADM_WALK_TERMINATE); } /*NOTREACHED*/ return (DLADM_WALK_CONTINUE); } static boolean_t test_condition_wireless_essid(nwam_condition_t condition, const char *essid) { struct nwamd_wlan_condition_walk_arg wa; wa.condition = condition; wa.exp_essid = essid; wa.exp_bssid = NULL; wa.num_connected = 0; wa.res = B_FALSE; (void) dladm_walk(check_wlan, dld_handle, &wa, DATALINK_CLASS_PHYS, DL_WIFI, DLADM_OPT_ACTIVE); return (wa.num_connected > 0 && wa.res == B_TRUE); } static boolean_t test_condition_wireless_bssid(nwam_condition_t condition, const char *bssid) { struct nwamd_wlan_condition_walk_arg wa; wa.condition = condition; wa.exp_bssid = bssid; wa.exp_essid = NULL; wa.num_connected = 0; wa.res = B_FALSE; (void) dladm_walk(check_wlan, dld_handle, &wa, DATALINK_CLASS_PHYS, DL_WIFI, DLADM_OPT_ACTIVE); return (wa.num_connected > 0 && wa.res == B_TRUE); } /* * This function takes an activation mode and a string representation of a * condition and evaluates it. */ boolean_t nwamd_check_conditions(nwam_activation_mode_t activation_mode, char **condition_strings, uint_t num_conditions) { boolean_t ret; nwam_condition_t condition; nwam_condition_object_type_t object_type; char *object_name; int i, j; for (i = 0; i < num_conditions; i++) { if (nwam_condition_string_to_condition(condition_strings[i], &object_type, &condition, &object_name) != NWAM_SUCCESS) { nlog(LOG_ERR, "check_conditions: invalid condition %s", condition_strings[i]); return (B_FALSE); } ret = B_FALSE; for (j = 0; j < (sizeof (condition_map) / sizeof (struct nwamd_condition_map)); j++) { if (condition_map[j].object_type == object_type) ret = condition_map[j].condition_func(condition, object_name); } free(object_name); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY && ret) { return (B_TRUE); } if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL && !ret) { return (B_FALSE); } } if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY && ret) return (B_TRUE); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL && ret) return (B_TRUE); return (B_FALSE); } /* * In rating activation conditions, we take the best-rated CONDITIONAL_ANY * condition, or sum all the CONDITIONAL_ALL condition ratings. This allows * us to compare between location activation conditions to pick the best. */ uint64_t nwamd_rate_conditions(nwam_activation_mode_t activation_mode, char **conditions, uint_t num_conditions) { nwam_condition_t condition; nwam_condition_object_type_t object_type; char *object_name; int i; uint64_t rating = 0, total_rating = 0; for (i = 0; i < num_conditions; i++) { object_name = NULL; if (nwam_condition_string_to_condition(conditions[i], &object_type, &condition, &object_name) != NWAM_SUCCESS || nwam_condition_rate(object_type, condition, &rating) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_rate_conditions: could not rate " "condition"); free(object_name); return (0); } free(object_name); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY) { if (rating > total_rating) total_rating = rating; } else if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL) { total_rating += rating; } } return (total_rating); } /* * Different from nwamd_triggered_check_all_conditions() in that this * function enqueues a timed check event. */ void nwamd_set_timed_check_all_conditions(void) { nwamd_event_t check_event = nwamd_event_init (NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) { /* Add another timed event to recheck conditions */ nwamd_event_enqueue_timed(check_event, condition_check_interval > CONDITION_CHECK_INTERVAL_MIN ? condition_check_interval : CONDITION_CHECK_INTERVAL_MIN); } } /* * Does not enqueue another check event. */ void nwamd_check_all_conditions(void) { nwamd_enm_check_conditions(); nwamd_loc_check_conditions(); } void nwamd_create_timed_condition_check_event(void) { nwamd_event_t check_event = nwamd_event_init (NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) nwamd_event_enqueue(check_event); } void nwamd_create_triggered_condition_check_event(uint32_t when) { nwamd_event_t check_event; if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, NULL)) { check_event = nwamd_event_init (NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) nwamd_event_enqueue_timed(check_event, when); } }