diff options
author | jbeck <none@none> | 2007-03-30 17:01:13 -0700 |
---|---|---|
committer | jbeck <none@none> | 2007-03-30 17:01:13 -0700 |
commit | d71dbb732372504daff1f1783bc0d8864ce9bd50 (patch) | |
tree | 250aa1eba146725a05b071c536722a5e879d43f0 | |
parent | 623cc4421f2b351089438cb912f6d8b9f2fd3e6c (diff) | |
download | illumos-joyent-d71dbb732372504daff1f1783bc0d8864ce9bd50.tar.gz |
PSARC 2007/136 Network Auto-Magic (NWAM) Phase 0
6355747 /lib/svc/method/net-svc makes a mess of hosts and ipnodes
6366093 "ifconfig <wireless-lan-device> dhcp" not enough to surf with browser
6539574 _link_aton() underallocates a buffer
31 files changed, 5844 insertions, 131 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint index 70de3ea291..4c9f107de7 100644 --- a/usr/src/Makefile.lint +++ b/usr/src/Makefile.lint @@ -67,6 +67,7 @@ COMMON_SUBDIRS = \ cmd/cksum \ cmd/clinfo \ cmd/cmd-crypto \ + cmd/cmd-inet/lib \ cmd/cmd-inet/sbin \ cmd/cmd-inet/usr.bin \ cmd/cmd-inet/usr.lib/dsvclockd \ diff --git a/usr/src/Targetdirs b/usr/src/Targetdirs index 49abf05331..e8dbff7b77 100644 --- a/usr/src/Targetdirs +++ b/usr/src/Targetdirs @@ -203,6 +203,7 @@ ROOT.BIN= \ /etc/sma \ /etc/sma/snmp \ /lib \ + /lib/inet \ /lib/secure \ /lib/svc \ /lib/svc/bin \ diff --git a/usr/src/cmd/cmd-inet/Makefile b/usr/src/cmd/cmd-inet/Makefile index fc35bd8e68..1993f2f8f5 100644 --- a/usr/src/cmd/cmd-inet/Makefile +++ b/usr/src/cmd/cmd-inet/Makefile @@ -2,9 +2,8 @@ # CDDL HEADER START # # The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License"). You may not use this file except in compliance -# with the License. +# 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. @@ -22,15 +21,16 @@ # #ident "%Z%%M% %I% %E% SMI" # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -include ../Makefile.cmd +include ../Makefile.cmd -SUBDIRS= etc sbin usr.bin usr.sbin usr.lib usr.sadm -MSGSUBDIRS= usr.bin usr.sbin usr.lib usr.sadm -POFILES= usr.bin/usr.bin.po usr.sbin/usr.sbin.po usr.sadm/usr.sadm.po +SUBDIRS= etc lib sbin usr.bin usr.sbin usr.lib usr.sadm +MSGSUBDIRS= lib usr.bin usr.sbin usr.lib usr.sadm +POFILES= lib/lib.po usr.bin/usr.bin.po usr.sbin/usr.sbin.po \ + usr.sadm/usr.sadm.po POFILE= cmd-inet.po all:= TARGET= all diff --git a/usr/src/cmd/cmd-inet/Makefile.msg b/usr/src/cmd/cmd-inet/Makefile.msg new file mode 100644 index 0000000000..3e3d20911b --- /dev/null +++ b/usr/src/cmd/cmd-inet/Makefile.msg @@ -0,0 +1,34 @@ +# +# 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 +# +# +#ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +_msg:= TARGET= _msg + +_msg: $(MSGSUBDIRS) .WAIT $(POFILE) + +$(POFILE): $(POFILES) + $(RM) $(POFILE) + $(CAT) $(POFILES) > $(POFILE) diff --git a/usr/src/cmd/cmd-inet/lib/Makefile b/usr/src/cmd/cmd-inet/lib/Makefile new file mode 100644 index 0000000000..3818acfa67 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/Makefile @@ -0,0 +1,51 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +SUBDIRS= nwamd +MSGSUBDIRS= nwamd + +include ../../Makefile.cmd + +POFILES= $(MSGSUBDIRS:%=%/%.po) +POFILE= lib.po + +all:= TARGET= all +install:= TARGET= install +clean:= TARGET= clean +clobber:= TARGET= clobber +lint:= TARGET= lint + +.KEEP_STATE: + +all install clean clobber lint: $(SUBDIRS) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include ../Makefile.msg diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/Makefile b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile new file mode 100644 index 0000000000..e35f0bb4d3 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/Makefile @@ -0,0 +1,72 @@ +# +# 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 +# + +# +# ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# usr/src/cmd/cmd-inet/lib/nwamd/Makefile +# + +# Needed for ROOTFS_LIBDIR definition +include ../../../../lib/Makefile.lib + +PROG= nwamd +OBJS= events.o interface.o llp.o main.o \ + state_machine.o util.o wireless.o +SRCS= $(OBJS:%.o=%.c) +HEADERS= defines.h functions.h structures.h variables.h + +include ../../../Makefile.cmd + +POFILE= $(PROG).po +POFILES= interface.po wireless.po + +ROOTCMDDIR= $(ROOTFS_LIBDIR)/inet + +LDLIBS += -lsocket -lnsl -linetcfg -linetutil -lumem -lscf -ldladm + +.KEEP_STATE: + +.PARALLEL: + +all: $(PROG) + +$(PROG): $(OBJS) + $(LINK.c) $(OBJS) -o $@ $(LDLIBS) + $(POST_PROCESS) + +install: $(ROOTCMD) + +check: $(SRCS) $(HEADERS) + $(CSTYLE) -cpP $(SRCS) $(HEADERS) + +$(ROOTCMD): all + +clean: + $(RM) $(OBJS) + +lint: lint_SRCS + +include ../../../Makefile.targ +include ../../Makefile.msg diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/defines.h b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h new file mode 100644 index 0000000000..1033f9d7ab --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/defines.h @@ -0,0 +1,58 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _DEFINES_H +#define _DEFINES_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <sys/time.h> + +#define PKILL "/usr/bin/pkill" +#define ZENITY "/usr/bin/zenity" +#define SVCADM "/usr/sbin/svcadm" +#define IFCONFIG "/sbin/ifconfig" +#define NET_SVC_METHOD "/lib/svc/method/net-svc" +#define NET_SVC_FMRI "svc:/network/service:default" +#define PFEXEC "/usr/bin/pfexec" + +#define ULP_DIR "/etc/nwam/ulp" +#define LLPDIR "/etc/nwam" +#define LLPFILE LLPDIR"/llp" +#define KNOWN_WIFI_NETS LLPDIR"/known_wifi_nets" + +#define BOOLEAN_TO_STRING(x) ((x) ? "TRUE" : "FALSE") +#define STRING(s) (((s) == NULL) ? "NULL" : (s)) + +/* IPC listening port */ +#define NP_LISTEN_PORT 12340 + +#define NWAM_DEFAULT_DHCP_WAIT_TIME 60 /* 1 minute */ + +#define TIMER_INFINITY 0xffffffff /* we use uint32s for timers */ +#define NSEC_TO_SEC(nsec) (nsec) / NANOSEC + +#endif /* _DEFINES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/events.c b/usr/src/cmd/cmd-inet/lib/nwamd/events.c new file mode 100644 index 0000000000..8d23d0816c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/events.c @@ -0,0 +1,656 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file contains routines to retrieve events from the system and package + * them for high level processing. + * + * struct np_event is the basic event structure. The np_event structure and + * its npe_name member are allocated using malloc(3c). free_event() frees both + * the npe_name member and the associated np_event structure. + * + * np_queue_add_event() and np_queue_get_event() provide functionality for + * adding events to a queue and blocking on that queue for an event. + * + * Functions of the form addevent_*() provide the mechanism to cook down a + * higher level event into an np_event and put it on the queue. + * + * routing_events() reads routing messages off of an IPv4 routing socket and + * by calling addevent_*() functions places appropriate events on the queue. + * + * start_event_collection() creates a thread to run routing_events() and one + * to run periodic_wireless_scan() in. Finally it does an initial collection + * of information from each interface currently known. + */ + +#include <arpa/inet.h> +#include <assert.h> +#include <errno.h> +#include <libsysevent.h> +#include <net/if.h> +#include <net/route.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> +#include <sys/fcntl.h> +#include <sys/sysevent/eventdefs.h> +#include <syslog.h> +#include <unistd.h> +#include <fcntl.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +struct np_event *equeue = NULL; +static struct np_event *equeue_end = NULL; + +pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t queue_cond = PTHREAD_COND_INITIALIZER; +pthread_t routing, scan; + +static void printaddrs(int mask, void *address); +static char *printaddr(void **address); +static struct sockaddr *dupsockaddr(struct sockaddr *); +static boolean_t cmpsockaddr(struct sockaddr *, struct sockaddr *); +static void *getaddr(int addrid, int mask, void *address); + +union rtm_buf +{ + /* Routing information. */ + struct + { + struct rt_msghdr rtm; + struct sockaddr_storage addr[RTAX_MAX]; + } r; + + /* Interface information. */ + struct + { + struct if_msghdr ifm; + struct sockaddr_storage addr[RTAX_MAX]; + } im; + + /* Interface address information. */ + struct + { + struct ifa_msghdr ifa; + struct sockaddr_storage addr[RTAX_MAX]; + } ia; +}; + +void +free_event(struct np_event *e) +{ + free(e->npe_name); + free(e); +} + +void +np_queue_add_event(struct np_event *e) +{ + (void) pthread_mutex_lock(&queue_mutex); + if (equeue_end != NULL) { + equeue_end->npe_next = e; + equeue_end = e; + } else { + equeue = equeue_end = e; + } + equeue_end->npe_next = NULL; + (void) pthread_cond_signal(&queue_cond); + (void) pthread_mutex_unlock(&queue_mutex); +} + +/* + * Blocking getevent. This routine will block until there is an event for + * it to return. + */ +struct np_event * +np_queue_get_event(void) +{ + struct np_event *rv = NULL; + + (void) pthread_mutex_lock(&queue_mutex); + + while (equeue == NULL) + (void) pthread_cond_wait(&queue_cond, &queue_mutex); + + rv = equeue; + equeue = equeue->npe_next; + if (equeue == NULL) + equeue_end = NULL; + + (void) pthread_mutex_unlock(&queue_mutex); + + rv->npe_next = NULL; + return (rv); +} + +const char * +npe_type_str(enum np_event_type type) +{ + switch (type) { + case EV_ROUTING: + return ("ROUTING"); + case EV_SYS: + return ("SYS"); + case EV_TIMER: + return ("TIMER"); + case EV_SHUTDOWN: + return ("SHUTDOWN"); + case EV_NEWADDR: + return ("NEWADDR"); + default: + return ("unknown"); + } +} + +static void +addevent_routing_ifa(struct ifa_msghdr *ifa, const char *name) +{ + struct np_event *e; + + dprintf("addevent_routing_ifa"); + if (ifa->ifam_index == 0) { + /* what is this? */ + dprintf("tossing index 0 routing event"); + return; + } + + e = calloc(1, sizeof (*e)); + if (e == NULL) { + syslog(LOG_ERR, "calloc failed"); + return; + } + + switch (ifa->ifam_type) { + case RTM_NEWADDR: + assert(name != NULL); + e->npe_type = EV_NEWADDR; + if ((e->npe_name = strdup(name)) == NULL) { + syslog(LOG_ERR, "strdup failed"); + free(e); + return; + } + dprintf("adding event type %s name %s to queue", + npe_type_str(e->npe_type), STRING(e->npe_name)); + np_queue_add_event(e); + break; + + default: + free(e); + dprintf("unhandled type in addevent_routing_ifa %d", + ifa->ifam_type); + break; + } +} + +static void +addevent_routing_msghdr(struct if_msghdr *ifm, const char *name) +{ + struct np_event *e; + + dprintf("addevent_routing_msghdr"); + if (ifm->ifm_index == 0) { + /* what is this? */ + dprintf("tossing index 0 routing event"); + return; + } + + switch (ifm->ifm_type) { + case RTM_IFINFO: + assert(name != NULL); + e = calloc(1, sizeof (*e)); + if (e == NULL) { + syslog(LOG_ERR, "calloc failed"); + return; + } + + e->npe_type = EV_ROUTING; + if ((e->npe_name = strdup(name)) == NULL) { + syslog(LOG_ERR, "strdup failed"); + free(e); + return; + } + dprintf("flags = %x, IFF_RUNNING = %x", ifm->ifm_flags, + IFF_RUNNING); + dprintf("adding event type %s name %s to queue", + npe_type_str(e->npe_type), STRING(e->npe_name)); + np_queue_add_event(e); + break; + + default: + dprintf("unhandled type in addevent_routing_msghdr %d", + ifm->ifm_type); + break; + } +} + +static const char * +rtmtype_str(int type) +{ + static char typestr[12]; /* strlen("type ") + enough for an int */ + + switch (type) { + case RTM_ADD: + return ("ADD"); + case RTM_DELETE: + return ("DELETE"); + case RTM_NEWADDR: + return ("NEWADDR"); + case RTM_DELADDR: + return ("DELADDR"); + case RTM_IFINFO: + return ("IFINFO"); + default: + (void) snprintf(typestr, sizeof (typestr), "type %d", + type); + return (typestr); + } +} + +/* ARGSUSED */ +static void * +routing_events(void *arg) +{ + int rtsock; + int n; + union rtm_buf buffer; + struct rt_msghdr *rtm; + struct ifa_msghdr *ifa; + struct if_msghdr *ifm; + + /* + * We use v4 interfaces as proxies for links so those are the only + * routing messages we need to listen to. Look at the comments in + * structures.h for more information about the split between the + * llp and interfaces. + */ + rtsock = socket(AF_ROUTE, SOCK_RAW, AF_INET); + if (rtsock == -1) { + syslog(LOG_ERR, "failed to open routing socket: %m"); + exit(EXIT_FAILURE); + } + + dprintf("routing socket %d", rtsock); + + for (;;) { + struct interface *ifp; + char *addrs, *if_name; + struct sockaddr_dl *addr_dl; + struct sockaddr *addr; + + rtm = &buffer.r.rtm; + n = read(rtsock, &buffer, sizeof (buffer)); + if (n == -1 && errno == EAGAIN) { + continue; + } else if (n == -1) { + syslog(LOG_ERR, "error reading routing socket " + "%d: %m", rtsock); + /* Low likelihood. What's recovery path? */ + continue; + } + + if (rtm->rtm_msglen < n) { + syslog(LOG_ERR, "only read %d bytes from " + "routing socket but message claims to be " + "of length %d", rtm->rtm_msglen); + continue; + } + + if (rtm->rtm_version != RTM_VERSION) { + syslog(LOG_ERR, "tossing routing message of " + "version %d type %d", rtm->rtm_version, + rtm->rtm_type); + continue; + } + + if (rtm->rtm_msglen != n) { + dprintf("routing message of %d size came from " + "read of %d on socket %d", rtm->rtm_msglen, + n, rtsock); + } + + switch (rtm->rtm_type) { + case RTM_NEWADDR: + ifa = (void *)rtm; + addrs = (char *)ifa + sizeof (*ifa); + + dprintf("routing message NEWADDR: index %d flags %x", + ifa->ifam_index, ifa->ifam_flags); + printaddrs(ifa->ifam_addrs, addrs); + + if ((addr = (struct sockaddr *)getaddr(RTA_IFA, + ifa->ifam_addrs, addrs)) == NULL) + break; + + if ((addr_dl = (struct sockaddr_dl *)getaddr + (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) + break; + if_name = addr_dl->sdl_data; + ifp = get_interface(if_name); + if (ifp == NULL) { + dprintf("no interface struct for %s; ignoring " + "message", STRING(if_name)); + break; + } + + /* if no cached address, cache it */ + if (ifp->if_ipaddr == NULL) { + ifp->if_ipaddr = dupsockaddr(addr); + dprintf("cached address %s for link %s", + printaddr((void **)&addr), if_name); + addevent_routing_ifa(ifa, if_name); + } else if (!cmpsockaddr(addr, ifp->if_ipaddr)) { + free(ifp->if_ipaddr); + ifp->if_ipaddr = dupsockaddr(addr); + addevent_routing_ifa(ifa, if_name); + } + break; + case RTM_IFINFO: + { + boolean_t plugged_in; + + ifm = (void *)rtm; + addrs = (char *)ifm + sizeof (*ifm); + dprintf("routing message IFINFO: index %d flags %x", + ifm->ifm_index, ifm->ifm_flags); + printaddrs(ifm->ifm_addrs, addrs); + + if ((addr_dl = (struct sockaddr_dl *)getaddr(RTA_IFP, + ifm->ifm_addrs, addrs)) == NULL) + break; + if_name = addr_dl->sdl_data; + ifp = get_interface(if_name); + if (ifp == NULL) { + dprintf("no interface struct for %s; ignoring " + "message", STRING(if_name)); + break; + } + + /* + * Check for toggling of the IFF_RUNNING + * flag. + * + * On any change in the flag value, we + * turn off the DHCPFAILED and DHCPSTARTED + * flags; the change in the RUNNING state + * indicates a "fresh start" for the + * interface, so we should try dhcp again. + * + * Ignore specific IFF_RUNNING changes for + * wireless interfaces; their semantics are + * a bit different (either the flag is always + * on, or, with newer drivers, it indicates + * whether or not they are connected to an AP). + * + * For wired interfaces, if the interface was + * not plugged in and now it is, start info + * collection. + * + * If it was plugged in and now it is + * unplugged, generate an event. + * + * XXX We probably need a lock to protect + * if_flags setting and getting. + */ + if ((ifp->if_flags & IFF_RUNNING) != + (ifm->ifm_flags & IFF_RUNNING)) { + ifp->if_lflags &= ~IF_DHCPFAILED; + ifp->if_lflags &= ~IF_DHCPSTARTED; + } + if (ifp->if_type == IF_WIRELESS) + break; + plugged_in = + ((ifp->if_flags & IFF_RUNNING) != 0); + ifp->if_flags = ifm->ifm_flags; + if (!plugged_in && + (ifm->ifm_flags & IFF_RUNNING)) { + start_if_info_collect(ifp, NULL); + } else if (plugged_in && + !(ifm->ifm_flags & IFF_RUNNING)) { + check_drop_dhcp(ifp); + addevent_routing_msghdr(ifm, if_name); + } + break; + } + default: + dprintf("routing message %s socket %d discarded", + rtmtype_str(rtm->rtm_type), rtsock); + break; + } + } + /* NOTREACHED */ + return (NULL); +} + +/* return B_TRUE if sin_family and sin_addr are the same, B_FALSE if not */ +static boolean_t +cmpsockaddr(struct sockaddr *addr1, struct sockaddr *addr2) +{ + struct sockaddr_in *sina, *sinb; + struct sockaddr_in6 *sin6a, *sin6b; + + if (addr1->sa_family != addr2->sa_family) + return (B_FALSE); + + switch (addr1->sa_family) { + case AF_INET: + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sina = (struct sockaddr_in *)addr1; + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sinb = (struct sockaddr_in *)addr2; + return (sina->sin_addr.s_addr == sinb->sin_addr.s_addr); + case AF_INET6: + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sin6a = (struct sockaddr_in6 *)addr1; + /* LINTED E_BAD_PTR_CAST_ALIGN */ + sin6b = (struct sockaddr_in6 *)addr2; + return + (IN6_ARE_ADDR_EQUAL(&sin6a->sin6_addr, &sin6b->sin6_addr)); + default: + dprintf("cmpsockaddr: unsupported af (%d)", addr1->sa_family); + return (B_FALSE); + } +} + +/* + * Duplicate a sockaddr. Caller will be responsible for freeing memory when it + * is no longer needed. Currently only supports AF_INET and AF_INET6 + * (returns NULL otherwise). + */ +static struct sockaddr * +dupsockaddr(struct sockaddr *addr) +{ + struct sockaddr_in *t1, *ret1; + struct sockaddr_in6 *t2, *ret2; + + switch (addr->sa_family) { + case AF_INET: + if ((ret1 = calloc(1, sizeof (struct sockaddr_in))) == NULL) { + syslog(LOG_ERR, "dupsockaddr: calloc failed"); + return (NULL); + } + /* LINTED E_BAD_PTR_CAST_ALIGN */ + t1 = (struct sockaddr_in *)addr; + ret1->sin_family = t1->sin_family; + ret1->sin_addr.s_addr = t1->sin_addr.s_addr; + return ((struct sockaddr *)ret1); + case AF_INET6: + if ((ret2 = calloc(1, sizeof (struct sockaddr_in6))) == NULL) { + syslog(LOG_ERR, "dupsockaddr: calloc failed"); + return (NULL); + } + /* LINTED E_BAD_PTR_CAST_ALIGN */ + t2 = (struct sockaddr_in6 *)addr; + ret2->sin6_family = t2->sin6_family; + (void) memcpy((void *)&ret2->sin6_addr, + (const void *)&t2->sin6_addr, sizeof (struct in6_addr)); + return ((struct sockaddr *)ret2); + default: + dprintf("dupsockaddr: unsupported af (%d)", addr->sa_family); + return (NULL); + } +} + +static char * +printaddr(void **address) +{ + static char buffer[80]; + sa_family_t family = *(sa_family_t *)*address; + struct sockaddr_in *s4 = *address; + struct sockaddr_in6 *s6 = *address; + struct sockaddr_dl *dl = *address; + + switch (family) { + case AF_UNSPEC: + (void) inet_ntop(AF_UNSPEC, &s4->sin_addr, buffer, + sizeof (buffer)); + *address = (char *)*address + sizeof (*s4); + break; + case AF_INET: + (void) inet_ntop(AF_INET, &s4->sin_addr, buffer, + sizeof (buffer)); + *address = (char *)*address + sizeof (*s4); + break; + case AF_INET6: + (void) inet_ntop(AF_INET6, &s6->sin6_addr, buffer, + sizeof (buffer)); + *address = (char *)*address + sizeof (*s6); + break; + case AF_LINK: + (void) snprintf(buffer, sizeof (buffer), "link %s", + dl->sdl_data); + *address = (char *)*address + sizeof (*dl); + break; + default: + /* + * We can't reliably update the size of this thing + * because we don't know what its type is. So bump + * it by a sockaddr_in and see what happens. The + * caller should really make sure this never happens. + */ + *address = (char *)*address + sizeof (*s4); + (void) snprintf(buffer, sizeof (buffer), + "unknown address family %d", family); + break; + } + return (buffer); +} + +static void +printaddrs(int mask, void *address) +{ + if (mask == 0) + return; + if (mask & RTA_DST) + dprintf("destination address: %s", printaddr(&address)); + if (mask & RTA_GATEWAY) + dprintf("gateway address: %s", printaddr(&address)); + if (mask & RTA_NETMASK) + dprintf("netmask: %s", printaddr(&address)); + if (mask & RTA_GENMASK) + dprintf("cloning mask: %s", printaddr(&address)); + if (mask & RTA_IFP) + dprintf("interface name: %s", printaddr(&address)); + if (mask & RTA_IFA) + dprintf("interface address: %s", printaddr(&address)); + if (mask & RTA_AUTHOR) + dprintf("author: %s", printaddr(&address)); + if (mask & RTA_BRD) + dprintf("broadcast address: %s", printaddr(&address)); +} + +static void +nextaddr(void **address) +{ + sa_family_t family = *(sa_family_t *)*address; + + switch (family) { + case AF_UNSPEC: + case AF_INET: + *address = (char *)*address + sizeof (struct sockaddr_in); + break; + case AF_INET6: + *address = (char *)*address + sizeof (struct sockaddr_in6); + break; + case AF_LINK: + *address = (char *)*address + sizeof (struct sockaddr_dl); + break; + default: + syslog(LOG_ERR, "unknown af (%d) while parsing rtm", family); + break; + } +} + +static void * +getaddr(int addrid, int mask, void *address) +{ + int i; + void *p = address; + + if ((mask & addrid) == 0) + return (NULL); + + for (i = 1; i < addrid; i <<= 1) { + if (i & mask) + nextaddr(&p); + } + return (p); +} + +boolean_t +start_event_collection(void) +{ + int err; + + /* + * if these are ever created/destroyed repetitively then we will + * have to change this. + */ + + if (err = pthread_create(&routing, NULL, routing_events, NULL)) { + syslog(LOG_ERR, "pthread_create routing: %s", strerror(err)); + exit(EXIT_FAILURE); + } else { + dprintf("routing thread: %d", routing); + } + + if (err = pthread_create(&scan, NULL, periodic_wireless_scan, NULL)) { + syslog(LOG_ERR, "pthread_create wireless scan: %s", + strerror(err)); + exit(EXIT_FAILURE); + } else { + dprintf("scan thread: %d", scan); + } + + walk_interface(start_if_info_collect, NULL); + + return (B_TRUE); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/functions.h b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h new file mode 100644 index 0000000000..2f0d86ced0 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/functions.h @@ -0,0 +1,102 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _FUNCTIONS_H +#define _FUNCTIONS_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* events.c: event queue handling */ +extern void free_event(struct np_event *); +extern void np_queue_add_event(struct np_event *); +extern struct np_event *np_queue_get_event(void); +extern const char *npe_type_str(enum np_event_type); +extern boolean_t start_event_collection(void); + +/* interface.c: interface and upper layer profile handling */ +extern void check_drop_dhcp(struct interface *); +extern void gen_newif_event(struct interface *); +extern void initialize_interfaces(void); +extern struct interface *add_interface(sa_family_t, const char *, uint64_t); +extern struct interface *get_interface(const char *); +extern void walk_interface(void (*)(struct interface *, void *), void *); +extern enum interface_type find_if_type(const char *); +extern const char *if_type_str(enum interface_type); +extern boolean_t interface_is_active(const struct interface *); +extern void show_if_status(const char *); +extern boolean_t bringupinterface(const char *, const char *, const char *, + boolean_t); +extern void takedowninterface(const char *, boolean_t, boolean_t, boolean_t); +extern void take_down_all_ifs(const char *); +extern void check_interface_timer(struct interface *, void *); +extern void start_if_info_collect(struct interface *, void *); +extern boolean_t ulp_is_active(void); +extern void activate_upper_layer_profile(boolean_t, const char *); +extern void deactivate_upper_layer_profile(void); +extern void display(const char *); +extern int lookup_boolean_property(const char *, const char *, boolean_t *); +extern int lookup_count_property(const char *, const char *, uint64_t *); + +/* wireless.c: wifi link handling */ +extern void init_mutexes(void); +extern int get_user_preference(const char *, const char *, const char *, + struct wireless_lan **, const struct wireless_lan *); +extern boolean_t connect_chosen_lan(struct wireless_lan *, const char *); +struct wireless_lan *prompt_for_visited(void); +boolean_t handle_wireless_lan(const char *); +extern boolean_t scan_wireless_nets(struct interface *); +extern void create_known_wifi_nets_file(void); +extern void update_known_wifi_nets_file(const char *, const char *); +extern void *periodic_wireless_scan(void *); +extern boolean_t known_wifi_nets_lookup(const char *, const char *); + +/* llp.c: link layer profile handling */ +extern void llp_parse_config(void); +extern llp_t *llp_lookup(const char *); +extern llp_t *llp_high_pri(llp_t *, llp_t *); +extern llp_t *llp_best_avail(void); +extern boolean_t llp_activate(llp_t *); +extern void llp_deactivate(void); +extern void llp_swap(llp_t *); +extern char *llp_prnm(llp_t *); + +/* state_machine.c: state machine handling */ +extern void state_machine(struct np_event *); +extern void cleanup(void); + +/* util.c: utility & ipc functions */ +/* PRINTFLIKE1 */ +extern void dprintf(const char *fmt, ...); +extern uint32_t getcurrenttime(void); +extern uint64_t get_ifflags(const char *, sa_family_t); +extern boolean_t is_plugged_in(struct interface *); +extern int start_childv(const char *command, char const * const *argv); +extern int start_child(const char *command, ...); +extern void start_timer(uint32_t, uint32_t); +extern boolean_t valid_graphical_user(boolean_t); +extern void lookup_zonename(char *zonename, size_t zonesize); + +#endif /* _FUNCTIONS_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/interface.c b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c new file mode 100644 index 0000000000..3724275f6e --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/interface.c @@ -0,0 +1,1171 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file contains the routines that manipulate interfaces, the + * list of interfaces present on the system, and upper layer profiles; + * and various support functions. It also contains the functions used + * to display various bits of informations and queries for the user + * using /usr/bin/zenity, and a set of functions to read property + * values stored in the SMF repository. Finally, it contains the + * functions required for the "gather info" threads. + * + * The daemon maintains a list of structures that represent each IPv4 + * interface found on the system (after doing 'ifconfig -a plumb'). + * This list represents the objects manipulated by the daemon; while + * the list of llp_t structures represents the configuration details + * requested by the user (either the automatic defaults or entries in + * /etc/nwam/llp). IPv6 interfaces are not tracked in the interfaces + * list; rather, when the decision is made to make an interface active, + * IPv6 is brought up in addition to IPv4 (assuming the LLP configuration + * includes IPv6; this is the default for automatic configuration). + * + * Interfaces are brought up and torn down by a sequence of ifconfig + * commands (currently posix_spawn'd() by nwamd; the longer-term direction + * here is to use libinetcfg). + * + * Upper Layer Profile management is controlled by user-provided scripts, + * which should be created in /etc/nwam/ulp. One script, + * /etc/nwam/ulp/check-conditions, checks the current network setup and + * returns the name of the ULP which should be active under the current + * conditions. A ULP is specified by two scripts, found in + * /etc/nwam/ulp/<ulp name>: bringup and teardown. All scripts are + * optional; if they do not exist or are not executable, nwamd will + * simply move on. + * + * When an interface has been successfully brought up (signalled by the + * assignment of an IP address to the interface), the daemon will first + * teardown the existing ULP (if there is one) by running the teardown + * script for that ULP. It will then run the check-conditions script; + * if the name of a ULP is returned, it runs the bringup script for that + * ULP. + * + * A "gather info" thread is initiated for an interface when it becomes + * available. For a wired interface, "available" means the IFF_RUNNING + * flag is set; wireless interfaces are considered to always be available, + * so a wireless interface's gather info thread will run once, when it is + * found at startup. This thread will do a scan on a wireless interface, + * and initiate DHCP on a wired interface. It will then generate an event + * for the state machine that indicates the availability of a new interface. + */ + +#include <errno.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> +#include <pthread.h> +#include <sys/sockio.h> +#include <syslog.h> +#include <unistd.h> +#include <libscf.h> +#include <utmpx.h> +#include <pwd.h> +#include <sys/types.h> +#include <arpa/inet.h> +#include <inetcfg.h> +#include <locale.h> +#include <libintl.h> +#include <signal.h> +#include <stdarg.h> +#include <sys/sysmacros.h> +#include <libdllink.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +static struct interface *ifs_head = NULL; +static struct interface *ifs_wired = NULL; +static struct interface *ifs_wireless = NULL; + +static char upper_layer_profile[MAXHOSTNAMELEN]; + +static void print_interface_list(); +static struct interface *get_next_interface(struct interface *); + +#define IPFILTER_FMRI "svc:/network/ipfilter:default" +#define LOOPBACK_IF "lo0" + +void +display(const char *msg) +{ + char cmd[1024]; + + dprintf("display('%s')", STRING(msg)); + if (valid_graphical_user(B_FALSE)) { + (void) snprintf(cmd, sizeof (cmd), "--text=\"%s\"", msg); + (void) start_child(ZENITY, "--info", cmd, NULL); + } else { + syslog(LOG_INFO, "%s", msg); + } +} + +void +show_if_status(const char *ifname) +{ + char msg[128]; + icfg_if_t intf; + icfg_handle_t h; + struct sockaddr_in sin; + socklen_t addrlen = sizeof (struct sockaddr_in); + int prefixlen = 0; + + (void) strlcpy(intf.if_name, ifname, sizeof (intf.if_name)); + /* We only display new addr info for v4 interfaces */ + intf.if_protocol = AF_INET; + if (icfg_open(&h, &intf) != ICFG_SUCCESS) { + syslog(LOG_ERR, "icfg_open failed on interface %s", ifname); + return; + } + if (icfg_get_addr(h, (struct sockaddr *)&sin, &addrlen, &prefixlen, + B_TRUE) != ICFG_SUCCESS) { + syslog(LOG_ERR, "icfg_get_addr failed on interface %s", ifname); + icfg_close(h); + return; + } + icfg_close(h); + (void) snprintf(msg, sizeof (msg), + gettext("brought interface %s up, got address %s"), ifname, + inet_ntoa(sin.sin_addr)); + display(msg); +} + +/* + * If this interface matches the currently active llp, return B_TRUE. + * Otherwise, return B_FALSE. + */ +boolean_t +interface_is_active(const struct interface *ifp) +{ + if (link_layer_profile == NULL || ifp == NULL) + return (B_FALSE); + + return (strcmp(ifp->if_name, link_layer_profile->llp_lname) == 0); +} + +/* + * Execute 'ifconfig ifname dhcp wait 0'. + */ +static void +start_dhcp(struct interface *ifp) +{ + int res; + uint32_t now_s; + uint64_t timer_s; + + if ((ifp->if_lflags & IF_DHCPSTARTED) != 0) { + dprintf("start_dhcp: already started; returning"); + return; + } + ifp->if_lflags |= IF_DHCPSTARTED; + + (void) start_child(IFCONFIG, ifp->if_name, "dhcp", "wait", "0", NULL); + + /* start dhcp timer */ + res = lookup_count_property(OUR_PG, "dhcp_wait_time", &timer_s); + if (res == -1) + timer_s = NWAM_DEFAULT_DHCP_WAIT_TIME; + + now_s = NSEC_TO_SEC(gethrtime()); + ifp->if_timer_expire = now_s + timer_s; + + start_timer(now_s, timer_s); +} + +static boolean_t +check_svc_up(const char *fmri, int wait_time) +{ + int i; + char *state; + + for (i = 1; i <= wait_time; i++) { + state = smf_get_state(fmri); + if (strcmp(SCF_STATE_STRING_ONLINE, state) == 0) { + free(state); + return (B_TRUE); + } + free(state); + (void) sleep(1); + } + return (B_FALSE); +} + +boolean_t +ulp_is_active(void) +{ + return (upper_layer_profile[0] != '\0'); +} + +/* + * Inputs: + * res is a pointer to the scf_resources_t to be released. + */ +static void +release_scf_resources(scf_resources_t *res) +{ + scf_value_destroy(res->sr_val); + scf_property_destroy(res->sr_prop); + scf_pg_destroy(res->sr_pg); + scf_snapshot_destroy(res->sr_snap); + scf_instance_destroy(res->sr_inst); + (void) scf_handle_unbind(res->sr_handle); + scf_handle_destroy(res->sr_handle); +} + +/* + * Inputs: + * lpg is the property group to look up + * lprop is the property within that group to look up + * Outputs: + * res is a pointer to an scf_resources_t. This is an internal + * structure that holds all the handles needed to get a specific + * property from the running snapshot; on a successful return it + * contains the scf_value_t that should be passed to the desired + * scf_value_get_foo() function, and must be freed after use by + * calling release_scf_resources(). On a failure return, any + * resources that may have been assigned to res are released, so + * the caller does not need to do any cleanup in the failure case. + * Returns: + * 0 on success + * -1 on failure + */ +static int +get_property_value(const char *lpg, const char *lprop, scf_resources_t *res) +{ + res->sr_inst = NULL; + res->sr_snap = NULL; + res->sr_pg = NULL; + res->sr_prop = NULL; + res->sr_val = NULL; + + if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) { + syslog(LOG_ERR, "scf_handle_create() failed: %s", + scf_strerror(scf_error())); + return (-1); + } + + if (scf_handle_bind(res->sr_handle) != 0) { + scf_handle_destroy(res->sr_handle); + syslog(LOG_ERR, "scf_handle_destroy() failed: %s", + scf_strerror(scf_error())); + return (-1); + } + if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) { + syslog(LOG_ERR, "scf_instance_create() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if (scf_handle_decode_fmri(res->sr_handle, OUR_FMRI, NULL, NULL, + res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { + syslog(LOG_ERR, "scf_handle_decode_fmri() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) { + syslog(LOG_ERR, "scf_snapshot_create() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if (scf_instance_get_snapshot(res->sr_inst, "running", + res->sr_snap) != 0) { + syslog(LOG_ERR, "scf_instance_get_snapshot() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { + syslog(LOG_ERR, "scf_pg_create() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if (scf_instance_get_pg_composed(res->sr_inst, res->sr_snap, lpg, + res->sr_pg) != 0) { + syslog(LOG_ERR, "scf_instance_get_pg_composed(%s) failed: %s", + lpg, scf_strerror(scf_error())); + goto failure; + } + if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { + syslog(LOG_ERR, "scf_property_create() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if (scf_pg_get_property(res->sr_pg, lprop, res->sr_prop) != 0) { + syslog(LOG_ERR, "scf_pg_get_property(%s) failed: %s", + lprop, scf_strerror(scf_error())); + goto failure; + } + if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { + syslog(LOG_ERR, "scf_value_create() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) { + syslog(LOG_ERR, "scf_property_get_value() failed: %s", + scf_strerror(scf_error())); + goto failure; + } + return (0); + +failure: + release_scf_resources(res); + return (-1); +} + +/* + * Inputs: + * lpg is the property group to look up + * lprop is the property within that group to look up + * Outputs: + * answer is a pointer to the property value + * Returns: + * 0 on success + * -1 on failure + * If successful, the property value is retured in *answer. + * Otherwise, *answer is undefined, and it is up to the caller to decide + * how to handle that case. + */ +int +lookup_boolean_property(const char *lpg, const char *lprop, boolean_t *answer) +{ + int result = -1; + scf_resources_t res; + uint8_t prop_val; + + if (get_property_value(lpg, lprop, &res) != 0) { + /* + * an error was already logged by get_property_value, + * and it released any resources assigned to res before + * returning. + */ + return (result); + } + if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) { + syslog(LOG_ERR, "scf_value_get_boolean() failed: %s", + scf_strerror(scf_error())); + goto cleanup; + } + *answer = (boolean_t)prop_val; + dprintf("lookup_boolean_property(%s, %s) returns %s", lpg, lprop, + *answer ? "TRUE" : "FALSE"); + result = 0; +cleanup: + release_scf_resources(&res); + return (result); +} + +/* + * Inputs: + * lpg is the property group to look up + * lprop is the property within that group to look up + * Outputs: + * answer is a pointer to the property value + * Returns: + * 0 on success + * -1 on failure + * If successful, the property value is retured in *answer. + * Otherwise, *answer is undefined, and it is up to the caller to decide + * how to handle that case. + */ +int +lookup_count_property(const char *lpg, const char *lprop, uint64_t *answer) +{ + int result = -1; + scf_resources_t res; + + if (get_property_value(lpg, lprop, &res) != 0) { + /* + * an error was already logged by get_property_value, + * and it released any resources assigned to res before + * returning. + */ + return (result); + } + if (scf_value_get_count(res.sr_val, answer) != 0) { + syslog(LOG_ERR, "scf_value_get_count() failed: %s", + scf_strerror(scf_error())); + goto cleanup; + } + dprintf("lookup_count_property(%s, %s) returns %lld", lpg, lprop, + *answer); + result = 0; +cleanup: + release_scf_resources(&res); + return (result); +} + +void +activate_upper_layer_profile(boolean_t do_dhcp, const char *ifname) +{ + FILE *f; + char buffer[1024]; + size_t buflen; + size_t offset; + const char bringup[] = "/bringup"; + + if (do_dhcp) { + boolean_t should; + int res; + + res = lookup_boolean_property(OUR_PG, "use_net_svc", &should); + /* + * If the look-up failed, try anyway: only avoid this if we + * know for sure not to. + */ + if ((res == 0 && should) || (res == -1)) { + if (check_svc_up(NET_SVC_FMRI, 5)) { + (void) start_child(NET_SVC_METHOD, "start", + ifname, NULL); + } else { + syslog(LOG_WARNING, "timed out when waiting " + "for %s to come up, start method %s not " + "executed", NET_SVC_FMRI, NET_SVC_METHOD); + } + } + } + f = popen(ULP_DIR "/check-conditions", "r"); + if (f == NULL) + return; + /* + * We want to build a path to the user's upper layer profile script + * that looks like ULP_DIR "/<string we read here>/bringup". If we + * leave some space at the beginning of this buffer for ULP_DIR "/" + * that saves us some shuffling later. + */ + offset = sizeof (ULP_DIR); + if (fgets(buffer + offset, + MIN(sizeof (upper_layer_profile), sizeof (buffer) - offset), + f) == NULL) { + (void) pclose(f); + return; /* EOF before anything read */ + } + (void) pclose(f); + (void) memcpy(buffer, ULP_DIR "/", sizeof (ULP_DIR)); + buflen = strlen(buffer); + if (buffer[buflen - 1] == '\n') + buffer[--buflen] = '\0'; + (void) memcpy(upper_layer_profile, buffer + offset, + buflen + 1 - offset); + (void) strlcpy(buffer + buflen, bringup, sizeof (buffer) - buflen); + (void) start_child(PFEXEC, "-P", "basic", buffer, NULL); + + syslog(LOG_NOTICE, "upper layer profile %s activated", + upper_layer_profile); +} + +void +deactivate_upper_layer_profile(void) +{ + char buffer[1024]; + + /* + * If ULP wasn't defined... + */ + if (!ulp_is_active()) + return; + + (void) snprintf(buffer, sizeof (buffer), ULP_DIR "/%s/teardown", + upper_layer_profile); + (void) start_child(PFEXEC, "-P", "basic", buffer, NULL); + + syslog(LOG_NOTICE, "upper layer profile %s deactivated", + upper_layer_profile); + + upper_layer_profile[0] = '\0'; +} + +/* + * Returns B_TRUE if the interface is successfully brought up; + * B_FALSE if bringup fails. + */ +boolean_t +bringupinterface(const char *ifname, const char *host, const char *ipv6addr, + boolean_t ipv6onlink) +{ + boolean_t do_dhcp; + struct interface *intf; + uint64_t ifflags; + + intf = get_interface(ifname); + if (intf == NULL) { + syslog(LOG_ERR, "could not bring up interface %s: not in list", + ifname); + return (B_FALSE); + } + + /* check current state; no point going on if flags are 0 */ + if ((ifflags = get_ifflags(ifname, intf->if_family)) == 0) { + dprintf("bringupinterface(%s): get_ifflags() returned 0", + ifname); + return (B_FALSE); + } + + /* + * If the link layer profile says that we want v6 then plumb it and + * bring it up; if there's a static address, configure it as well. + */ + if (ipv6onlink) { + dprintf("bringupinterface: configuring ipv6"); + (void) start_child(IFCONFIG, ifname, "inet6", "plumb", "up", + NULL); + if (ipv6addr) { + (void) start_child(IFCONFIG, ifname, "inet6", "addif", + ipv6addr, "up", NULL); + } + } + + do_dhcp = (strcmp(host, "dhcp") == 0); + + /* + * If we need to use DHCP and DHCP is already controlling + * the interface, we don't need to do anything. + */ + if (do_dhcp && (ifflags & IFF_DHCPRUNNING) != 0) { + dprintf("bringupinterface: nothing to do"); + return (B_TRUE); + } + + if (intf->if_type == IF_WIRELESS) { + if (!handle_wireless_lan(ifname)) { + syslog(LOG_INFO, "Could not connect to any WLAN, not " + "bringing %s up", ifname); + return (B_FALSE); + } + } + + if (do_dhcp) { + start_dhcp(intf); + } else { + (void) start_child(IFCONFIG, ifname, host, NULL); + (void) start_child(IFCONFIG, ifname, "up", NULL); + } + + return (B_TRUE); +} + +void +takedowninterface(const char *ifname, boolean_t dhcp, boolean_t popup, + boolean_t v6onlink) +{ + uint64_t flags; + struct interface *ifp; + + dprintf("takedowninterface(%s, %s, %s, %s)", ifname, + BOOLEAN_TO_STRING(dhcp), BOOLEAN_TO_STRING(popup), + BOOLEAN_TO_STRING(v6onlink)); + + if ((ifp = get_interface(ifname)) == NULL) { + dprintf("takedowninterface: can't find interface struct for %s", + ifname); + } else { + if (ifp->if_lflags & IF_DHCPFAILED) { + /* + * We're here because of a dhcp failure, and + * we actually want dhcp to keep trying. So + * don't take the interface down. + */ + dprintf("takedowninterface: still trying for dhcp on " + "%s, so will not take down interface", ifname); + return; + } + } + + flags = get_ifflags(ifname, AF_INET); + + if (dhcp) { + if ((flags & IFF_DHCPRUNNING) != 0) { + /* + * We generally prefer doing a release, as that + * tells the server that it can relinquish the + * lease, whereas drop is just a client-side + * operation. But if we never came up, release + * will fail, because dhcpagent does not allow + * an interface which is selecting to release, + * so we have to drop in that circumstance. So + * try release first, then fall back to drop. + */ + if (start_child(IFCONFIG, ifname, "dhcp", "release", + NULL) != 0) { + (void) start_child(IFCONFIG, ifname, "dhcp", + "drop", NULL); + } + } + } else { + if ((flags & IFF_UP) != 0) + (void) start_child(IFCONFIG, ifname, "down", NULL); + /* need to unset a statically configured addr */ + (void) start_child(IFCONFIG, ifname, "0.0.0.0", "netmask", + "0", "broadcast", "0.0.0.0", NULL); + } + + if (v6onlink) { + /* + * Unplumbing the link local interface causes dhcp and ndpd to + * remove other addresses they have added. + */ + (void) start_child(IFCONFIG, ifname, "inet6", "unplumb", NULL); + } + + if (find_if_type(ifname) == IF_WIRELESS) + (void) dladm_wlan_disconnect(ifname); + + dprintf("takedown interface, free cached ip address"); + free(ifp->if_ipaddr); + ifp->if_ipaddr = NULL; + if (popup) { + char msg[64]; /* enough to hold this string */ + + (void) snprintf(msg, sizeof (msg), + gettext("took interface %s down"), ifname); + display(msg); + } +} + +/* + * Take down all known interfaces. If ignore_if is non-null, an + * active (IFF_UP) interface whose name matches ignore_if will *not* + * be taken down. + */ +void +take_down_all_ifs(const char *ignore_if) +{ + struct interface *ifp; + uint64_t flags; + boolean_t ignore_set = (ignore_if != NULL); + + deactivate_upper_layer_profile(); + + for (ifp = get_next_interface(NULL); ifp != NULL; + ifp = get_next_interface(ifp)) { + if (ignore_set && strcmp(ifp->if_name, ignore_if) == 0) + continue; + flags = get_ifflags(ifp->if_name, ifp->if_family); + if ((flags & IFF_UP) != 0) { + takedowninterface(ifp->if_name, + flags & IFF_DHCPRUNNING, B_FALSE, + ifp->if_family == AF_INET6); + } + } +} + +static struct interface * +get_next_interface(struct interface *ifp) +{ + return (ifp == NULL ? ifs_head : ifp->if_next); +} + +/* + * Add an interface struct to the interface list. The list is + * partially ordered; all the wired interfaces appear first, + * followed by all the wireless interfaces. New interfaces are + * added at the end of the appropriate list section. + */ +static void +interface_list_insert(struct interface *ifp) +{ + struct interface **wpp; + struct interface *endp; + boolean_t first_wireless = B_FALSE; + boolean_t first_wired = B_FALSE; + + switch (ifp->if_type) { + case IF_WIRELESS: + first_wireless = (ifs_wireless == NULL); + wpp = &ifs_wireless; + endp = NULL; + break; + + case IF_WIRED: + first_wired = (ifs_wired == NULL); + wpp = &ifs_wired; + endp = ifs_wireless; + break; + + default: + /* don't add to the list */ + return; + } + + /* set list head if this is the first entry */ + if (ifs_head == NULL) { + ifs_head = *wpp = ifp; + ifp->if_next = NULL; + return; + } + + if (*wpp != NULL) { + while (*wpp != endp) + wpp = &(*wpp)->if_next; + } + *wpp = ifp; + ifp->if_next = endp; + + /* update list head if we just inserted the first wired interface */ + if (first_wired) + ifs_head = ifs_wired; + + /* link sections if we just inserted the first wireless interface */ + if (first_wireless) { + wpp = &ifs_wired; + while (*wpp != NULL) + wpp = &(*wpp)->if_next; + *wpp = ifs_wireless; + } +} + +/* + * Returns the interface structure upon success. Returns NULL and sets + * errno upon error. If lr is null then it will look up the information + * needed. + * + * Note that given the MT nature of this program we are almost certainly + * racing for this structure. That needs to be fixed. + */ +struct interface * +add_interface(sa_family_t family, const char *name, uint64_t flags) +{ + struct interface *i; + enum interface_type iftype; + + if (name == NULL) + return (NULL); + + dprintf("add_interface: found interface %s", name); + if (family == AF_INET6) { + /* + * we don't track IPv6 interfaces separately from their + * v4 counterparts; a link either has v4 only, or both + * v4 and v6, so we only maintain a v4 interface struct. + */ + dprintf("not adding v6 interface for %s", name); + return (NULL); + } else if (family != AF_INET) { + /* + * the classic "shouldn't happen"... + */ + dprintf("not adding af %d interface for %s", family, name); + return (NULL); + } + + if ((iftype = find_if_type(name)) == IF_TUN) { + /* + * for now, we're ignoring tunnel interfaces (we expect + * them to be entirely manipulated by higher layer profile + * activation/deactivation scripts) + */ + dprintf("%s is a tunnel interface; ignoring", name); + return (NULL); + } + + if ((i = malloc(sizeof (*i))) == NULL) { + dprintf("add_interface: malloc failed"); + return (NULL); + } + + i->if_name = strdup(name); + if (i->if_name == NULL) { + free(i); + dprintf("add_interface: malloc failed"); + return (NULL); + } + i->if_ipaddr = NULL; + i->if_family = family; + i->if_type = iftype; + i->if_flags = flags == 0 ? get_ifflags(name, family) : flags; + i->if_lflags = 0; + i->if_timer_expire = 0; + + dprintf("added interface %s of type %s af %d; is %savailable", + i->if_name, if_type_str(i->if_type), i->if_family, + ((i->if_type == IF_WIRELESS) || + ((i->if_flags & IFF_RUNNING) != 0)) ? "" : "not "); + + interface_list_insert(i); + + return (i); +} + +/* + * Searches for an interface and returns the interface structure if found. + * Returns NULL otherwise. errno is set upon error exit. + */ +struct interface * +get_interface(const char *name) +{ + struct interface *i; + + if (name == NULL) + return (NULL); + + for (i = ifs_head; i != NULL; i = i->if_next) { + if (strcmp(name, i->if_name) == 0) { + return (i); + } + } + + return (NULL); +} + +/* + * Checks interface flags and, if IFF_DHCPRUNNING and !IFF_UP, does + * an 'ifconfig ifname dhcp drop'. + */ +void +check_drop_dhcp(struct interface *ifp) +{ + uint64_t flags = get_ifflags(ifp->if_name, ifp->if_family); + + if (!(flags & IFF_DHCPRUNNING) || (flags & IFF_UP)) { + dprintf("check_drop_dhcp: nothing to do (flags=0x%llx)", flags); + return; + } + + (void) start_child(IFCONFIG, ifp->if_name, "dhcp", "drop", NULL); +} + +/* + * For wireless interface, we will try to find out available wireless + * network; for wired, if dhcp should be used, start it now to try to + * avoid delays there. + * + * For the real code, we should pass back the network information + * gathered. Note that the state engine will then use the llp to + * determine which interface should be set up... + */ +static void * +gather_interface_info(void *arg) +{ + struct interface *i = arg; + llp_t *llp; + + assert(i != NULL); + + dprintf("Start gathering info for %s", i->if_name); + + switch (i->if_type) { + case IF_WIRELESS: + (void) scan_wireless_nets(i); + break; + case IF_WIRED: + /* + * It should not happen as the llp list should be done when + * this function is called. But let the state engine decide + * what to do. + */ + if ((llp = llp_lookup(i->if_name)) == NULL) + break; + /* + * The following is to avoid locking up the state machine + * as it is currently the choke point. We start dhcp with + * a wait time of 0; later, if we see the link go down + * (IFF_RUNNING is cleared), we will drop the attempt. + */ + if (llp->llp_ipv4src == IPV4SRC_DHCP && is_plugged_in(i)) + start_dhcp(i); + break; + default: + /* For other types, do not do anything. */ + return (NULL); + } + + gen_newif_event(i); + + dprintf("Done gathering info for %s", i->if_name); + return (NULL); +} + +void +gen_newif_event(struct interface *i) +{ + struct np_event *e; + + e = calloc(1, sizeof (struct np_event)); + if (e == NULL) { + dprintf("gen_newif_event: calloc failed"); + return; + } + e->npe_name = strdup(i->if_name); + if (e->npe_name == NULL) { + dprintf("gen_newif_event: strdup failed"); + free(e); + return; + } + e->npe_type = EV_ROUTING; + + /* + * This event notifies the state machine that a new interface is + * (at least nominally) available to be brought up. When the state + * machine processes the event, it will look at the entire list of + * interfaces and corresponding LLPs, and make a determination about + * the best available LLP under current conditions. + */ + np_queue_add_event(e); + dprintf("gen_newif_event: generated event for if %s", i->if_name); +} + +/* + * Caller uses this function to walk through the whole interface list. + * For each interface, the caller provided walker is called with + * the interface and arg as parameters. + * + * XXX There is no lock held right now for accessing the interface + * list. We probably need that in future. + */ +void +walk_interface(void (*walker)(struct interface *, void *), void *arg) +{ + struct interface *i; + + for (i = ifs_head; i != NULL; i = i->if_next) + walker(i, arg); +} + +static void +print_interface_list(void) +{ + struct interface *wp; + + dprintf("Walking interface list; starting with wired interfaces"); + for (wp = ifs_head; wp != NULL; wp = wp->if_next) { + if (wp == ifs_wireless) + dprintf("Now wireless interfaces"); + dprintf("==> %s", wp->if_name); + } +} + +/* + * Walker function passed to icfg_iterate_if() below - the icfg_if_it * + * argument is guaranteed to be non-NULL by icfg_iterate_if(), + * since the function it uses to generate the list - icfg_get_if_list()) - + * guarantees this. + */ +/* ARGSUSED */ +static int +do_add_interface(icfg_if_t *intf, void *arg) +{ + uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol); + + /* We don't touch loopback interface. */ + if (flags & IFF_LOOPBACK) + return (ICFG_SUCCESS); + + /* If adding fails, just ignore that interface... */ + (void) add_interface(intf->if_protocol, intf->if_name, flags); + + return (ICFG_SUCCESS); +} + +/* + * Walker function passed to icfg_iterate_if() below - the icfg_if_it * + * argument is guaranteed to be non-NULL by icfg_iterate_if(), + * since the function it uses to generate the list - icfg_get_if_list()) - + * guarantees this. + */ +/* ARGSUSED */ +static int +do_unplumb_if(icfg_if_t *intf, void *arg) +{ + uint64_t flags = get_ifflags(intf->if_name, intf->if_protocol); + + /* We don't touch loopback interface. */ + if (flags & IFF_LOOPBACK) + return (ICFG_SUCCESS); + + (void) start_child(IFCONFIG, intf->if_name, + intf->if_protocol == AF_INET6 ? "inet6" : "inet", "unplumb", NULL); + + return (ICFG_SUCCESS); +} + +void +initialize_interfaces(void) +{ + int times; + + dprintf("initialize_interfaces: setting link_layer_profile(%p) to NULL", + (void *)link_layer_profile); + link_layer_profile = NULL; + upper_layer_profile[0] = '\0'; + + /* + * Bring down all interfaces bar lo0. + */ + (void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_unplumb_if); + (void) icfg_iterate_if(AF_INET6, ICFG_PLUMBED, NULL, do_unplumb_if); + + /* + * In case dhcpagent is running... If it is running, when + * we do another DHCP command on the same interface later, it may + * be confused. Just kill dhcpagent to simplify handling. + */ + dprintf("killing dhcpagent"); + (void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL); + + /* + * Really we should walk the device tree instead of doing + * the 'ifconfig -a plumb'. + */ + for (times = 1; times <= 30; times++) { + if (start_child(IFCONFIG, "-a", "plumb", NULL) == 0) + break; + + /* + * Assume the di_init problem is the cause: sleep and try + * again, as the DDI has probably not been initialized yet. + */ + (void) sleep(1); + } + if (times > 30) + syslog(LOG_ERR, IFCONFIG "-a plumb failed %d times", times); + + (void) dladm_init_linkprop(); + + (void) icfg_iterate_if(AF_INET, ICFG_PLUMBED, NULL, do_add_interface); + + print_interface_list(); + +} + +/* + * Walker function used to start info gathering of each interface. + */ +/* ARGSUSED */ +void +start_if_info_collect(struct interface *ifp, void *arg) +{ + pthread_t if_thr; + pthread_attr_t attr; + + /* + * Only if the cable of the wired interface is + * plugged in, start gathering info from it. + */ + if (!is_plugged_in(ifp)) + return; + + /* + * This is a "fresh start" for the interface; if dhcp + * previously failed, the flag can now be cleared. + */ + ifp->if_lflags &= ~IF_DHCPFAILED; + + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (pthread_create(&if_thr, &attr, gather_interface_info, + (void *)ifp) != 0) { + syslog(LOG_ERR, "create interface gathering thread: %m"); + exit(EXIT_FAILURE); + } else { + dprintf("interface info thread: %d", if_thr); + } +} + +/* + * Walker function used to check timer for each interface. + * If timer has expired, generate a timer event for the + * interface. + */ +/* ARGSUSED */ +void +check_interface_timer(struct interface *ifp, void *arg) +{ + uint32_t now = *(uint32_t *)arg; + struct np_event *ev; + + if (ifp->if_timer_expire == 0) + return; + + if (ifp->if_timer_expire > now) { + start_timer(now, ifp->if_timer_expire - now); + return; + } + + ifp->if_timer_expire = 0; + + if ((ev = calloc(1, sizeof (*ev))) == NULL) { + dprintf("could not allocate timer event for %s; ignoring timer", + ifp->if_name); + return; + } + ev->npe_type = EV_TIMER; + ev->npe_name = strdup(ifp->if_name); + if (ev->npe_name == NULL) { + dprintf("could not strdup name for timer event on %s; ignoring", + ifp->if_name); + free(ev); + return; + } + np_queue_add_event(ev); +} + +enum interface_type +find_if_type(const char *name) +{ + enum interface_type type; + + if (name == NULL) { + dprintf("find_if_type: no ifname; returning IF_UNKNOWN"); + return (IF_UNKNOWN); + } + + if (strncmp(name, "ip.tun", 6) == 0) { + /* + * We'll need to update our tunnel detection once + * clearview/uv and clearview/tun driver projects + * go back; tunnel names won't necessarily be ip.tunN + */ + type = IF_TUN; + } else { + /* + * We didn't recognize it. Try the libdladm function + * to decide if it is wireless or not; if not, assume + * that it's wired. + */ + type = dladm_wlan_is_valid(name) ? IF_WIRELESS : IF_WIRED; + } + + return (type); +} + +const char * +if_type_str(enum interface_type type) +{ + switch (type) { + case IF_WIRED: + return ("wired"); + case IF_WIRELESS: + return ("wireless"); + case IF_TUN: + return ("tunnel"); + case IF_UNKNOWN: + default: + return ("unknown type"); + } +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/llp.c b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c new file mode 100644 index 0000000000..1ea8be7e1c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/llp.c @@ -0,0 +1,666 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file contains the routines that manipulate Link Layer Profiles + * (aka LLPs) and various support functions. This includes parsing and + * updating of the /etc/nwam/llp file. + * + * The daemon maintains a list of llp_t structures that represent the + * provided configuration information for a link. After the llp file + * is read, entries are added to the LLP list for any known links + * (identified by checking the interface list, which is based on the + * v4 interfaces present after 'ifconfig -a plumb') which were not + * represented in the llp file. These entries contain the default + * "automatic" settings: plumb both IPv4 and IPv6, use DHCP on the + * v4 interface, and accept router- and DHCPv6-assigned addresses on + * the v6 interface. The default entries created by the daemon are + * also added to the llp file. + * + * LLP priority is assigned based on two factors: the order within + * the llp file, with earlier entries having higher priority; and + * a preference for wired interfaces before wireless. Entries that + * are added to the file by the daemon are added *after* any existing + * entries; within the added block, wired entries are added before + * wireless. Thus if the llp file is never modified externally, wired + * will generally be ordered before wireless. However, if the + * administrator creates the file with wireless entries before wired, + * that priority order will be respected. + * + * The llp list (pointed to by the global llp_head) is protected by + * the global llp_lock, which should be pthread_mutex_lock()'d before + * reading or writing the list. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <strings.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syslog.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <atomic.h> +#include <pthread.h> +#include <signal.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +/* Lock to protect the llp list. */ +static pthread_mutex_t llp_lock = PTHREAD_MUTEX_INITIALIZER; + +llp_t *llp_head = NULL; +llp_t *link_layer_profile = NULL; + +/* + * Global variable to hold the highest priority. Need to use the atomic + * integer arithmetic functions to update it. + */ +static uint32_t llp_highest_pri = 0; + +static void print_llp_list(void); + +char * +llp_prnm(llp_t *llp) +{ + if (llp == NULL) + return ("null_llp"); + else if (llp->llp_lname == NULL) + return ("null_lname"); + else + return (llp->llp_lname); +} + +static void +llp_list_free(llp_t *head) +{ + llp_t **llpp; + llp_t *llpfree; + + if (pthread_mutex_lock(&llp_lock) != 0) { + /* Something very serious is wrong... */ + syslog(LOG_ERR, "llp_list_free: cannot lock mutex: %m"); + return; + } + llpp = &head; + while (*llpp != NULL) { + llpfree = *llpp; + *llpp = llpfree->llp_next; + free(llpfree->llp_ipv4addrstr); + free(llpfree); + } + (void) pthread_mutex_unlock(&llp_lock); +} + +llp_t * +llp_lookup(const char *link) +{ + llp_t *llp; + + if (link == NULL) + return (NULL); + + /* The name may change. Better hold the lock. */ + if (pthread_mutex_lock(&llp_lock) != 0) { + /* Something very serious is wrong... */ + syslog(LOG_ERR, "llp_lookup: cannot lock mutex: %m"); + return (NULL); + } + for (llp = llp_head; llp != NULL; llp = llp->llp_next) { + if (strcmp(link, llp->llp_lname) == 0) + break; + } + (void) pthread_mutex_unlock(&llp_lock); + return (llp); +} + +/* + * Choose the higher priority llp of the two passed in. If one is + * NULL, the other will be higher priority. If both are NULL, NULL + * is returned. + * + * Assumes that both are available (i.e. doesn't check IFF_RUNNING + * or IF_DHCPFAILED flag values). + */ +llp_t * +llp_high_pri(llp_t *a, llp_t *b) +{ + if (a == NULL) + return (b); + else if (b == NULL) + return (a); + + /* + * Higher priority is represented by a lower number. This seems a + * bit backwards, but for now it makes assigning priorities very easy. + * + * We shouldn't have ties right now, but just in case, tie goes to a. + */ + return ((a->llp_pri <= b->llp_pri) ? a : b); +} + +/* + * Chooses the highest priority link that corresponds to an + * available interface. + */ +llp_t * +llp_best_avail(void) +{ + llp_t *p, *rtnllp = NULL; + struct interface *ifp; + + /* The priority may change. Better hold the lock. */ + if (pthread_mutex_lock(&llp_lock) != 0) { + /* Something very serious is wrong... */ + syslog(LOG_ERR, "llp_best_avail: cannot lock mutex: %m"); + return (NULL); + } + for (p = llp_head; p != NULL; p = p->llp_next) { + ifp = get_interface(p->llp_lname); + if (ifp == NULL || !is_plugged_in(ifp) || + (ifp->if_lflags & IF_DHCPFAILED) != 0) + continue; + rtnllp = llp_high_pri(p, rtnllp); + } + (void) pthread_mutex_unlock(&llp_lock); + + return (rtnllp); +} + +/* + * Returns B_TRUE if llp is successfully activated; + * B_FALSE if activation fails. + */ +boolean_t +llp_activate(llp_t *llp) +{ + boolean_t rtn; + char *host; + /* + * Choosing "dhcp" as a hostname is unsupported right now. + * We use hostname="dhcp" as a keyword telling bringupinterface() + * to use dhcp on the interface. + */ + char *dhcpstr = "dhcp"; + + llp_deactivate(); + + host = (llp->llp_ipv4src == IPV4SRC_DHCP) ? dhcpstr : + llp->llp_ipv4addrstr; + + if (bringupinterface(llp->llp_lname, host, llp->llp_ipv6addrstr, + llp->llp_ipv6onlink)) { + link_layer_profile = llp; + dprintf("llp_activate: activated llp for %s", llp_prnm(llp)); + rtn = B_TRUE; + } else { + dprintf("llp_activate: failed to bringup %s", llp_prnm(llp)); + link_layer_profile = NULL; + rtn = B_FALSE; + } + + return (rtn); +} + +/* + * Deactivate the current active llp (link_layer_profile) + */ +void +llp_deactivate(void) +{ + if (link_layer_profile == NULL) + return; + + takedowninterface(link_layer_profile->llp_lname, + link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, B_TRUE, + link_layer_profile->llp_ipv6onlink); + + dprintf("llp_deactivate: setting link_layer_profile(%p) to NULL", + (void *)link_layer_profile); + link_layer_profile = NULL; +} + +/* + * Replace the currently active link layer profile with the one + * specified. And since we're changing the lower layer stuff, + * we need to first deactivate the current upper layer profile. + * An upper layer profile will be reactivated later, when we get + * confirmation that the new llp is fully up (has an address + * assigned). + * + * If the new llp is the same as the currently active one, don't + * do anything. + * + * If the new llp is NULL, just take down the currently active one. + */ +void +llp_swap(llp_t *newllp) +{ + char *upifname; + + if (newllp == link_layer_profile) + return; + + deactivate_upper_layer_profile(); + + if (link_layer_profile == NULL) { + /* + * there shouldn't be anything else running; + * make sure that's the case! + */ + upifname = (newllp == NULL) ? NULL : newllp->llp_lname; + take_down_all_ifs(upifname); + } else { + dprintf("taking down current link layer profile (%s)", + llp_prnm(link_layer_profile)); + llp_deactivate(); + } + if (newllp != NULL) { + dprintf("bringing up new link layer profile (%s)", + llp_prnm(newllp)); + (void) llp_activate(newllp); + } +} + +/* + * + * ifp->if_family == AF_INET, addr_src == DHCP ==> addr == NULL + * ifp->if_family == AF_INET, addr_src == STATIC ==> addr non null sockaddr_in + * ifp->if_family == AF_INET6, ipv6onlink == FALSE ==> addr == NULL + * ifp->if_family == AF_INET6, ipv6onlink == TRUE, + * if addr non NULL then it is the textual representation of the address + * and prefix. + * + * The above set of conditions describe what the inputs to this fuction are + * expected to be. Given input which meets those conditions this functions + * then outputs a line of configuration describing the inputs. + * + * Note that it is assumed only one thread can call this function at + * any time. So there is no lock to protect the file writing. This + * is true as the only caller of this function should originate from + * llp_parse_config(), which is done at program initialization time. + */ +static void +add_if_file(FILE *fp, struct interface *ifp, ipv4src_t addr_src, + boolean_t ipv6onlink, void *addr) +{ + char addr_buf[INET6_ADDRSTRLEN]; + + switch (ifp->if_family) { + case AF_INET: + switch (addr_src) { + case IPV4SRC_STATIC: + /* This is not supposed to happen... */ + if (addr == NULL) { + (void) fprintf(fp, "%s\tdhcp\n", ifp->if_name); + break; + } + (void) inet_ntop(AF_INET, addr, addr_buf, + INET6_ADDRSTRLEN); + (void) fprintf(fp, "%s\tstatic\t%s\n", ifp->if_name, + addr_buf); + break; + case IPV4SRC_DHCP: + /* Default is DHCP for now. */ + default: + (void) fprintf(fp, "%s\tdhcp\n", ifp->if_name); + break; + } + break; + + case AF_INET6: + if (ipv6onlink) + (void) fprintf(fp, "%s\tipv6\n", ifp->if_name); + break; + + default: + syslog(LOG_ERR, "interface %s of type %d?!", ifp->if_name, + ifp->if_family); + break; + } +} + +/* + * Walker function to pass to walk_interface() to add a default + * interface description to the LLPFILE. + * + * Regarding IF_TUN interfaces: see comments before find_and_add_llp() + * for an explanation of why we skip them. + */ +static void +add_if_default(struct interface *ifp, void *arg) +{ + FILE *fp = (FILE *)arg; + + if (ifp->if_type != IF_TUN) + add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL); +} + +/* Create the LLPFILE using info from the interface list. */ +static void +create_llp_file(void) +{ + FILE *fp; + int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + + /* Create the NWAM directory in case it does not exist. */ + if (mkdir(LLPDIR, dirmode) != 0) { + if (errno != EEXIST) { + syslog(LOG_ERR, "create NWAM directory: %m"); + return; + } + } + if ((fp = fopen(LLPFILE, "w")) == NULL) { + syslog(LOG_ERR, "create LLP config file: %m"); + return; + } + syslog(LOG_INFO, "Creating %s", LLPFILE); + walk_interface(add_if_default, fp); + (void) fclose(fp); +} + +/* + * Append an llp struct to the end of the llp list. + */ +static void +llp_list_append(llp_t *llp) +{ + llp_t **wpp = &llp_head; + + /* + * should be a no-op, but for now, make sure we only + * create llps for wired and wireless interfaces. + */ + if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) + return; + + if (pthread_mutex_lock(&llp_lock) != 0) { + /* Something very serious is wrong... */ + syslog(LOG_ERR, "llp_list_append: cannot lock mutex: %m"); + return; + } + + while (*wpp != NULL) + wpp = &(*wpp)->llp_next; + *wpp = llp; + llp->llp_next = NULL; + + (void) pthread_mutex_unlock(&llp_lock); +} + +/* + * Create a llp given the parameters and add it to the global list. + */ +static void +create_and_add_llp(const char *name, ipv4src_t ipv4src, const char *addrstr, + boolean_t ipv6onlink, const char *ipv6addrstr) +{ + llp_t *newllp; + int lnamelen; + + if ((newllp = llp_lookup(name)) != NULL) { + if (ipv6addrstr != NULL) { + newllp->llp_ipv6addrstr = strdup(ipv6addrstr); + if (newllp->llp_ipv6addrstr == NULL) { + syslog(LOG_ERR, "could not save ipv6 static " + "address for %s", name); + } + } + newllp->llp_ipv6onlink = ipv6onlink; + return; + } else if ((newllp = calloc(1, sizeof (llp_t))) == NULL) { + syslog(LOG_ERR, "calloc llp: %m"); + return; + } + + lnamelen = sizeof (newllp->llp_lname); + if (strlcpy(newllp->llp_lname, name, lnamelen) >= lnamelen) { + syslog(LOG_ERR, "llp: link name too long; ignoring entry"); + free(newllp); + return; + } + if (ipv4src == IPV4SRC_STATIC) { + if ((newllp->llp_ipv4addrstr = strdup(addrstr)) == NULL) { + syslog(LOG_ERR, "malloc ipaddrstr: %m"); + free(newllp); + return; + } + } else { + newllp->llp_ipv4addrstr = NULL; + } + newllp->llp_next = NULL; + newllp->llp_pri = atomic_add_32_nv(&llp_highest_pri, 1); + newllp->llp_ipv4src = ipv4src; + newllp->llp_type = find_if_type(newllp->llp_lname); + newllp->llp_ipv6onlink = ipv6onlink; + + if (ipv6onlink && ipv6addrstr != NULL) { + newllp->llp_ipv6addrstr = strdup(ipv6addrstr); + if (newllp->llp_ipv6addrstr == NULL) + syslog(LOG_WARNING, "could not store static address %s" + "on interface %s", ipv6addrstr, newllp->llp_lname); + } else { + newllp->llp_ipv6addrstr = NULL; + } + + llp_list_append(newllp); + + dprintf("created llp for link %s, pri %d", newllp->llp_lname, + newllp->llp_pri); +} + +/* + * Walker function to pass to walk_interface() to find out if + * an interface description is missing from LLPFILE. If it is, + * add it. + * + * Currently, IF_TUN type interfaces are special-cased: they are + * only handled as user-enabled, layered links (which may be created + * as part of a higher-layer profile, for example). Thus, they + * shouldn't be considered when looking at the llp list, so don't + * add them here. + */ +static void +find_and_add_llp(struct interface *ifp, void *arg) +{ + FILE *fp = (FILE *)arg; + + if (ifp->if_type != IF_TUN && (llp_lookup(ifp->if_name) == NULL)) { + dprintf("Adding %s to %s", ifp->if_name, LLPFILE); + add_if_file(fp, ifp, IPV4SRC_DHCP, B_TRUE, NULL); + /* If we run out of memory, ignore this interface for now. */ + create_and_add_llp(ifp->if_name, IPV4SRC_DHCP, NULL, + B_TRUE, NULL); + } +} + +/* + * This is a very "slow" function. It uses walk_interface() to find + * out if any of the interface is missing from the LLPFILE. For the + * missing ones, add them to the LLPFILE. + */ +static void +add_missing_if_llp(FILE *fp) +{ + walk_interface(find_and_add_llp, fp); +} + +static void +print_llp_list(void) +{ + llp_t *wp; + + dprintf("Walking llp list"); + for (wp = llp_head; wp != NULL; wp = wp->llp_next) + dprintf("==> %s", wp->llp_lname); +} + +/* + * This function parses /etc/nwam/llp which describes the phase 0 link layer + * profile. The file is line oriented with each line containing tab or space + * delimited fields. Each address family (IPv4, IPv6) is described on a + * separate line. + * The first field is a link name. + * The second field can be either static, dhcp, ipv6, or noipv6. + * If the second field is static then the next field is an ipv4 address which + * can contain a prefix. Previous versions of this file could contain a + * hostname in this field which is no longer supported. + * If the second field is dhcp then dhcp will be used on the interface. + * If the second field is ipv6 then an ipv6 interface is plumbed up. The + * outcome of this is that if offered by the network in.ndpd and dhcp + * will conspire to put addresses on additional ipv6 logical interfaces. + * If the next field is non-null then it is taken to be an IPv6 address + * and possible prefix which are applied to the interface. + * If the second field is noipv6 then no ipv6 interfaces will be put on that + * link. + */ +void +llp_parse_config(void) +{ + static const char STATICSTR[] = "static"; + static const char DHCP[] = "dhcp"; + static const char IPV6[] = "ipv6"; + static const char NOIPV6[] = "noipv6"; + FILE *fp; + char line[LINE_MAX]; + char *cp, *lasts, *lstr, *srcstr, *addrstr, *v6addrstr; + int lnum; + ipv4src_t ipv4src; + boolean_t ipv6onlink; + + fp = fopen(LLPFILE, "r+"); + if (fp == NULL) { + if (errno != ENOENT) { + /* + * XXX See comment before create_llp_file() re + * better error handling. + */ + syslog(LOG_ERR, "open LLP config file: %m"); + return; + } + + /* + * If there is none, we should create one instead. + * For now, we will use the order of the interface list + * for the priority. We should have a priority field + * in the llp file eventually... + */ + create_llp_file(); + + /* Now we can try to reopen the file for processing. */ + fp = fopen(LLPFILE, "r+"); + if (fp == NULL) { + syslog(LOG_ERR, "2nd open LLP config file: %m"); + return; + } + } + + if (llp_head != NULL) + llp_list_free(llp_head); + llp_head = NULL; + + for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) { + ipv4src = IPV4SRC_DHCP; + ipv6onlink = B_FALSE; + addrstr = NULL; + v6addrstr = NULL; + + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + cp = line; + while (isspace(*cp)) + cp++; + + if (*cp == '#' || *cp == '\0') + continue; + + dprintf("parsing llp conf file line %d...", lnum); + + if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) || + ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) { + syslog(LOG_ERR, "llp:%d: not enough tokens; " + "ignoring entry", lnum); + continue; + } + if (strcasecmp(srcstr, STATICSTR) == 0) { + if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL || + atoi(addrstr) == 0) { /* crude check for number */ + syslog(LOG_ERR, "llp:%d: missing ipaddr " + "for static config; ignoring entry", + lnum); + continue; + } + ipv4src = IPV4SRC_STATIC; + } else if (strcasecmp(srcstr, DHCP) == 0) { + ipv4src = IPV4SRC_DHCP; + } else if (strcasecmp(srcstr, IPV6) == 0) { + ipv6onlink = B_TRUE; + if ((addrstr = strtok_r(NULL, " \t", &lasts)) != NULL) { + v6addrstr = strdup(addrstr); + if (v6addrstr == NULL) { + syslog(LOG_ERR, "could not store v6 " + "static address %s for %s", + v6addrstr, lstr); + } + } else { + v6addrstr = NULL; + } + } else if (strcasecmp(srcstr, NOIPV6) == 0) { + ipv6onlink = B_FALSE; + } else { + syslog(LOG_ERR, "llp:%d: unrecognized " + "field; ignoring entry", lnum); + continue; + } + + create_and_add_llp(lstr, ipv4src, addrstr, ipv6onlink, + v6addrstr); + } + + /* + * So we have read in the llp file, is there an interface which + * it does not describe? If yes, we'd better add it to the + * file for future reference. Again, since we don't have a + * priority field yet, we will add the interface in the order + * in the interface list. + */ + add_missing_if_llp(fp); + + (void) fclose(fp); + + print_llp_list(); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/main.c b/usr/src/cmd/cmd-inet/lib/nwamd/main.c new file mode 100644 index 0000000000..1072a8f11c --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/main.c @@ -0,0 +1,392 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * nwamd - NetWork Auto-Magic Daemon + */ + +#include <fcntl.h> +#include <priv.h> +#include <pthread.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <syslog.h> +#include <unistd.h> +#include <locale.h> +#include <libintl.h> +#include <errno.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +#define TIMESPECGT(x, y) ((x.tv_sec > y.tv_sec) || \ + ((x.tv_sec == y.tv_sec) && (x.tv_nsec > y.tv_nsec))) + +const char *OUR_FMRI = "svc:/network/physical:nwam"; +const char *OUR_PG = "nwamd"; + +boolean_t fg = B_FALSE; +boolean_t shutting_down; +sigset_t original_sigmask; +char zonename[ZONENAME_MAX]; + +/* + * nwamd + * + * This is the Network Auto-Magic daemon. For further high level information + * see the Network Auto-Magic project and the Approachability communities + * on opensolaris.org, and nwamd(1M). + * + * The general structure of the code is as a set of threads collecting + * system events which are fed into a state machine which alters system + * state based on configuration. + * + * signal management + * Due to being threaded, a simple set of signal handlers would not work + * very well for nwamd. Instead nwamd blocks signals at startup and + * then starts a thread which sits in sigwait(2) waiting for signals. + * When a signal is received the signal handling thread dispatches it. + * It handles: + * - shutting down, done by creating an event which is passed through the + * system allowing the various subsystems to do any necessary cleanup. + * - SIGALRM for timers. + * - SIGHUP for instance refresh, which tells us to look up various + * properties from SMF(5). + * + * subprocess management + * nwamd starts several different subprocesses to manage the system. Some + * of those start other processes (e.g. `ifconfig <if> dhcp` ends up starting + * dhcpagent if necessary). Due to the way we manage signals if we started + * those up without doing anything special their signal mask would mostly + * block signals. So we restore the signal mask when we start subprocesses. + * This is especially important with respect to DHCP as later when we exit + * we need to kill the dhcpagent process which we started; for details, see + * the block comment in state_machine.c in its cleanup() function. + */ + +/* + * In this file there are several utility functions which might otherwise + * belong in util.c, but since they are only called from main(), they can + * live here as static functions: + * - syslog set-up + * - daemonizing + * - looking up SMF(5) properties + * - signal handling + * - managing privileges(5) + */ + +static void +start_logging(void) +{ + openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON); +} + +static void +daemonize(void) +{ + pid_t pid; + + /* + * A little bit of magic here. By the first fork+setsid, we + * disconnect from our current controlling terminal and become + * a session group leader. By forking again without calling + * setsid again, we make certain that we are not the session + * group leader and can never reacquire a controlling terminal. + */ + if ((pid = fork()) == (pid_t)-1) { + syslog(LOG_ERR, "fork 1 failed"); + exit(EXIT_FAILURE); + } + if (pid != 0) { + (void) wait(NULL); + dprintf("child %d exited, daemonizing", pid); + _exit(0); + } + if (setsid() == (pid_t)-1) { + syslog(LOG_ERR, "setsid"); + exit(EXIT_FAILURE); + } + if ((pid = fork()) == (pid_t)-1) { + syslog(LOG_ERR, "fork 2 failed"); + exit(EXIT_FAILURE); + } + if (pid != 0) { + _exit(0); + } + (void) chdir("/"); + (void) umask(022); + closelog(); + (void) closefrom(STDIN_FILENO); + (void) open("/dev/null", O_RDONLY); + (void) open("/dev/null", O_WRONLY); + (void) dup2(STDOUT_FILENO, STDERR_FILENO); + start_logging(); +} + +/* + * Look up nwamd property values and set daemon variables appropriately. + * This function will be called on startup and via the signal handling + * thread on receiving a HUP (which occurs when the nwam service is + * refreshed). + */ +static void +lookup_daemon_properties(void) +{ + boolean_t debug_set; + uint64_t scan_interval; + + if (lookup_boolean_property(OUR_PG, "debug", &debug_set) == 0) + debug = debug_set; + if (lookup_count_property(OUR_PG, "scan_interval", &scan_interval) == 0) + wlan_scan_interval = scan_interval; + dprintf("Read daemon configuration properties."); +} + +/* ARGSUSED */ +static void * +sighandler(void *arg) +{ + struct np_event *ev; + sigset_t sigset; + int sig; + uint32_t now; + + (void) sigfillset(&sigset); + + for (;;) { + sig = sigwait(&sigset); + dprintf("signal %d caught", sig); + switch (sig) { + case SIGALRM: + /* + * We may have multiple interfaces with + * scheduled timers; walk the list and + * create a timer event for each one. + */ + timer_expire = TIMER_INFINITY; + now = NSEC_TO_SEC(gethrtime()); + walk_interface(check_interface_timer, &now); + break; + case SIGHUP: + /* + * Refresh action - reread configuration properties. + */ + lookup_daemon_properties(); + break; + default: + syslog(LOG_NOTICE, "%s received, shutting down", + strsignal(sig)); + shutting_down = B_TRUE; + if ((ev = malloc(sizeof (*ev))) == NULL) { + dprintf("could not allocate shutdown event"); + cleanup(); + exit(EXIT_FAILURE); + } + ev->npe_type = EV_SHUTDOWN; + ev->npe_name = NULL; + np_queue_add_event(ev); + break; + } + + /* if we're shutting down, exit this thread */ + if (shutting_down) + return (NULL); + } +} + +static void +init_signalhandling(void) +{ + pthread_attr_t attr; + pthread_t sighand; + int err; + sigset_t new; + + (void) sigfillset(&new); + (void) pthread_sigmask(SIG_BLOCK, &new, &original_sigmask); + (void) pthread_attr_init(&attr); + (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (err = pthread_create(&sighand, &attr, sighandler, NULL)) { + syslog(LOG_ERR, "pthread_create system: %s", strerror(err)); + exit(EXIT_FAILURE); + } else { + dprintf("signal handler thread: %d", sighand); + } + (void) pthread_attr_destroy(&attr); +} + +static void +change_user_set_privs(void) +{ + priv_set_t *priv_set; + + priv_set = priv_allocset(); + if (getppriv(PRIV_PERMITTED, priv_set) == -1) { + dprintf("getppriv %s", strerror(errno)); + } else { + char *p; + + p = priv_set_to_str(priv_set, ',', 0); + dprintf("started with privs %s", p != NULL ? p : "Unknown"); + free(p); + } + + priv_emptyset(priv_set); + (void) priv_addset(priv_set, "basic"); + (void) priv_addset(priv_set, "file_chown_self"); + (void) priv_addset(priv_set, "file_dac_read"); + (void) priv_addset(priv_set, "file_dac_write"); + (void) priv_addset(priv_set, "net_privaddr"); + (void) priv_addset(priv_set, "net_rawaccess"); + (void) priv_addset(priv_set, "proc_exec"); + (void) priv_addset(priv_set, "proc_fork"); + (void) priv_addset(priv_set, "proc_info"); + (void) priv_addset(priv_set, "proc_owner"); + (void) priv_addset(priv_set, "proc_session"); + (void) priv_addset(priv_set, "proc_setid"); + (void) priv_addset(priv_set, "sys_ip_config"); + (void) priv_addset(priv_set, "sys_ipc_config"); + (void) priv_addset(priv_set, "sys_net_config"); + (void) priv_addset(priv_set, "sys_res_config"); + (void) priv_addset(priv_set, "sys_resource"); + + if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { + syslog(LOG_ERR, "setppriv inheritable: %m"); + priv_freeset(priv_set); + exit(EXIT_FAILURE); + } + + if (setppriv(PRIV_SET, PRIV_PERMITTED, priv_set) == -1) { + syslog(LOG_ERR, "setppriv permitted: %m"); + priv_freeset(priv_set); + exit(EXIT_FAILURE); + } + + if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { + syslog(LOG_ERR, "setppriv effective: %m"); + priv_freeset(priv_set); + exit(EXIT_FAILURE); + } + + priv_freeset(priv_set); +} + +int +main(int argc, char *argv[]) +{ + int c; + int scan_lev; + struct np_event *e; + + (void) setlocale(LC_ALL, ""); + (void) textdomain(TEXT_DOMAIN); + + shutting_down = B_FALSE; + start_logging(); + syslog(LOG_INFO, "nwamd pid %d started", getpid()); + + while ((c = getopt(argc, argv, "fs:")) != -1) { + switch (c) { + case 'f': + fg = B_TRUE; + break; + case 's': + scan_lev = atoi(optarg); + if (scan_lev >= DLADM_WLAN_STRENGTH_VERY_WEAK && + scan_lev <= DLADM_WLAN_STRENGTH_EXCELLENT) { + wireless_scan_level = scan_lev; + } else { + syslog(LOG_ERR, "invalid signal " + "strength: %s", optarg); + } + break; + default: + syslog(LOG_ERR, "unrecognized option %c", + optopt); + break; + } + } + + lookup_daemon_properties(); + + change_user_set_privs(); + + if (!fg) + daemonize(); + + init_signalhandling(); + + init_mutexes(); + + lookup_zonename(zonename, sizeof (zonename)); + + initialize_interfaces(); + + llp_parse_config(); + + (void) start_event_collection(); + + while ((e = np_queue_get_event()) != NULL) { /* forever */ + + syslog(LOG_INFO, "got event type %s", + npe_type_str(e->npe_type)); + switch (e->npe_type) { + case EV_ROUTING: + case EV_NEWADDR: + case EV_TIMER: + state_machine(e); + free_event(e); + break; + case EV_SYS: + free_event(e); + break; + case EV_SHUTDOWN: + state_machine(e); + (void) pthread_cancel(routing); + (void) pthread_cancel(scan); + (void) pthread_join(routing, NULL); + (void) pthread_join(scan, NULL); + syslog(LOG_INFO, "nwamd shutting down"); + exit(EXIT_SUCCESS); + /* NOTREACHED */ + default: + free_event(e); + syslog(LOG_NOTICE, "unknown event"); + break; + } + } + return (0); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c new file mode 100644 index 0000000000..19cbda649f --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/state_machine.c @@ -0,0 +1,218 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file contains the core logic of nwamd. + * + * This functionality is built around state_machine() which consumes an event. + * The events which are input to this function are generated by either a change + * in interface state or a signal. The events which correspond to signals are + * either external requests to shutdown or a timer event. The interface events + * indicate if an interface has acquired a new address (via DHCP) or if a new + * interface has appeared on the system. The latter event is used to detect new + * links. + * + * state_machine() calls high level routines in llp.c and interface.c to act on + * the state of the machine in response to events. + */ + +#include <assert.h> +#include <arpa/inet.h> +#include <errno.h> +#include <libsysevent.h> +#include <net/if.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <netinet/in.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/nvpair.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/types.h> +#include <syslog.h> +#include <unistd.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +void +state_machine(struct np_event *e) +{ + struct interface *evif; + llp_t *evllp, *prefllp = NULL; + uint64_t flags; + boolean_t dhcp_restored = B_FALSE; + + dprintf("state_machine(event type: %s, name: %s)", + npe_type_str(e->npe_type), STRING(e->npe_name)); + switch (e->npe_type) { + case EV_TIMER: + /* Our timer popped; check our dhcp status. */ + if ((evif = get_interface(e->npe_name)) == NULL) { + dprintf("couldn't find waiting interface; " + "ignoring EV_TIMER event"); + break; + } + flags = get_ifflags(evif->if_name, evif->if_family); + if ((flags & IFF_DHCPRUNNING) == 0 || (flags & IFF_UP) != 0) { + /* + * dhcp did come up successfully, or we're no + * longer trying to do dhcp in this interface; + * so no need to worry about the timer expiring. + */ + dprintf("timer popped for %s, but dhcp state is okay " + "(ifflags 0x%llx)", evif->if_name, flags); + break; + } + /* + * dhcp has not yet completed; give up on it for + * now and, if it is the currently active llp, + * switch to the next best. + */ + dprintf("giving up on dhcp on %s (ifflags 0x%llx)", + evif->if_name, flags); + evif->if_lflags |= IF_DHCPFAILED; + if (interface_is_active(evif)) { + prefllp = llp_best_avail(); + llp_swap(prefllp); + } + + break; + + case EV_ROUTING: + if ((evif = get_interface(e->npe_name)) == NULL || + (evllp = llp_lookup(e->npe_name)) == NULL) { + dprintf("state_machine: either no intf (%p) or no llp " + "(%p) for %s; ignoring EV_ROUTING event", + (void *)evif, (void *)evllp, STRING(e->npe_name)); + break; + } + prefllp = llp_best_avail(); + if (prefllp != link_layer_profile) { + dprintf("state_machine: change in state of link %s " + "resulted in new preferred llp: %s (was %s)", + llp_prnm(evllp), llp_prnm(prefllp), + llp_prnm(link_layer_profile)); + llp_swap(prefllp); + } + + break; + + case EV_NEWADDR: + if ((evif = get_interface(e->npe_name)) == NULL || + (evllp = llp_lookup(e->npe_name)) == NULL) { + dprintf("state_machine: either no intf (%p) or no llp " + "(%p) for %s; ignoring EV_NEWADDR event", + (void *)evif, (void *)evllp, STRING(e->npe_name)); + break; + } + flags = get_ifflags(evif->if_name, evif->if_family); + if (!(flags & IFF_DHCPRUNNING) && !(flags & IFF_UP) && + evllp->llp_ipv4src == IPV4SRC_DHCP) { + evif->if_timer_expire = 0; + if ((evif->if_lflags & IF_DHCPFAILED) != 0) { + evif->if_lflags &= ~IF_DHCPFAILED; + dhcp_restored = B_TRUE; + } + } + if (evllp != link_layer_profile) { + if (dhcp_restored && + llp_high_pri(evllp, link_layer_profile) == evllp) { + dprintf("state_machine: dhcp completed on " + "higher priority llp (%s); swapping", + llp_prnm(evllp)); + llp_swap(evllp); + } else { + dprintf("state_machine: newaddr event was for " + "%s, not for current active link (%s); " + "taking down %s", evllp->llp_lname, + llp_prnm(link_layer_profile), + evllp->llp_lname); + takedowninterface(evllp->llp_lname, + evllp->llp_ipv4src == IPV4SRC_DHCP, B_FALSE, + evllp->llp_ipv6onlink); + break; + } + } + /* + * An address has been assigned to the current active link. + * Notify the user, and activate the upper layer profile. + * + * Since other changes to the link (netmask change, broadcast + * addr change, etc.) can cause a NEWADDR event (XXX would + * be good if our event generator could do a better job + * filtering!), only do this if there is not currently an + * active ulp. + */ + if (!ulp_is_active()) { + show_if_status(evllp->llp_lname); + activate_upper_layer_profile(evllp->llp_ipv4src == + IPV4SRC_DHCP, evllp->llp_lname); + } + break; + + case EV_SHUTDOWN: + /* Cleanup not expecting to see any more events after this */ + cleanup(); + return; + /* NOTREACHED */ + + default: + dprintf("unknown event"); + break; + } +} + +void +cleanup(void) +{ + if (link_layer_profile != NULL) { + deactivate_upper_layer_profile(); + takedowninterface(link_layer_profile->llp_lname, + link_layer_profile->llp_ipv4src == IPV4SRC_DHCP, B_FALSE, + link_layer_profile->llp_ipv6onlink); + } + /* + * Since actions taken in nwamd result in dhcpagent being + * launched, it's under our contract. Thus, it needs to be + * stopped when our stop method is executed. But it needs + * to stick around long enough for us to release any leases + * we might have; thus, we don't want the stop method to + * explicitly kill it. We do it here, when we know we've + * finished any dhcp cleanup that needed to be done. + */ + dprintf("killing dhcpagent"); + (void) start_child(PKILL, "-z", zonename, "dhcpagent", NULL); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/structures.h b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h new file mode 100644 index 0000000000..d6e89ba3ce --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/structures.h @@ -0,0 +1,183 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _STRUCTURES_H +#define _STRUCTURES_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <net/if.h> +#include <libscf.h> +#include <libdlwlan.h> + +/* + * XXX More work on the state machine is needed. In the future, + * events will be more like EV_NEWADDR, identifying the actual + * event that we care about, rather than the source of the event + * that we originally implemented. For example, EV_ROUTING should + * be split into EV_LINK_UP and EV_LINK_DOWN. It's a bit of a mix + * right now. + */ +enum np_event_type { + EV_ROUTING, + EV_SYS, + EV_TIMER, + EV_SHUTDOWN, + EV_NEWADDR +}; + +enum interface_type { + IF_UNKNOWN, + IF_WIRED, + IF_WIRELESS, + IF_TUN +}; + +struct np_event { + enum np_event_type npe_type; + char *npe_name; + struct np_event *npe_next; +}; + +/* + * This structure is used to represent the current state of the system. We + * maintain these for IPv4 as proxies for links in the system. This is + * differentiated from the LLP which contains the intended configuration of + * the system. + * + * Currently these are stored on ifs_head with the wired interfaces sorted + * together and the wireless ones sorted together with ifs_wired and + * ifs_wireless pointed at these sublists. Access to these lists is not + * currently MT-safe. + * + * We explicitly do not maintain IPv6 interface structures. In the current + * state machine, IPv6 interfaces are completely dependent on their IPv4 + * counterparts. For example, we make a decision to bring up an interface + * based on routing socket messages read from an AF_INET socket; we will + * always bring up v4, and may additionally bring up v6. Also, when we + * start up, we find all interfaces in the system by doing 'ifconfig -a + * plumb', which will plumb v4 on all links; we always keep the v4 interface + * plumbed, taking it up and down depending on whether it's currently in use + * or not. v6 interfaces are not plumbed initially; when we decide a link + * should be active (and its llp tells us to do v6), we'll mark the (already + * existing) v4 interface up, and 'plumb up' the v6 interface. Conversely, + * when a link is no longer active, we 'down unplumb' the v6 interface, but + * only 'down' the v4 interface. + */ +struct interface { + char *if_name; + sa_family_t if_family; + uint64_t if_flags; + uint32_t if_lflags; + enum interface_type if_type; + uint32_t if_timer_expire; + struct sockaddr *if_ipaddr; + struct interface *if_next; +}; + +/* + * interface local flag values + */ +/* + * IF_DHCPFAILED: indicates that we timed out a dhcp request. Will be + * cleared if the request eventually succeeds, or if the IFF_RUNNING flag + * is toggled off/on (i.e. the cable is unplugged/plugged) + */ +#define IF_DHCPFAILED 0x01 +/* + * IF_DHCPSTARTED: much like IFF_DHCPRUNNING; but means specifically that + * we have inititated dhcp on this interface. Used to prevent overlapping + * invocations of dhcp. + */ +#define IF_DHCPSTARTED 0x02 + +/* + * visited_wlans are stored on visited_wlan_list of type visisted_wlans_list. + * Access is protected by wifi_mutex. + */ +struct visited_wlans { + struct wireless_lan *wifi_net; + struct visited_wlans *next; +}; + +struct visited_wlans_list { + struct visited_wlans *head; + int total; +}; + +typedef enum { + IPV4SRC_STATIC, + IPV4SRC_DHCP +} ipv4src_t; + +/* + * This structure contains the intended configuration of the system as + * differentiated from the actual IPv4 configuration of the system represented + * by the interface structures. + * + * llp structures are held on the list llp_head. Access to this list is + * protected by llp_lock. + */ +typedef struct llp { + struct llp *llp_next; + char llp_lname[LIFNAMSIZ]; + int llp_pri; /* lower number => higher priority */ + enum interface_type llp_type; + ipv4src_t llp_ipv4src; + char *llp_ipv4addrstr; /* if ipsrc is STATIC */ + char *llp_ipv6addrstr; /* if the user provided a static addr */ + boolean_t llp_ipv6onlink; /* true if we plumb up a v6 interface */ +} llp_t; + +/* + * These entries are user allocated and should be managed by whomever + * originates the structure. + */ +struct wireless_lan { + char *essid; + char *bssid; + char *signal_strength; + char *raw_wepkey; + dladm_wlan_wepkey_t *cooked_wepkey; + boolean_t need_wepkey; + char *wl_if_name; +}; + +/* + * A holder for all the resources needed to get a property value + * using libscf. + */ +typedef struct scf_resources { + scf_handle_t *sr_handle; + scf_instance_t *sr_inst; + scf_snapshot_t *sr_snap; + scf_propertygroup_t *sr_pg; + scf_property_t *sr_prop; + scf_value_t *sr_val; +} scf_resources_t; + + +#endif /* _STRUCTURES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/util.c b/usr/src/cmd/cmd-inet/lib/nwamd/util.c new file mode 100644 index 0000000000..31198df801 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/util.c @@ -0,0 +1,321 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * util.c contains a set of miscellaneous utility functions which: + * - syslog(LOG_DEBUG, ...) if debugging is enabled + * - check for an IP interface being marked running + * - look up all flags for an IP interface + * - start a child process + * - schedule a timer + * - check to see if a user is logged in to a graphical console + * - look up the zone name + */ + +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <pthread.h> +#include <string.h> +#include <strings.h> +#include <stropts.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <net/if.h> +#include <spawn.h> +#include <wait.h> +#include <inetcfg.h> +#include <utmpx.h> +#include <pwd.h> +#include <limits.h> +#include <errno.h> +#include <zone.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +extern char **environ; +boolean_t debug = B_FALSE; + +void +dprintf(const char *fmt, ...) +{ + va_list ap; + char vbuf[1024]; + + va_start(ap, fmt); + if (debug) { + (void) vsnprintf(vbuf, sizeof (vbuf), fmt, ap); + syslog(LOG_DEBUG, "%d: %s", pthread_self(), vbuf); + } + va_end(ap); +} + +boolean_t +is_plugged_in(struct interface *i) +{ + if (i->if_type == IF_WIRELESS) + return (B_TRUE); + + return ((get_ifflags(i->if_name, i->if_family) & IFF_RUNNING) != 0); +} + +uint64_t +get_ifflags(const char *name, sa_family_t family) +{ + icfg_if_t intf; + icfg_handle_t h; + uint64_t flags = 0; + + (void) strlcpy(intf.if_name, name, sizeof (intf.if_name)); + intf.if_protocol = family; + + if (icfg_open(&h, &intf) != ICFG_SUCCESS) + return (0); + + if (icfg_get_flags(h, &flags) != ICFG_SUCCESS) { + /* + * Interfaces can be ripped out from underneath us (for example + * by DHCP). We don't want to spam the console for those. + */ + if (errno == ENOENT) + dprintf("get_ifflags: icfg_get_flags failed for '%s'", + name); + else + syslog(LOG_ERR, "get_ifflags: icfg_get_flags %s af " + "%d: %m", name, family); + /* just to be sure... */ + flags = 0; + } + icfg_close(h); + + return (flags); +} + +/* + * + * This starts a child process determined by command. If command contains a + * slash then it is assumed to be a full path; otherwise the path is searched + * for an executable file with the name command. Command is also used as + * argv[0] of the new process. The rest of the arguments of the function + * up to the first NULL make up pointers to arguments of the new process. + * + * This function returns child exit status on success and -1 on failure. + * + * NOTE: original_sigmask must be set before this function is called. + */ +int +start_childv(const char *command, char const * const *argv) +{ + posix_spawnattr_t attr; + sigset_t fullset; + int i, rc, status, n; + pid_t pid; + char vbuf[1024]; + + vbuf[0] = 0; + n = sizeof (vbuf); + for (i = 1; argv[i] != NULL && n > 2; i++) { + n -= strlcat(vbuf, " ", n); + n -= strlcat(vbuf, argv[i], n); + } + if (argv[i] != NULL || n < 0) + syslog(LOG_ERR, "start_childv can't log full arg vector"); + + if ((rc = posix_spawnattr_init(&attr)) != 0) { + dprintf("posix_spawnattr_init %d %s\n", rc, strerror(rc)); + return (-1); + } + (void) sigfillset(&fullset); + if ((rc = posix_spawnattr_setsigdefault(&attr, &fullset)) != 0) { + dprintf("setsigdefault %d %s\n", rc, strerror(rc)); + return (-1); + } + if ((rc = posix_spawnattr_setsigmask(&attr, &original_sigmask)) != 0) { + dprintf("setsigmask %d %s\n", rc, strerror(rc)); + return (-1); + } + if ((rc = posix_spawnattr_setflags(&attr, + POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK)) != 0) { + dprintf("setflags %d %s\n", rc, strerror(rc)); + return (-1); + } + + if ((rc = posix_spawnp(&pid, command, NULL, &attr, (char * const *)argv, + environ)) > 0) { + dprintf("posix_spawnp failed errno %d", rc); + return (-1); + } + + if ((rc = posix_spawnattr_destroy(&attr)) != 0) { + dprintf("posix_spawn_attr_destroy %d %s\n", rc, strerror(rc)); + return (-1); + } + + (void) waitpid(pid, &status, 0); + if (WIFSIGNALED(status) || WIFSTOPPED(status)) { + i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); + syslog(LOG_ERR, "'%s%s' %s with signal %d (%s)", command, vbuf, + (WIFSIGNALED(status) ? "terminated" : "stopped"), i, + strsignal(i)); + return (-2); + } else { + syslog(LOG_INFO, "'%s%s' completed normally: %d", command, vbuf, + WEXITSTATUS(status)); + return (WEXITSTATUS(status)); + } +} + +int +start_child(const char *command, ...) +{ + const char **argv = NULL; + int argv_len = 0; + va_list ap; + int i = 1, rc; + + va_start(ap, command); + do { + if (i >= argv_len) { + void *p; + + argv_len = argv_len != 0 ? argv_len * 2 : 4; + p = realloc(argv, sizeof (*argv)*argv_len); + if (p != NULL) { + argv = p; + } else { + syslog(LOG_ERR, "Out of memory in start_child"); + free(argv); + return (-1); + } + } + + argv[i] = va_arg(ap, const char *); + } while (argv[i++] != NULL); + va_end(ap); + argv[0] = command; + + rc = start_childv(command, argv); + free(argv); + + return (rc); +} + +uint32_t timer_expire = TIMER_INFINITY; + +/* + * Schedules a SIGALRM in delay seconds, unless one is already + * scheduled sooner. If one is already scheduled later than + * delay seconds from now, that one will be replaced. + */ +void +start_timer(uint32_t now, uint32_t delay) +{ + if (now + delay > timer_expire) + return; + + timer_expire = now + delay; + (void) alarm(delay); +} + +boolean_t +valid_graphical_user(boolean_t query) +{ + struct utmpx *utp; + char *user = NULL; + const char HOMESTR[] = "HOME="; + char buf[1024]; /* == sysconf(_SC_GETPW_R_SIZE_MAX) == NSS_BUFSIZ */ + static char home_dir[PATH_MAX + sizeof (HOMESTR)]; + struct passwd passwd; + struct passwd *pw; + boolean_t popup_ok; + + /* + * Check to see if our SMF property says popups are OK. + */ + if ((lookup_boolean_property(OUR_PG, query ? "popup_query" : + "popup_info", &popup_ok) == 0) && !popup_ok) + return (B_FALSE); + + /* + * Look for someone logged into the console from host ":0" (i.e., + * the X display. Down the road, we should generalize this so + * ":0" is not hard-coded. + */ + setutxent(); + while ((utp = getutxent()) != NULL) { + if ((utp->ut_type == USER_PROCESS) && + (strcmp(utp->ut_line, "console") == 0) && + (strcmp(utp->ut_host, ":0") == 0)) { + user = strdup(utp->ut_user); + break; + } + } + endutxent(); + dprintf("utmpx: done %s", user != NULL ? user : ""); + + if (user == NULL) + return (B_FALSE); + + pw = getpwnam_r(user, &passwd, buf, sizeof (buf)); + if (pw == NULL) { + syslog(LOG_ERR, "couldn't get user %s: %m", user); + free(user); + return (B_FALSE); + } + free(user); + + /* + * We shouldn't be dumping this into our environment or changing + * our uid/gid but instead starting up the zenity processes with + * this display as this user. RFE to change this. + */ + (void) putenv("DISPLAY=:0.0"); + + (void) strlcpy(home_dir, HOMESTR, sizeof (home_dir)); + (void) strlcat(home_dir, pw->pw_dir, sizeof (home_dir)); + (void) putenv(home_dir); + + return (pw != NULL); +} + +void +lookup_zonename(char *zonename, size_t zonesize) +{ + zoneid_t zoneid = getzoneid(); + + if (getzonenamebyid(zoneid, zonename, zonesize) >= 0) + return; + syslog(LOG_ERR, "could not determine zone name"); + (void) strlcpy(zonename, GLOBAL_ZONENAME, zonesize); +} diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/variables.h b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h new file mode 100644 index 0000000000..2a4794f1be --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/variables.h @@ -0,0 +1,60 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _VARIABLES_H +#define _VARIABLES_H + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <libdlwlan.h> +#include <sys/zone.h> + +extern struct np_event *equeue; + +extern pthread_mutex_t queue_mutex; +extern pthread_cond_t queue_cond; +extern pthread_t routing, scan; + +extern llp_t *link_layer_profile; + +extern sigset_t original_sigmask; +extern pid_t ppid; + +extern boolean_t shutting_down; + +extern uint32_t timer_expire; + +extern uint_t wlan_scan_interval; +extern dladm_wlan_strength_t wireless_scan_level; + +extern const char *OUR_FMRI; +extern const char *OUR_PG; + +extern boolean_t debug; + +extern char zonename[ZONENAME_MAX]; + +#endif /* _VARIABLES_H */ diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c new file mode 100644 index 0000000000..72e6708f10 --- /dev/null +++ b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c @@ -0,0 +1,1596 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * This file containes all the routines to handle wireless (more + * accurately, 802.11 "WiFi" family only at this moment) operations. + * This is only phase 0 work so the handling is pretty simple. + * + * When the daemon starts up, for each WiFi interface detected, it'll + * spawn a thread doing an access point (AP) scanning. After the scans + * finish and if one of the WiFi interfaces is chosen to be active, the + * code will pop up a window showing the scan results and wait for the + * user's input on which AP to connect to and then complete the AP + * connection and IP interface set up. WEP is supported to connect to + * those APs which require it. The code also maintains a list of known + * WiFi APs in the file KNOWN_WIFI_NETS. Whenever the code successfully + * connects to an AP, the AP's ESSID/BSSID will be added to that file. + * This file is used in the following way. + * + * If the AP scan results contain one known AP, the code will automatically + * connect to that AP without asking the user. But if the detected signal + * strength of that AP is weaker than some other AP's, the code will still + * pop up a window asking for user input. + * + * If the AP scan results contain more than one known APs, the code will + * pop up a window listing those known APs only. If the user does not + * make a choice, the full list of available APs will be shown. + * + * If the AP scan results contain no known AP, the full list of available + * APs is shown and the user is asked which AP to connect to. + * + * Note that not all APs broadcast the Beacon. And some events may + * happen during the AP scan such that not all available APs are found. + * So the code also allows a user to manually input an AP's data in the + * pop up window. This allows a user to connect to the aforementioned + * "hidden" APs. + * + * The code also periodically (specified by wlan_scan_interval) checks + * for the health of the AP connection. If the signal strength of the + * connected AP drops below a threshold (specified by wireless_scan_level), + * the code will try to do another scan to find out other APs available. + * If there is currently no connected AP, a scan will also be done + * periodically to look for available APs. In both cases, if there are + * new APs, the above AP connection procedure will be performed. + * + * One limitation of the current code is that a user cannot initiate a + * WiFi APs scan manually. A manual scan can only be done when the code + * shows a pop up window asking for user input. Suppose there is no + * connected AP and periodic scan is going on. If a user wants to + * connect to a hidden AP, this is only possible if there is another + * non-hidden AP available such that after a periodic scan, the pop up + * window is shown. This will be fixed in a later phase. + */ + +#include <unistd.h> +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <strings.h> +#include <syslog.h> +#include <assert.h> +#include <limits.h> +#include <errno.h> +#include <sys/stat.h> +#include <syslog.h> +#include <pthread.h> +#include <sys/wait.h> +#include <stropts.h> +#include <sys/types.h> +#include <fcntl.h> +#include <locale.h> +#include <libintl.h> +#include <libdladm.h> +#include <libdllink.h> +#include <libinetutil.h> + +#include "defines.h" +#include "structures.h" +#include "functions.h" +#include "variables.h" + +static pthread_mutex_t wifi_mutex; +static pthread_mutexattr_t wifi_mutex_attr; +static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER; + +typedef enum { + SUCCESS = 0, + FAILURE, + TRY_AGAIN +} return_vals_t; + +/* + * Is a wireless interface doing a scan currently? We only allow one + * wireless interface to do a scan at any one time. This is to + * avoid unnecessary interference. The following variable is used + * to store the interface doing the scan. It is protected by + * wifi_init_mutex. + */ +static struct interface *wifi_scan_intf = NULL; + +/* used entries have non NULL memebers */ +static struct wireless_lan *wlans = NULL; +static uint_t wireless_lan_count = 0; /* allocated */ +static uint_t wireless_lan_used = 0; /* used entries */ + +static int wepkey_string_to_secobj_value(char *, uint8_t *, uint_t *); +static int store_wepkey(char *, char *, char *); +static dladm_wlan_wepkey_t *retrieve_wepkey(const char *, const char *); + +static boolean_t add_wlan_entry(struct interface *, char *, char *, char *, + boolean_t); +static boolean_t already_in_visited_wlan_list(const struct wireless_lan *); +static boolean_t check_wlan(const char *, const char *); +static boolean_t connect_or_autoconf(struct wireless_lan *, const char *); +static return_vals_t connect_to_new_wlan(const struct wireless_lan *, int, + const char *); +static boolean_t find_wlan_entry(struct interface *, char *, char *); +static void free_wireless_lan(struct wireless_lan *); +static struct wireless_lan *get_specific_lan(void); +static void get_user_wepkey(struct wireless_lan *); +static char *get_zenity_response(const char *); +static boolean_t wlan_autoconf(const char *ifname); +static int zenity_height(int); +static boolean_t get_scan_results(void *, dladm_wlan_attr_t *); + +#define WIRELESS_LAN_INIT_COUNT 8 + +struct visited_wlans_list *visited_wlan_list = NULL; + +/* + * The variable wlan_scan_interval controls the interval in seconds + * between periodic scans. + */ +uint_t wlan_scan_interval = 120; + +/* + * The variable wireless_scan_level specifies the lowest signal level + * when a periodic wireless scan needs to be done. + */ +dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; + +void +init_mutexes(void) +{ + (void) pthread_mutexattr_init(&wifi_mutex_attr); + (void) pthread_mutexattr_settype(&wifi_mutex_attr, + PTHREAD_MUTEX_RECURSIVE); + (void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr); +} + +/* + * wlan is expected to be non-NULL. + */ +static void +get_user_wepkey(struct wireless_lan *wlan) +{ + char zenity_cmd[1024]; + char buf[1024]; + FILE *zcptr; + + /* + * First, test if we have wepkey stored as secobj. If so, + * no need to prompt for it. + */ + wlan->cooked_wepkey = retrieve_wepkey(wlan->essid, wlan->bssid); + if (wlan->cooked_wepkey != NULL) { + dprintf("get_user_wepkey: retrieve_wepkey() returns non NULL"); + return; + } + + (void) snprintf(zenity_cmd, sizeof (zenity_cmd), + "%s --entry --text=\"%s %s\"" + " --title=\"%s\" --hide-text", ZENITY, + gettext("Enter WEP key for WiFi network"), wlan->essid, + gettext("Enter WEP key")); + + if (!valid_graphical_user(B_TRUE)) + return; + + zcptr = popen(zenity_cmd, "r"); + if (zcptr != NULL) { + if (fgets(buf, sizeof (buf), zcptr) != NULL) { + wlan->raw_wepkey = strdup(buf); + if (wlan->raw_wepkey != NULL) { + /* Store WEP key persistently */ + if (store_wepkey(wlan->essid, wlan->bssid, + wlan->raw_wepkey) != 0) { + syslog(LOG_ERR, + "get_user_wepkey: failed to store" + " user specified WEP key"); + } + } else { + syslog(LOG_ERR, + "get_user_wepkey: strdup failed"); + } + } + (void) pclose(zcptr); + } else { + syslog(LOG_ERR, "Could not run %s: %m", ZENITY); + } +} + +static boolean_t +find_wlan_entry(struct interface *intf, char *essid, char *bssid) +{ + int i; + + (void) pthread_mutex_lock(&wifi_mutex); + /* Check if the new entry is already there. */ + for (i = 0; i < wireless_lan_used; i++) { + /* + * Assume that essid and bssid are already NULL terminated. + * Note that we also check for the interface name here. + * If there is only one wireless interface, it should not + * matter. But if there are more than 1, then it is safer + * to use the interface which finds the AP to connect to + * it. + */ + if (strcmp(wlans[i].essid, essid) == 0 && + strcmp(wlans[i].bssid, bssid) == 0 && + strcmp(wlans[i].wl_if_name, intf->if_name) == 0) { + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_TRUE); + } + } + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_FALSE); +} + +static void +free_wireless_lan(struct wireless_lan *wlp) +{ + free(wlp->essid); + wlp->essid = NULL; + free(wlp->bssid); + wlp->bssid = NULL; + free(wlp->signal_strength); + wlp->signal_strength = NULL; + free(wlp->raw_wepkey); + wlp->raw_wepkey = NULL; + free(wlp->cooked_wepkey); + wlp->cooked_wepkey = NULL; + free(wlp->wl_if_name); + wlp->wl_if_name = NULL; +} + +static boolean_t +add_wlan_entry(struct interface *intf, char *essid, char *bssid, + char *signal_strength, boolean_t wep) +{ + int n; + + (void) pthread_mutex_lock(&wifi_mutex); + + if (wireless_lan_used == wireless_lan_count) { + int newcnt; + struct wireless_lan *r; + + newcnt = (wireless_lan_count == 0) ? + WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2; + r = realloc(wlans, newcnt * sizeof (*wlans)); + if (r == NULL) { + syslog(LOG_ERR, "add_wlan_entry: realloc failed"); + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_FALSE); + } + (void) memset((void *)(r + wireless_lan_count), 0, + (newcnt - wireless_lan_count) * sizeof (*r)); + wireless_lan_count = newcnt; + wlans = r; + } + + n = wireless_lan_used; + wlans[n].essid = strdup(essid); + wlans[n].bssid = strdup(bssid); + wlans[n].signal_strength = strdup(signal_strength); + wlans[n].wl_if_name = strdup(intf->if_name); + wlans[n].need_wepkey = wep; + wlans[n].raw_wepkey = NULL; + wlans[n].cooked_wepkey = NULL; + if (wlans[n].essid == NULL || wlans[n].bssid == NULL || + wlans[n].signal_strength == NULL || wlans[n].wl_if_name == NULL) { + syslog(LOG_ERR, "add_wlan_entry: strdup failed"); + free_wireless_lan(&(wlans[n])); + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_FALSE); + } + wireless_lan_used++; + (void) pthread_mutex_unlock(&wifi_mutex); + return (B_TRUE); +} + +static void +clear_lan_entries(void) +{ + int i; + + (void) pthread_mutex_lock(&wifi_mutex); + for (i = 0; i < wireless_lan_used; i++) + free_wireless_lan(&(wlans[i])); + wireless_lan_used = 0; + (void) pthread_mutex_unlock(&wifi_mutex); +} + +/* + * Verify if a WiFi NIC is associated with the given ESSID. If the given + * ESSID is NULL, and if the NIC is already connected, return true. + * Otherwise, + * + * 1. If the NIC is associated with the given ESSID, return true. + * 2. If the NIC is not associated with any AP, return false. + * 3. If the NIC is associated with a different AP, tell the driver + * to disassociate with it and then return false. + */ +static boolean_t +check_wlan(const char *intf, const char *exp_essid) +{ + dladm_wlan_linkattr_t attr; + dladm_status_t status; + char cur_essid[DLADM_STRSIZE]; + char errmsg[DLADM_STRSIZE]; + + status = dladm_wlan_get_linkattr(intf, &attr); + if (status != DLADM_STATUS_OK) { + dprintf("check_wlan: dladm_wlan_get_linkattr() failed: %s", + dladm_status2str(status, errmsg)); + return (B_FALSE); + } + if (attr.la_status == DLADM_WLAN_LINKSTATUS_DISCONNECTED) + return (B_FALSE); + if (exp_essid == NULL) + return (B_TRUE); + (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); + + /* Is the NIC associated with the expected one? */ + if (strcmp(cur_essid, exp_essid) == 0) + return (B_TRUE); + + /* Tell the driver to disassociate with the current AP. */ + if (dladm_wlan_disconnect(intf) != DLADM_STATUS_OK) + dprintf("check_wlan: dladm_wlan_disconnect() fails"); + return (B_FALSE); +} + +/* + * Given a wireless interface, use it to scan for available networks. + */ +boolean_t +scan_wireless_nets(struct interface *intf) +{ + boolean_t new_ap = B_FALSE; + dladm_status_t status; + int num_ap; + + assert(intf->if_type == IF_WIRELESS); + /* + * If there is already a scan in progress, wait until the + * scan is done to avoid interference. But if the interface + * doing the scan is the same as the one requesting the new + * scan, just return. + * + * Whenever a wireless scan is in progress, all the other + * threads checking the wireless AP list should wait. + */ + (void) pthread_mutex_lock(&wifi_init_mutex); + while (wifi_scan_intf != NULL) { + dprintf("scan_wireless_nets in progress: old %s new %s", + wifi_scan_intf->if_name, intf->if_name); + if (strcmp(wifi_scan_intf->if_name, intf->if_name) == 0) { + (void) pthread_mutex_unlock(&wifi_init_mutex); + return (B_FALSE); + } + (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); + } + wifi_scan_intf = intf; + (void) pthread_mutex_unlock(&wifi_init_mutex); + + /* + * Since only one scan is allowed at any one time, no need to grab + * a lock in checking wireless_lan_used. + */ + num_ap = wireless_lan_used; + status = dladm_wlan_scan(intf->if_name, intf, get_scan_results); + if (status != DLADM_STATUS_OK) + syslog(LOG_NOTICE, "cannot scan link '%s'", intf->if_name); + else + new_ap = (wireless_lan_used > num_ap); + + (void) pthread_mutex_lock(&wifi_init_mutex); + wifi_scan_intf = NULL; + (void) pthread_cond_signal(&wifi_init_cond); + (void) pthread_mutex_unlock(&wifi_init_mutex); + + return (new_ap); +} + +/* ARGSUSED */ +static void +wireless_scan(struct interface *ifp, void *arg) +{ + if (ifp->if_type == IF_WIRELESS) { + dprintf("periodic_wireless_scan: %s", ifp->if_name); + if (scan_wireless_nets(ifp)) { + dprintf("new AP added: %s", ifp->if_name); + gen_newif_event(ifp); + } + } +} + +static boolean_t +get_scan_results(void *arg, dladm_wlan_attr_t *attrp) +{ + + boolean_t wep; + char essid_name[DLADM_STRSIZE]; + char bssid_name[DLADM_STRSIZE]; + char strength[DLADM_STRSIZE]; + + (void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name); + (void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name); + (void) dladm_wlan_strength2str(&attrp->wa_strength, strength); + + wep = (attrp->wa_secmode == DLADM_WLAN_SECMODE_WEP); + + if (!find_wlan_entry(arg, essid_name, bssid_name) && + add_wlan_entry(arg, essid_name, bssid_name, strength, wep)) { + return (B_TRUE); + } + return (B_FALSE); +} + +/* ARGSUSED */ +void * +periodic_wireless_scan(void *arg) +{ + /* + * No periodic scan if the "-i" option is used to change the + * interval to 0. + */ + if (wlan_scan_interval == 0) + return (NULL); + + for (;;) { + int ret; + dladm_wlan_linkattr_t attr; + llp_t *cur_llp; + struct interface *ifp; + + ret = poll(NULL, 0, wlan_scan_interval * MILLISEC); + + /* + * We assume that once an llp is created, it will never be + * deleted in the lifetime of the process. So it is OK + * to do this assignment without a lock. + */ + cur_llp = link_layer_profile; + + /* + * We do a scan if + * + * 1. There is no active profile. Or + * 2. We are now disconnected from the AP. Or + * 3. The signal strength falls below a certain specified level. + */ + if (ret == 0) { + if (cur_llp != NULL) { + if (cur_llp->llp_type != IF_WIRELESS || + dladm_wlan_get_linkattr(cur_llp->llp_lname, + &attr) != DLADM_STATUS_OK) { + continue; + } + if (attr.la_status == + DLADM_WLAN_LINKSTATUS_CONNECTED && + attr.la_wlan_attr.wa_strength > + wireless_scan_level) { + continue; + } + /* + * Clear the IF_DHCPFAILED and IF_DHCPSTARTED + * flags on this interface; this is a "fresh + * start" for the interface, so we should + * retry dhcp. + */ + ifp = get_interface(cur_llp->llp_lname); + if (ifp != NULL) { + ifp->if_lflags &= ~IF_DHCPFAILED; + ifp->if_lflags &= ~IF_DHCPSTARTED; + } + /* + * Deactivate the original llp. + * If we reached this point, we either were + * not connected, or were connected with + * "very weak" signal strength; so we're + * assuming that having this llp active was + * not very useful. So we deactivate. + */ + llp_deactivate(); + } + /* We should start from fresh. */ + clear_lan_entries(); + walk_interface(wireless_scan, NULL); + } else if (ret == -1) { + if (errno == EINTR) + continue; + syslog(LOG_INFO, "periodic_wireless_scan: poll failed"); + return (NULL); + } + } + /* NOTREACHED */ + return (NULL); +} + +/* + * Below are functions used to handle storage/retrieval of WEP keys + * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj() + * and dladm_get_secobj(). + */ + +/* + * Convert wepkey hexascii string to raw secobj value. This + * code is very similar to convert_secobj() in dladm.c, it would + * be good to have a libdladm function to convert values. + */ +static int +wepkey_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp) +{ + size_t buf_len = strlen(buf); + + dprintf("before: wepkey_string_to_secobj_value: buf_len = %d", buf_len); + if (buf_len == 0) { + syslog(LOG_ERR, + "wepkey_string_to_secobj_value: empty WEP key"); + return (-1); + } + + if (buf[buf_len - 1] == '\n') + buf[--buf_len] = '\0'; + + dprintf("after: wepkey_string_to_secobj_value: buf_len = %d", buf_len); + switch (buf_len) { + case 5: /* ASCII key sizes */ + case 13: + (void) memcpy(obj_val, buf, (uint_t)buf_len); + *obj_lenp = (uint_t)buf_len; + break; + case 10: + case 26: /* Hex key sizes, not preceded by 0x */ + if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp) + != 0) { + syslog(LOG_ERR, + "wepkey_string_to_secobj_value: invalid WEP key"); + return (-1); + } + break; + case 12: + case 28: /* Hex key sizes, preceded by 0x */ + if (strncmp(buf, "0x", 2) != 0 || + hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val, + obj_lenp) != 0) { + syslog(LOG_ERR, + "wepkey_string_to_secobj_value: invalid WEP key"); + return (-1); + } + break; + default: + syslog(LOG_ERR, + "wepkey_string_to_secobj_value: invalid WEP key length"); + return (-1); + } + return (0); +} + +/* + * Print the key format into the appropriate field, then convert any ":" + * characters to ".", as ":[1-4]" is the slot indicator, which otherwise + * would trip us up. The third parameter is expected to be of size + * DLADM_SECOBJ_NAME_MAX. + */ +static void +set_key_name(const char *essid, const char *bssid, char *name, size_t nsz) +{ + int i; + + if (bssid == NULL) + (void) snprintf(name, nsz, "nwam-%s", essid); + else + (void) snprintf(name, nsz, "nwam-%s-%s", essid, bssid); + for (i = 0; i < strlen(name); i++) + if (name[i] == ':') + name[i] = '.'; +} + +static int +store_wepkey(char *essid, char *bssid, char *raw_wepkey) +{ + uint8_t obj_val[DLADM_SECOBJ_VAL_MAX]; + uint_t obj_len; + char obj_name[DLADM_SECOBJ_NAME_MAX]; + dladm_status_t status; + char errmsg[DLADM_STRSIZE]; + + /* + * Name wepkey object for this WLAN so it can be later retrieved + * (name is unique for each ESSID/BSSID combination). + */ + set_key_name(essid, bssid, obj_name, sizeof (obj_name)); + dprintf("store_wepkey: obj_name is %s", obj_name); + + if (wepkey_string_to_secobj_value(raw_wepkey, obj_val, &obj_len) != 0) { + /* above function logs internally on failure */ + return (-1); + } + + status = dladm_set_secobj(obj_name, DLADM_SECOBJ_CLASS_WEP, + obj_val, obj_len, + DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_TEMP); + if (status != DLADM_STATUS_OK) { + syslog(LOG_ERR, "store_wepkey: could not create secure object " + "'%s' for wepkey: %s", obj_name, + dladm_status2str(status, errmsg)); + return (-1); + } + return (0); +} + +/* + * retrieve_wepkey returns NULL if no wepkey was recovered from dladm + */ +static dladm_wlan_wepkey_t * +retrieve_wepkey(const char *essid, const char *bssid) +{ + dladm_status_t status; + char errmsg[DLADM_STRSIZE]; + dladm_wlan_wepkey_t *cooked_wepkey; + dladm_secobj_class_t class; + + /* + * Newly-allocated wepkey must be freed by caller, or by + * subsequent call to retrieve_wepkey(). + */ + if ((cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t))) == NULL) { + syslog(LOG_ERR, "retrieve_wepkey: malloc failed"); + return (NULL); + } + + /* Set name appropriately to retrieve wepkey for this WLAN */ + set_key_name(essid, bssid, cooked_wepkey->wk_name, + DLADM_SECOBJ_NAME_MAX); + dprintf("retrieve_wepkey: len = %d, object = %s\n", + strlen(cooked_wepkey->wk_name), cooked_wepkey->wk_name); + cooked_wepkey->wk_len = DLADM_SECOBJ_NAME_MAX; + cooked_wepkey->wk_idx = 1; + + /* Try the kernel first, then fall back to persistent storage. */ + status = dladm_get_secobj(cooked_wepkey->wk_name, &class, + cooked_wepkey->wk_val, &cooked_wepkey->wk_len, + DLADM_OPT_TEMP); + if (status != DLADM_STATUS_OK) { + dprintf("retrieve_wepkey: dladm_get_secobj(TEMP) failed: %s", + dladm_status2str(status, errmsg)); + status = dladm_get_secobj(cooked_wepkey->wk_name, &class, + cooked_wepkey->wk_val, &cooked_wepkey->wk_len, + DLADM_OPT_PERSIST); + } + + switch (status) { + case DLADM_STATUS_OK: + dprintf("retrieve_wepkey: dladm_get_secobj succeeded: len %d", + cooked_wepkey->wk_len); + break; + case DLADM_STATUS_NOTFOUND: + /* + * We do not want an error in the case that the secobj + * is not found, since we then prompt for it. + */ + free(cooked_wepkey); + return (NULL); + default: + syslog(LOG_ERR, "retrieve_wepkey: could not get wepkey " + "from secure object '%s': %s", cooked_wepkey->wk_name, + dladm_status2str(status, errmsg)); + free(cooked_wepkey); + return (NULL); + } + + return (cooked_wepkey); +} + +/* Create the KNOWN_WIFI_NETS using info from the interface list. */ +void +create_known_wifi_nets_file(void) +{ + FILE *fp; + int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + + /* Create the NWAM directory in case it does not exist. */ + if (mkdir(LLPDIR, dirmode) != 0) { + if (errno != EEXIST) { + syslog(LOG_ERR, "could not create %s: %m", LLPDIR); + return; + } + } + if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) { + syslog(LOG_ERR, "could not open %s: %m", KNOWN_WIFI_NETS); + return; + } + dprintf("Creating %s", KNOWN_WIFI_NETS); + (void) fclose(fp); +} + +/* + * Add an entry to known_wifi_nets file given the parameters. + */ +void +update_known_wifi_nets_file(const char *essid, const char *bssid) +{ + FILE *fp; + + dprintf("update_known_wifi_nets_file(%s, %s)", essid, STRING(bssid)); + fp = fopen(KNOWN_WIFI_NETS, "a+"); + if (fp == NULL) { + if (errno != ENOENT) { + syslog(LOG_ERR, "fopen(%s) failed: %m", + KNOWN_WIFI_NETS); + return; + } + + /* + * If there is none, we should create one instead. + * For now, we will use the order of seeing each new net + * for the priority. We should have a priority field + * in the known_wifi_nets file eventually... + */ + create_known_wifi_nets_file(); + fp = fopen(KNOWN_WIFI_NETS, "a"); + if (fp == NULL) { + syslog(LOG_ERR, "second fopen(%s) failed: %m", + KNOWN_WIFI_NETS); + return; + } + } + /* now see if this info is already in the file */ + if (known_wifi_nets_lookup(essid, bssid) == B_FALSE) { + /* now add this to the file */ + (void) fprintf(fp, "%s\t%s\n", essid, + bssid == NULL ? "" : bssid); + } + (void) fclose(fp); +} + +/* + * Check if the given AP (ESSID, BSSID pair) is on the known AP list. + */ +boolean_t +known_wifi_nets_lookup(const char *new_essid, const char *new_bssid) +{ + FILE *fp; + char line[LINE_MAX]; + char *cp, *lasts, *essid, *bssid; + int line_num; + boolean_t found = B_FALSE; + + /* + * For now the file format is: + * essid\tbssid + * (essid followed by tab followed by bssid) + */ + fp = fopen(KNOWN_WIFI_NETS, "r+"); + if (fp == NULL) { + if (errno != ENOENT) { + syslog(LOG_ERR, "fopen(%s) failed: %m", + KNOWN_WIFI_NETS); + return (B_FALSE); + } + create_known_wifi_nets_file(); + return (B_FALSE); + } + for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) { + if (line[strlen(line) - 1] == '\n') + line[strlen(line) - 1] = '\0'; + + cp = line; + while (isspace(*cp)) + cp++; + + if (*cp == '#' || *cp == '\0') + continue; + + if ((essid = strtok_r(cp, "\t", &lasts)) == NULL) { + syslog(LOG_ERR, "%s:%d: not enough tokens; " + "ignoring entry", KNOWN_WIFI_NETS, line_num); + continue; + } + bssid = strtok_r(NULL, "\t", &lasts); + if (strcmp(essid, new_essid) != 0) + continue; + if (new_bssid == NULL) { + /* + * no BSSID specified => ESSID match + * is good enough. + */ + found = B_TRUE; + } else if (bssid != NULL && strcmp(bssid, new_bssid) == 0) { + /* Match on both is always good. */ + found = B_TRUE; + } + if (found) + break; + } + (void) fclose(fp); + return (found); +} + +/* + * reqlan->essid is required (i.e., cannot be NULL) + * reqlan->bssid is optional (i.e., may be NULL) + */ +boolean_t +connect_chosen_lan(struct wireless_lan *reqlan, const char *ifname) +{ + uint_t keycount; + dladm_wlan_wepkey_t *key; + dladm_wlan_attr_t attr; + dladm_status_t status; + uint_t flags = DLADM_WLAN_CONNECT_NOSCAN; + int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT; + char errmsg[DLADM_STRSIZE]; + + (void) memset(&attr, 0, sizeof (attr)); + /* try to apply essid selected by the user */ + if (reqlan->essid == NULL) + return (B_FALSE); + dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid, + STRING(reqlan->bssid), ifname); + + /* If it is already connected to the required AP, just return. */ + if (check_wlan(ifname, reqlan->essid)) + return (B_TRUE); + + if (dladm_wlan_str2essid(reqlan->essid, &attr.wa_essid) != + DLADM_STATUS_OK) { + syslog(LOG_ERR, + "connect_chosen_lan: invalid ESSID '%s' for '%s'", + reqlan->essid, ifname); + return (B_FALSE); + } + attr.wa_valid = DLADM_WLAN_ATTR_ESSID; + if (reqlan->bssid != NULL) { + if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) != + DLADM_STATUS_OK) { + syslog(LOG_ERR, + "connect_chosen_lan: invalid BSSID '%s' for '%s'", + reqlan->bssid, ifname); + return (B_FALSE); + } + attr.wa_valid |= DLADM_WLAN_ATTR_BSSID; + } + + /* First check for the wepkey */ + if (reqlan->need_wepkey) { + get_user_wepkey(reqlan); + if (reqlan->cooked_wepkey == NULL) + return (B_FALSE); + attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; + attr.wa_secmode = DLADM_WLAN_SECMODE_WEP; + key = reqlan->cooked_wepkey; + keycount = 1; + dprintf("connect_chosen_lan: retrieved key"); + } else { + key = NULL; + keycount = 0; + } + + /* + * Connect; only scan if a bssid was not specified. + * If it times out and we were trying with a bssid, + * try a second time with just the ESSID. + */ + + status = dladm_wlan_connect(ifname, &attr, timeout, key, keycount, + flags); + dprintf("connect_chosen_lan: dladm_wlan_connect returned %s", + dladm_status2str(status, errmsg)); + if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid != NULL) { + syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), " + "trying again with just (%s)", + reqlan->essid, reqlan->bssid, reqlan->essid); + attr.wa_valid &= ~DLADM_WLAN_ATTR_BSSID; + flags = 0; + status = dladm_wlan_connect(ifname, &attr, timeout, key, + keycount, flags); + } + if (status != DLADM_STATUS_OK) { + syslog(LOG_ERR, + "connect_chosen_lan: connect to '%s' failed on '%s': %s", + reqlan->essid, ifname, dladm_status2str(status, errmsg)); + return (B_FALSE); + } + return (B_TRUE); +} + +/* + * First attempt to connect to the network specified by essid. + * If that fails, attempt to connect using autoconf. + */ +static boolean_t +connect_or_autoconf(struct wireless_lan *reqlan, const char *ifname) +{ + if (!connect_chosen_lan(reqlan, ifname)) { + syslog(LOG_WARNING, + "Could not connect to chosen WLAN %s, going to auto-conf", + reqlan->essid); + return (wlan_autoconf(ifname)); + } + return (B_TRUE); +} + +/* + * The +1 is for the extra "compare" row, the 24 is for the font spacing + * and the 125 if extra for the buttons et al. + */ +static int +zenity_height(int rows) +{ + return (((rows + 1) * 24) + 125); +} + +static return_vals_t +connect_to_new_wlan(const struct wireless_lan *lanlist, int num, + const char *ifname) +{ + int i, rtn, j = 0; + struct interface *intf; + struct wireless_lan *reqlan; + char buf[2048]; + char *endbuf = buf; + size_t buflen = 0; + char zenity_cmd[2048]; + char *other_str = gettext("Other"); + char *rescan_str = gettext("Rescan"); + boolean_t autoconf = B_FALSE; + + dprintf("connect_to_new_wlan(..., %d, %s)", num, ifname); + + if (num == 0) { + display(gettext("No Wifi networks found; continuing in case " + "you know of any which do not broadcast.")); + } + + if ((intf = get_interface(ifname)) == NULL) { + dprintf("connect_to_new_wlan: cannot find wireless interface: " + "%s", ifname); + return (FAILURE); + } + + /* build list for display */ + buf[0] = '\0'; + for (i = 0; i < num; i++) { + if ((lanlist[i].essid == NULL) || (lanlist[i].bssid == NULL)) { + syslog(LOG_WARNING, "wifi list entry %d broken: " + "essid %s, bssid %s; ignoring", i, + STRING(lanlist[i].essid), STRING(lanlist[i].bssid)); + continue; + } + /* + * Only use the interface which finds the AP to connect to it. + */ + if (strcmp(lanlist[i].wl_if_name, ifname) != 0) { + dprintf("connect_to_new_wlan: wrong interface (%s) for " + "%s (should be %s)", ifname, lanlist[i].essid, + lanlist[i].wl_if_name); + continue; + } + + j++; + /* + * Zenity uses a space as its delimiter, so put the ESSID in + * quotes so it won't get confused if there is a space in the + * ESSID name. + */ + if (sizeof (buf) - 1 > buflen) { + buflen += snprintf(endbuf, sizeof (buf) - buflen, + "%d '%s' %s %s '%s' ", j, + lanlist[i].essid, lanlist[i].bssid, + lanlist[i].need_wepkey ? "WEP" : "none", + lanlist[i].signal_strength); + endbuf = buf + buflen; + } + } + if (sizeof (buf) - 1 > buflen) { + /* + * All columns except the first are empty for the "Other" + * and "Rescan" rows. + */ + (void) snprintf(endbuf, sizeof (buf) - buflen, + "\"%s\" \"\" \"\" \"\" \"\" \"%s\" \"\" \"\" \"\" \"\"", + other_str, rescan_str); + } + + (void) snprintf(zenity_cmd, sizeof (zenity_cmd), + "%s --list --title=\"%s\"" + " --height=%d --width=500 --column=\"#\" --column=\"%s\"" + " --column=\"%s\" --column=\"%s\" --column=\"%s\"" + " %s ", ZENITY, gettext("Choose WiFi network you wish to activate"), + zenity_height(j), "ESSID", "BSSID", gettext("Encryption"), + gettext("Signal"), buf); + + /* present list to user and get selection */ + rtn = get_user_preference(zenity_cmd, other_str, rescan_str, &reqlan, + lanlist); + switch (rtn) { + case 1: + /* user chose "other"; pop-up for specific essid */ + reqlan = get_specific_lan(); + break; + case 2: + /* user chose "Rescan" */ + (void) scan_wireless_nets(intf); + return (TRY_AGAIN); + case -1: + reqlan = NULL; + break; + default: + /* common case: reqlan was set in get_user_preference() */ + break; + } + + if ((reqlan == NULL) || (reqlan->essid == NULL)) { + dprintf("did not get user preference; attempting autoconf"); + return (wlan_autoconf(ifname) ? SUCCESS : FAILURE); + } + dprintf("get_user_preference() returned essid %s, bssid %s, encr %s", + reqlan->essid, STRING(reqlan->bssid), + reqlan->need_wepkey ? "WEP" : "none"); + + /* set wepkey before first time connection */ + if (reqlan->need_wepkey && reqlan->raw_wepkey == NULL && + reqlan->cooked_wepkey == NULL) + get_user_wepkey(reqlan); + + /* + * now attempt to connect to selection, backing + * off to autoconf if the connect fails + */ + if (connect_chosen_lan(reqlan, ifname)) { + /* succeeded, so add entry to known_essid_list_file */ + update_known_wifi_nets_file(reqlan->essid, reqlan->bssid); + } else { + /* failed to connect; try auto-conf */ + syslog(LOG_WARNING, "Could not connect to chosen WLAN " + "%s; going to auto-conf", reqlan->essid); + autoconf = B_TRUE; + } + free_wireless_lan(reqlan); + + if (!autoconf) + return (SUCCESS); + return (wlan_autoconf(ifname) ? SUCCESS : FAILURE); +} + +struct wireless_lan * +prompt_for_visited(void) +{ + char buf[1024]; + size_t buflen = 0; + char *endbuf = buf; + char zenity_cmd[1024]; + char *select_str = gettext("Select from all available WiFi networks"); + char *rescan_str = gettext("Rescan"); + struct wireless_lan *req_conf = NULL, *list; + int i = 0; + + /* Build zenity command string */ + buf[0] = '\0'; + if (visited_wlan_list->total > 0) { + struct visited_wlans *vlp; + struct wireless_lan *wlp; + + list = calloc(visited_wlan_list->total, + sizeof (struct wireless_lan)); + if (list == NULL) { + syslog(LOG_ERR, "prompt_for_visited: calloc failed"); + return (NULL); + } + for (vlp = visited_wlan_list->head; + vlp != NULL; vlp = vlp->next) { + + wlp = vlp->wifi_net; + if (wlp->essid == NULL || wlp->bssid == NULL) { + dprintf("Invalid essid/bssid values"); + continue; + } + i++; + if (sizeof (buf) - 1 > buflen) { + buflen += snprintf(endbuf, + sizeof (buf) - buflen, + "%d '%s' %s %s '%s' ", + i, wlp->essid, wlp->bssid, + wlp->need_wepkey ? "WEP" : gettext("none"), + wlp->signal_strength); + endbuf = buf + buflen; + } + list[i-1].essid = wlp->essid; + list[i-1].bssid = wlp->bssid; + list[i-1].need_wepkey = wlp->need_wepkey; + list[i-1].raw_wepkey = wlp->raw_wepkey; + list[i-1].cooked_wepkey = wlp->cooked_wepkey; + list[i-1].signal_strength = wlp->signal_strength; + list[i-1].wl_if_name = wlp->wl_if_name; + } + } + if (sizeof (buf) - 1 > buflen) { + (void) snprintf(endbuf, sizeof (buf) - buflen, + "\"%s\"", select_str); + } + + (void) snprintf(zenity_cmd, sizeof (zenity_cmd), + "%s --list --title=\"%s\"" + " --height=%d --width=670 --column=\"#\" --column=\"%s\"" + " --column=\"%s\" --column=\"%s\" --column=\"%s\"" + " %s", ZENITY, gettext("Choose from pre-visited WiFi network"), + zenity_height(i), "ESSID", "BSSID", gettext("Encryption"), + gettext("Signal"), buf); + + /* + * If the user doesn't make a choice or something goes wrong + * (get_user_preference() returned -1), or if the user chooses + * the "select from all available" string (get_user_preference() + * returned 1), we simply return NULL: there was no selection + * made from the visited list. If the user *did* make a choice + * (get_user_preference() returned 0), return the alloc'd struct. + */ + if (get_user_preference(zenity_cmd, select_str, rescan_str, + &req_conf, list) != 0) + req_conf = NULL; + + free(list); + return (req_conf); +} + +static boolean_t +wlan_autoconf(const char *ifname) +{ + dladm_status_t status; + boolean_t autoconf; + + if (lookup_boolean_property(OUR_PG, "autoconf", &autoconf) == 0) { + if (!autoconf) + return (B_FALSE); + } + + /* If the NIC is already associated with something, just return. */ + if (check_wlan(ifname, NULL)) + return (B_TRUE); + + /* + * Do autoconf, relying on the heuristics used by dladm_wlan_connect() + * to cycle through WLANs detected in priority order, attempting + * to connect. + */ + status = dladm_wlan_connect(ifname, NULL, + DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0); + if (status != DLADM_STATUS_OK) { + char errmsg[DLADM_STRSIZE]; + + syslog(LOG_ERR, + "wlan_autoconf: dladm_wlan_connect failed for '%s': %s", + ifname, dladm_status2str(status, errmsg)); + return (B_FALSE); + } + return (B_TRUE); +} + +/* + * Returns: + * B_TRUE if this info is already in visited_wlan_list + * B_FALSE if not + */ +static boolean_t +already_in_visited_wlan_list(const struct wireless_lan *new_wlan) +{ + struct visited_wlans *vwlp; + + vwlp = visited_wlan_list->head; + while (vwlp != NULL && vwlp->wifi_net != NULL) { + if (strcmp(vwlp->wifi_net->essid, new_wlan->essid) == 0) { + dprintf("%s already in visited_wlan_list", + vwlp->wifi_net->essid); + return (B_TRUE); + } else { + vwlp = vwlp->next; + } + } + return (B_FALSE); +} + +static char * +get_zenity_response(const char *cmd) +{ + char buf[1024]; + size_t buf_len; + char *rtnp; + FILE *cptr; + int ret; + + if (!valid_graphical_user(B_TRUE)) + return (NULL); + + cptr = popen(cmd, "r"); + if (cptr == NULL) { + syslog(LOG_ERR, "Could not run %s: %m", ZENITY); + return (NULL); + } + if (fgets(buf, sizeof (buf), cptr) != NULL) { + buf_len = strlen(buf); + if (buf_len > 0 && buf[buf_len - 1] == '\n') + buf[buf_len - 1] = '\0'; + dprintf("get_zenity_resp: zenity returned '%s'", buf); + } else { + buf[0] = '\0'; + dprintf("get_zenity_resp: zenity returned nothing"); + } + ret = pclose(cptr); + /* + * We should probably make sure that those ZENITY_* exit + * environment variables are not set first... + */ + if (ret == -1 || !WIFEXITED(ret) || (WEXITSTATUS(ret) == 255)) { + dprintf("get_zenity_resp: %s did not exit normally", ZENITY); + return (NULL); + } + if (WEXITSTATUS(ret) == 1) { + dprintf("get_zenity_resp: user cancelled"); + return (NULL); + } + if ((rtnp = strdup(buf)) == NULL) + syslog(LOG_ERR, "get_zenity_response: strdup failed"); + + return (rtnp); +} + +/* + * get_user_preference(): Present a list of essid/bssid pairs to the + * user via zenity (passed in in a pre-formatted zenity string in the + * param cmd). If there's a final list item ("Other", "Select from + * full list", etc.) that may be selected and should be differentiated, + * that item should be passed in in compare param. + * + * Four possible return values: + * -1: No response from user, or other error. *req_lan is undefined. + * 0: essid/bssid pair was selected. *req_lan has these values in + * a malloc'd buffer, which the caller is responsible for freeing. + * 1: a compare string ("Other") was given, and the user response matched + * that string. *req_lan is undefined. + * 2: a compare string ("Rescan") was given, and the user response matched + * that string. *req_lan is undefined. + */ +int +get_user_preference(const char *cmd, const char *compare_other, + const char *compare_rescan, struct wireless_lan **req_lan, + const struct wireless_lan *list) +{ + char *response; + struct wireless_lan *wlp; + const struct wireless_lan *sel; + int answer; + + assert(req_lan != NULL); + wlp = calloc(1, sizeof (struct wireless_lan)); + if (wlp == NULL) { + syslog(LOG_ERR, "malloc failed"); + return (-1); + } + + response = get_zenity_response(cmd); + if (response == NULL) { + free(wlp); + return (-1); + } + if (strcmp(response, compare_other) == 0) { + free(response); + free(wlp); + return (1); + } + if (strcmp(response, compare_rescan) == 0) { + free(response); + free(wlp); + return (2); + } + answer = atoi(response); + if (answer <= 0) { + dprintf("%s returned invalid string", ZENITY); + free(response); + free(wlp); + return (-1); + } + sel = &list[answer - 1]; + + if ((wlp->essid = strdup(sel->essid)) == NULL) + goto dup_error; + + if ((sel->bssid != NULL) && ((wlp->bssid = strdup(sel->bssid)) == NULL)) + goto dup_error; + + wlp->need_wepkey = sel->need_wepkey; + + if ((sel->raw_wepkey != NULL) && + ((wlp->raw_wepkey = strdup(sel->raw_wepkey)) == NULL)) + goto dup_error; + + if (sel->cooked_wepkey != NULL) { + wlp->cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t)); + if (wlp->cooked_wepkey == NULL) + goto dup_error; + *(wlp->cooked_wepkey) = *(sel->cooked_wepkey); + } + + if ((sel->signal_strength != NULL) && + ((wlp->signal_strength = strdup(sel->signal_strength)) == NULL)) + goto dup_error; + + if ((sel->wl_if_name != NULL) && + ((wlp->wl_if_name = strdup(sel->wl_if_name)) == NULL)) + goto dup_error; + + dprintf("selected: %s, %s, %s, '%s', %s", wlp->essid, + STRING(wlp->bssid), wlp->need_wepkey ? "WEP" : "none", + STRING(wlp->signal_strength), STRING(wlp->wl_if_name)); + + free(response); + + *req_lan = wlp; + return (0); + +dup_error: + syslog(LOG_ERR, "get_user_preference: strdup failed"); + free_wireless_lan(wlp); + free(wlp); + free(response); + return (-1); +} + +/* + * Returns a pointer to an alloc'd struct wireless lan if a response + * is received from the user; only the essid will be valid in this + * case. If no response received, or other failure, returns NULL. + */ +static struct wireless_lan * +get_specific_lan(void) +{ + char specify_str[1024]; + char *response; + struct wireless_lan *wlp; + + /* + * TRANSLATION_NOTE: the token "ESSID" should not be translated + * in the phrase below. + */ + (void) snprintf(specify_str, sizeof (specify_str), ZENITY + " --entry --title=\"%s\" --text=\"%s\"", + gettext("Specify WiFi network"), gettext("Enter ESSID")); + + response = get_zenity_response(specify_str); + if (response == NULL) + return (NULL); + + wlp = calloc(1, sizeof (struct wireless_lan)); + if (wlp == NULL) { + syslog(LOG_ERR, "malloc failed: %m"); + free(response); + return (NULL); + } + wlp->essid = response; + + (void) snprintf(specify_str, sizeof (specify_str), ZENITY + " --list --title=\"%s\" --text=\"%s\" --column=\"%s\" none wep", + gettext("Security"), gettext("Enter security"), + gettext("Type")); + + response = get_zenity_response(specify_str); + if (response != NULL && strcmp(response, "wep") == 0) + wlp->need_wepkey = B_TRUE; + + free(response); + return (wlp); +} + +/* + * Returns: + * B_TRUE if things go well + * B_FALSE if we were unable to connect to anything + */ +boolean_t +handle_wireless_lan(const char *ifname) +{ + const struct wireless_lan *cur_wlans; + int i, num_wlans; + struct wireless_lan *req_conf = NULL; + boolean_t result; + dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK; + dladm_wlan_strength_t strength; + return_vals_t connect_result; + +start_over: + if (visited_wlan_list == NULL) { + if ((visited_wlan_list = calloc(1, + sizeof (struct visited_wlans_list))) == NULL) { + syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); + return (B_FALSE); + } + } + + /* + * We wait while a scan is in progress. Since we allow a user + * to initiate a re-scan, we can proceed even when no scan + * has been done to fill in the AP list. + */ + (void) pthread_mutex_lock(&wifi_init_mutex); + while (wifi_scan_intf != NULL) + (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex); + (void) pthread_mutex_unlock(&wifi_init_mutex); + + (void) pthread_mutex_lock(&wifi_mutex); + num_wlans = wireless_lan_used; + cur_wlans = wlans; + + /* + * Try to see if any of the wifi nets currently available + * has been used previously. If more than one available + * nets has been used before, then prompt user with + * all the applicable previously wifi nets, and ask which + * one to connect to. + */ + for (i = 0; i < num_wlans; i++) { + struct visited_wlans *new_wlan; + + /* Find the AP with the highest signal. */ + if (dladm_wlan_str2strength(cur_wlans[i].signal_strength, + &strength) != DLADM_STATUS_OK) { + continue; + } + if (strength > strongest) + strongest = strength; + + if (!known_wifi_nets_lookup(cur_wlans[i].essid, + cur_wlans[i].bssid)) + continue; + + if (already_in_visited_wlan_list(&cur_wlans[i])) { + /* don't have to add it again */ + continue; + } + + /* add this to the visited_wlan_list */ + dprintf("adding essid %s, bssid %s to visited list", + cur_wlans[i].essid, STRING(cur_wlans[i].bssid)); + + new_wlan = calloc(1, sizeof (struct visited_wlans)); + if (new_wlan == NULL) { + syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); + result = B_FALSE; + connect_result = FAILURE; + goto all_done; + } + new_wlan->wifi_net = calloc(1, sizeof (struct wireless_lan)); + if (new_wlan->wifi_net == NULL) { + free(new_wlan); + syslog(LOG_ERR, "handle_wireless_lan: calloc failed"); + result = B_FALSE; + connect_result = FAILURE; + goto all_done; + } + new_wlan->wifi_net->essid = strdup(cur_wlans[i].essid); + new_wlan->wifi_net->bssid = strdup(cur_wlans[i].bssid); + new_wlan->wifi_net->raw_wepkey = NULL; + new_wlan->wifi_net->cooked_wepkey = NULL; + new_wlan->wifi_net->need_wepkey = cur_wlans[i].need_wepkey; + new_wlan->wifi_net->signal_strength = + strdup(cur_wlans[i].signal_strength); + new_wlan->wifi_net->wl_if_name = + strdup(cur_wlans[i].wl_if_name); + if (new_wlan->wifi_net->essid == NULL || + new_wlan->wifi_net->bssid == NULL || + new_wlan->wifi_net->signal_strength == NULL || + new_wlan->wifi_net->wl_if_name == NULL) { + syslog(LOG_ERR, "handle_wireless_lan: strdup failed"); + free_wireless_lan(new_wlan->wifi_net); + free(new_wlan->wifi_net); + free(new_wlan); + result = B_FALSE; + connect_result = FAILURE; + goto all_done; + } + + new_wlan->next = visited_wlan_list->head; + visited_wlan_list->head = new_wlan; + visited_wlan_list->total++; + } + + if (visited_wlan_list->total == 1) { + struct wireless_lan *target = visited_wlan_list->head->wifi_net; + + /* + * only one previously visited wifi net, connect to it + * (falling back to autoconf if the connect fails) if there + * is no AP with a better signal strength. + */ + if (dladm_wlan_str2strength(target->signal_strength, + &strength) == DLADM_STATUS_OK) { + if (strength < strongest) + goto connect_any; + } + result = connect_or_autoconf(target, ifname); + connect_result = result ? SUCCESS : FAILURE; + + } else if (visited_wlan_list->total > 1) { + /* + * more than one previously visited wifi nets seen. + * prompt user for which one should we connect to + */ + if ((req_conf = prompt_for_visited()) != NULL) { + result = connect_or_autoconf(req_conf, ifname); + connect_result = result ? SUCCESS : FAILURE; + } else { + /* + * The user didn't make a choice; offer the full list. + */ + connect_result = connect_to_new_wlan(cur_wlans, + num_wlans, ifname); + result = (connect_result == SUCCESS); + } + } else { +connect_any: + /* last case, no previously visited wlan found */ + connect_result = connect_to_new_wlan(cur_wlans, num_wlans, + ifname); + result = (connect_result == SUCCESS); + } + +all_done: + /* + * We locked down the list above; free it now that we're done. + */ + (void) pthread_mutex_unlock(&wifi_mutex); + if (visited_wlan_list != NULL) { + struct visited_wlans *vwlp = visited_wlan_list->head, *next; + + for (; vwlp != NULL; vwlp = next) { + if (vwlp->wifi_net != NULL) { + free_wireless_lan(vwlp->wifi_net); + free(vwlp->wifi_net); + vwlp->wifi_net = NULL; + } + next = vwlp->next; + free(vwlp); + } + free(visited_wlan_list); + visited_wlan_list = NULL; + } + if (req_conf != NULL) { + free_wireless_lan(req_conf); + free(req_conf); + req_conf = NULL; + } + if (connect_result == TRY_AGAIN) { + dprintf("end of handle_wireless_lan() TRY_AGAIN"); + goto start_over; + } + return (result); +} diff --git a/usr/src/cmd/svc/milestone/Makefile b/usr/src/cmd/svc/milestone/Makefile index 0b421f1825..b084a04e44 100644 --- a/usr/src/cmd/svc/milestone/Makefile +++ b/usr/src/cmd/svc/milestone/Makefile @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" @@ -103,6 +103,7 @@ SVCMETHOD=\ manifest-import \ net-loopback \ net-init \ + net-nwam \ net-physical \ net-routing-setup \ net-svc \ diff --git a/usr/src/cmd/svc/milestone/net-loopback b/usr/src/cmd/svc/milestone/net-loopback index 3688a206f5..ea30da7326 100644 --- a/usr/src/cmd/svc/milestone/net-loopback +++ b/usr/src/cmd/svc/milestone/net-loopback @@ -53,31 +53,8 @@ SUNW_NO_MPATHD=; export SUNW_NO_MPATHD # in the boot process) and set forwarding flags. # -# ipV4 loopback +# IPv4 loopback /sbin/ifconfig lo0 plumb 127.0.0.1 up -# Configure the v6 loopback if any IPv6 interfaces are configured. -interface_names="`echo /etc/hostname6.*[0-9] 2>/dev/null`" -if [ "$interface_names" != "/etc/hostname6.*[0-9]" ]; then - ORIGIFS="$IFS" - IFS="$IFS." - set -- $interface_names - IFS="$ORIGIFS" - while [ $# -ge 2 ]; do - shift - if [ $# -gt 1 -a "$2" != "/etc/hostname6" ]; then - while [ $# -gt 1 -a "$1" != "/etc/hostname6" ]; do - shift - done - else - inet6_list="$inet6_list $1" - shift - fi - done -fi - -if [ -n "$inet6_list" ]; then - /sbin/ifconfig lo0 inet6 plumb ::1 up -else - exit $SMF_EXIT_OK -fi +# IPv6 loopback +/sbin/ifconfig lo0 inet6 plumb ::1 up diff --git a/usr/src/cmd/svc/milestone/net-nwam b/usr/src/cmd/svc/milestone/net-nwam new file mode 100644 index 0000000000..bbe5220b1b --- /dev/null +++ b/usr/src/cmd/svc/milestone/net-nwam @@ -0,0 +1,66 @@ +#!/sbin/sh +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +#ident "%Z%%M% %I% %E% SMI" + +. /lib/svc/share/smf_include.sh + +# +# In a shared-IP zone we need this service to be up, but all of the work +# it tries to do is irrelevant (and will actually lead to the service +# failing if we try to do it), so just bail out. +# In the global zone and exclusive-IP zones we proceed. +# +smf_configure_ip || exit $SMF_EXIT_OK + +case "$1" in +'refresh') + /usr/bin/pkill -HUP -z `smf_zonename` nwamd + ;; + +'start') + if smf_is_globalzone; then + # Initialize security objects. + /sbin/dladm init-secobj + fi + # start nwamd in foreground; it will daemonize itself + if /lib/inet/nwamd ; then + exit $SMF_EXIT_OK + else + exit $SMF_EXIT_ERR_FATAL + fi + ;; + +'stop') + /usr/bin/pkill -z `smf_zonename` nwamd + ;; + +*) + echo "Usage: $0 { start | stop | refresh }" + exit $SMF_EXIT_ERR_FATAL + ;; +esac +exit $SMF_EXIT_OK diff --git a/usr/src/cmd/svc/milestone/net-svc b/usr/src/cmd/svc/milestone/net-svc index a91d3d09fa..f987e91dc4 100644 --- a/usr/src/cmd/svc/milestone/net-svc +++ b/usr/src/cmd/svc/milestone/net-svc @@ -54,6 +54,7 @@ case "$1" in ;; esac +interface=$2 # If boot variables are not set, set variables we use [ -z "$_INIT_UTS_NODENAME" ] && _INIT_UTS_NODENAME=`/usr/bin/uname -n` @@ -134,72 +135,6 @@ update_nss () } # -# update_hosts_file -# This routine updates the /etc/inet/hosts file with the hostname and IP -# address that was obtained via DHCP. -# -update_hosts_file () -{ - filename=hosts - # Delete any old lines added by dhcp. - /usr/bin/sed -e '/# Added by DHCP$/d' /etc/inet/${filename} \ - > /tmp/${filename}_clear.$$ - - shift $# # Clear $0-9 first in case grep fails - set -- `/usr/bin/grep "^[ ]*$ipaddr[ ]" \ - /tmp/${filename}_clear.$$ 2>/dev/null` - - if [ $# -gt 0 ]; then - # - # IP address is already in the file. Ensure the - # associated hostname is the same as the Hostname - # property returned by the DHCP server. - # - /usr/bin/sed -e "/^[ ]*${ipaddr}[ ]/d" \ - /tmp/${filename}_clear.$$ >/tmp/${filename}.$$ - echo "${ipaddr}\t${hostname}\t# Added by DHCP" \ - >>/tmp/${filename}.$$ - else - # - # IP address is missing from the respective file. Now check - # to see if the hostname is present with a different IP. - # - shift $# # Clear $0-9 in case grep fails - set -- `/usr/bin/grep -s -v '^#' /tmp/${filename}_clear.$$ | \ - /usr/bin/egrep "[ ]${hostname}([ ]|$)"` - - if [ $# -gt 0 ]; then - # - # Hostname is present in the file. Rewrite this line - # to have the new IP address and the DHCP comment. - # - /usr/bin/sed -e "/^[ ]*${1}[ ]/d" \ - /tmp/${filename}_clear.$$ >/tmp/${filename}.$$ - - shift # Shift off $1 (the old IP) - - echo "$ipaddr $*\c" | /usr/bin/tr ' ' '\t' \ - >>/tmp/${filename}.$$ - - echo "\t# Added by DHCP" >>/tmp/${filename}.$$ - else - # - # Hostname is not present in the named file. - # Add a new line for the host at the end of - # the new respective file. - # - /usr/bin/mv /tmp/${filename}_clear.$$ \ - /tmp/${filename}.$$ - echo "${ipaddr}\t${hostname}\t# Added by DHCP" \ - >>/tmp/${filename}.$$ - fi - fi - - /usr/bin/rm -f /tmp/${filename}_clear.$$ - mv_file /tmp/${filename}.$$ /etc/inet/${filename} 444 -} - -# # We now need to reset the netmask and broadcast address for our network # interfaces. Since this may result in a name service lookup, we want to # now wait for NIS to come up if we previously started it. @@ -224,7 +159,11 @@ domain=`/usr/bin/domainname 2>/dev/null` smf_netstrategy if [ "$_INIT_NET_STRATEGY" = "dhcp" ]; then - dnsservers=`/sbin/dhcpinfo DNSserv` + if [ -n "$interface" ]; then + dnsservers=`/sbin/dhcpinfo -i $interface DNSserv` + else + dnsservers=`/sbin/dhcpinfo DNSserv` + fi else dnsservers="" fi @@ -242,7 +181,11 @@ if [ -n "$dnsservers" ]; then if [ ! -f /etc/resolv.conf ]; then /usr/bin/touch /etc/resolv.conf fi - dnsdomain=`/sbin/dhcpinfo DNSdmain` + if [ -n "$interface" ]; then + dnsdomain=`/sbin/dhcpinfo -i $interface DNSdmain` + else + dnsdomain=`/sbin/dhcpinfo DNSdmain` + fi export dnsservers dnsdomain /usr/bin/nawk </etc/resolv.conf >/tmp/resolv.conf.$$ ' function writedomain() { @@ -283,22 +226,19 @@ elif /usr/bin/grep '# Added by DHCP$' /etc/nsswitch.conf >/dev/null 2>&1; then mv_file /tmp/nsswitch.conf.$$ /etc/nsswitch.conf 644 fi -if [ "$_INIT_NET_STRATEGY" = "dhcp" ]; then - - hostname=`/usr/bin/uname -n` - ipaddr=`/sbin/dhcpinfo Yiaddr` - update_hosts_file - -else - # We're not using a dhcp strategy, so host entries added by - # DHCP should be removed from /etc/inet/hosts. +# Clean up any old DHCP-added entries (except loopback) in the hosts file. +if /usr/bin/grep '# Added by DHCP$' /etc/inet/hosts >/dev/null 2>&1; then + /usr/bin/nawk '{ + if (index($0, "# Added by DHCP") == 0 || + $1 == "127.0.0.1" || $1 == "::1") { + print $0 + } + }' /etc/inet/hosts > /tmp/hosts.$$ + mv_file /tmp/hosts.$$ /etc/inet/hosts 444 +fi - if /usr/bin/grep '# Added by DHCP$' /etc/inet/hosts >/dev/null 2>&1; - then - /usr/bin/sed -e '/# Added by DHCP$/d' \ - /etc/inet/hosts > /tmp/hosts.$$ - mv_file /tmp/hosts.$$ /etc/inet/hosts 444 - fi +if [ -z "$SMF_FMRI" ] || [ "$SMF_FMRI" = "svc:/network/physical:nwam" ]; then + exit 0 fi # diff --git a/usr/src/cmd/svc/milestone/network-physical.xml b/usr/src/cmd/svc/milestone/network-physical.xml index c491b04420..0405ddedfc 100644 --- a/usr/src/cmd/svc/milestone/network-physical.xml +++ b/usr/src/cmd/svc/milestone/network-physical.xml @@ -1,7 +1,7 @@ <?xml version="1.0"?> <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1"> <!-- - Copyright 2006 Sun Microsystems, Inc. All rights reserved. + Copyright 2007 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. CDDL HEADER START @@ -65,21 +65,77 @@ <propval name='duration' type='astring' value='transient' /> </property_group> + <template> + <common_name> + <loctext xml:lang='C'> + physical network interfaces + </loctext> + </common_name> + <documentation> + <manpage title='ifconfig' section='1M' + manpath='/usr/share/man' /> + </documentation> + </template> + </instance> - <stability value='Unstable' /> + <instance name='nwam' enabled='false'> + + <exec_method + type='method' + name='start' + exec='/lib/svc/method/net-nwam start' + timeout_seconds='600' /> + + <exec_method + type='method' + name='stop' + exec='/lib/svc/method/net-nwam stop' + timeout_seconds='60' /> + + <exec_method + type='method' + name='refresh' + exec='/lib/svc/method/net-nwam refresh' + timeout_seconds='60' /> + + <property_group name='general' type='framework'> + <!-- to start/stop NWAM services --> + <propval name='action_authorization' type='astring' + value='solaris.smf.manage.nwam' /> + <propval name='value_authorization' type='astring' + value='solaris.smf.manage.nwam' /> + </property_group> + + <property_group name='nwamd' type='application'> + <stability value='Unstable' /> + <propval name='debug' type='boolean' value='false' /> + <propval name='use_net_svc' type='boolean' value='true' /> + <propval name='popup_info' type='boolean' value='true' /> + <propval name='popup_query' type='boolean' value='true' /> + <propval name='autoconf' type='boolean' value='true' /> + <propval name='dhcp_wait_time' type='count' value='60' /> + <propval name='scan_interval' type='count' value='120' /> + <propval name='value_authorization' type='astring' + value='solaris.smf.value.nwam' /> + </property_group> <template> <common_name> <loctext xml:lang='C'> - physical network interfaces + physical network interface autoconfiguration </loctext> </common_name> <documentation> - <manpage title='ifconfig' section='1M' + <manpage title='nwamd' section='1M' manpath='/usr/share/man' /> </documentation> </template> + + </instance> + + <stability value='Unstable' /> + </service> </service_bundle> diff --git a/usr/src/lib/libsecdb/auth_attr.txt b/usr/src/lib/libsecdb/auth_attr.txt index fd6d0219ce..46521fbc2d 100644 --- a/usr/src/lib/libsecdb/auth_attr.txt +++ b/usr/src/lib/libsecdb/auth_attr.txt @@ -18,7 +18,7 @@ # # CDDL HEADER END # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # /etc/security/auth_attr @@ -90,6 +90,7 @@ solaris.smf.manage.bind:::Manage DNS Service States::help=BindStates.html solaris.smf.manage.cron:::Manage Cron Service States::help=SmfCronStates.html solaris.smf.manage.hal:::Manage HAL Service States::help=SmfHALStates.html solaris.smf.manage.name-service-cache:::Manage Name Service Cache Daemon Service States::help=SmfNscdStates.html +solaris.smf.manage.nwam:::Manage Network Auto-Magic Service States::help=SmfNWAMStates.html solaris.smf.manage.power:::Manage Power Management Service States::help=SmfPowerStates.html solaris.smf.manage.routing:::Manage Routing Service States::help=SmfRoutingStates.html solaris.smf.manage.rmvolmgr:::Manage Rmvolmgr Service States::help=SmfRmvolmgrStates.html @@ -98,6 +99,7 @@ solaris.smf.manage.sendmail:::Manage Sendmail Service States::help=SmfSendmailSt solaris.smf.manage.ssh:::Manage Secure Shell Service States::help=SmfSshStates.html solaris.smf.manage.system-log:::Manage Syslog Service States::help=SmfSyslogStates.html solaris.smf.value.:::Change Values of SMF Service Properties::help=SmfValueHeader.html +solaris.smf.value.nwam:::Change Values of SMF Network Auto-Magic Properties::help=SmfValueNWAM.html solaris.smf.value.routing:::Change Values of SMF Routing Properties::help=SmfValueRouting.html # solaris.system.:::Machine Administration::help=SysHeader.html diff --git a/usr/src/lib/libsecdb/help/auths/Makefile b/usr/src/lib/libsecdb/help/auths/Makefile index 3f063bc0d3..a6b50e871d 100644 --- a/usr/src/lib/libsecdb/help/auths/Makefile +++ b/usr/src/lib/libsecdb/help/auths/Makefile @@ -18,7 +18,7 @@ # # CDDL HEADER END # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" @@ -75,12 +75,14 @@ HTMLENTS = \ SmfModifyHeader.html \ SmfModifyMethod.html \ SmfNscdStates.html \ + SmfNWAMStates.html \ SmfPowerStates.html \ SmfRoutingStates.html \ SmfSendmailStates.html \ SmfSshStates.html \ SmfSyslogStates.html \ SmfValueHeader.html \ + SmfValueNWAM.html \ SmfValueRouting.html \ NetworkHeader.html \ WifiConfig.html \ diff --git a/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html b/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html new file mode 100644 index 0000000000..cbfc92d892 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfNWAMStates.html @@ -0,0 +1,40 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#ident "%Z%%M% %I% %E% SMI" +--> +<!-- + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Manage NWAM Service States is in the Authorizations Include +column, it grants the authorization to enable, disable, or restart +Network Auto-Magic (NWAM) services. +<p> +If Manage NWAM Service States is grayed, then you are not entitled +to Add or Remove this authorization. +<BR> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html b/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html new file mode 100644 index 0000000000..da69dc3353 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/SmfValueNWAM.html @@ -0,0 +1,40 @@ +<HTML> +<!-- + 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 2007 Sun Microsystems, Inc. All rights reserved. +Use is subject to license terms. +--> +<!-- SCCS keyword +#ident "%Z%%M% %I% %E% SMI" +--> +<!-- + <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1"> +--> +<BODY> +When Value NWAM Properties is in the Authorizations Include +column, it grants the the authorization to change Network Auto-Magic +(NWAM) service property values. +<P> +If Value NWAM Properties is grayed, then you are not entitled to +Add or Remove this authorization. +<BR> +</BODY> +</HTML> diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index 6901a2b673..be1c99f66f 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -20,7 +20,7 @@ # # -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" @@ -49,7 +49,7 @@ Mail Management:::Manage sendmail & queues:auths=solaris.smf.manage.sendmail;hel Maintenance and Repair:::Maintain and repair a system:auths=solaris.smf.manage.system-log;help=RtMaintAndRepair.html Media Backup:::Backup files and file systems:help=RtMediaBkup.html Media Restore:::Restore files and file systems from backups:help=RtMediaRestore.html -Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing;profiles=Network Wifi Management;help=RtNetMngmnt.html +Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing,solaris.smf.value.nwam,solaris.smf.manage.nwam;profiles=Network Wifi Management;help=RtNetMngmnt.html Network Security:::Manage network and host security:auths=solaris.smf.manage.ssh;profiles=Network Wifi Security,Network Link Security;help=RtNetSecure.html Network Wifi Management:::Manage wifi network configuration:auths=solaris.network.wifi.config;help=RtNetWifiMngmnt.html Network Wifi Security:::Manage wifi network security:auths=solaris.network.wifi.wep;help=RtNetWifiSecure.html diff --git a/usr/src/lib/libsocket/inet/link_addr.c b/usr/src/lib/libsocket/inet/link_addr.c index 36840521ab..bdb62fbbc7 100644 --- a/usr/src/lib/libsocket/inet/link_addr.c +++ b/usr/src/lib/libsocket/inet/link_addr.c @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * 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. @@ -19,8 +18,9 @@ * * CDDL HEADER END */ + /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -106,7 +106,7 @@ _link_aton(const char *ascaddr, int *maclen) return (NULL); } - if ((netaddr = malloc(numcolons)) == NULL) { + if ((netaddr = malloc(numcolons + 1)) == NULL) { *maclen = 0; return (NULL); } diff --git a/usr/src/pkgdefs/SUNW0on/prototype_com b/usr/src/pkgdefs/SUNW0on/prototype_com index 247579340a..c4ef0c557c 100644 --- a/usr/src/pkgdefs/SUNW0on/prototype_com +++ b/usr/src/pkgdefs/SUNW0on/prototype_com @@ -234,12 +234,14 @@ f none usr/lib/help/auths/locale/SmfModifyFramework.html 444 root bin f none usr/lib/help/auths/locale/SmfModifyHeader.html 444 root bin f none usr/lib/help/auths/locale/SmfModifyMethod.html 444 root bin f none usr/lib/help/auths/locale/SmfNscdStates.html 444 root bin +f none usr/lib/help/auths/locale/SmfNWAMStates.html 444 root bin f none usr/lib/help/auths/locale/SmfPowerStates.html 444 root bin f none usr/lib/help/auths/locale/SmfRoutingStates.html 444 root bin f none usr/lib/help/auths/locale/SmfSendmailStates.html 444 root bin f none usr/lib/help/auths/locale/SmfSshStates.html 444 root bin f none usr/lib/help/auths/locale/SmfSyslogStates.html 444 root bin f none usr/lib/help/auths/locale/SmfValueHeader.html 444 root bin +f none usr/lib/help/auths/locale/SmfValueNWAM.html 444 root bin f none usr/lib/help/auths/locale/SmfValueRouting.html 444 root bin f none usr/lib/help/auths/locale/NetworkHeader.html 444 root bin f none usr/lib/help/auths/locale/WifiConfig.html 444 root bin diff --git a/usr/src/pkgdefs/SUNWcsr/prototype_com b/usr/src/pkgdefs/SUNWcsr/prototype_com index 4a56993853..f8eefab542 100644 --- a/usr/src/pkgdefs/SUNWcsr/prototype_com +++ b/usr/src/pkgdefs/SUNWcsr/prototype_com @@ -327,6 +327,8 @@ s none etc/whodo=../usr/sbin/whodo s none etc/wtmpx=../var/adm/wtmpx d none etc/rpcsec 755 root sys d none lib 755 root bin +d none lib/inet 755 root bin +f none lib/inet/nwamd 555 root bin d none lib/svc 0755 root bin d none lib/svc/bin 0755 root bin f none lib/svc/bin/lsvcrun 0555 root sys @@ -353,6 +355,7 @@ f none lib/svc/method/manifest-import 0555 root bin f none lib/svc/method/mpxio-upgrade 0555 root bin f none lib/svc/method/net-init 0555 root bin f none lib/svc/method/net-loopback 0555 root bin +f none lib/svc/method/net-nwam 0555 root bin f none lib/svc/method/net-physical 0555 root bin f none lib/svc/method/net-routing-setup 0555 root bin f none lib/svc/method/net-svc 0555 root bin diff --git a/usr/src/pkgdefs/SUNWcsu/prototype_com b/usr/src/pkgdefs/SUNWcsu/prototype_com index cbbc6a9d0b..f479da7d69 100644 --- a/usr/src/pkgdefs/SUNWcsu/prototype_com +++ b/usr/src/pkgdefs/SUNWcsu/prototype_com @@ -479,12 +479,14 @@ f none usr/lib/help/auths/locale/C/SmfModifyFramework.html 444 root bin f none usr/lib/help/auths/locale/C/SmfModifyHeader.html 444 root bin f none usr/lib/help/auths/locale/C/SmfModifyMethod.html 444 root bin f none usr/lib/help/auths/locale/C/SmfNscdStates.html 444 root bin +f none usr/lib/help/auths/locale/C/SmfNWAMStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfPowerStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfRoutingStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfSendmailStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfSshStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfSyslogStates.html 444 root bin f none usr/lib/help/auths/locale/C/SmfValueHeader.html 444 root bin +f none usr/lib/help/auths/locale/C/SmfValueNWAM.html 444 root bin f none usr/lib/help/auths/locale/C/SmfValueRouting.html 444 root bin f none usr/lib/help/auths/locale/C/SysDate.html 444 root bin f none usr/lib/help/auths/locale/C/SysHeader.html 444 root bin |