/* get-next-event loop * Copyright (C) 1997 Angelos D. Keromytis. * Copyright (C) 1998-2002 D. Hugh Redelmeier. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include #include #include #include #include #include #include #include #include #include #include #ifdef SOLARIS # include /* for Solaris 2.6: defines SIOCGIFCONF */ #endif #include #include #include #include #include #include #include #include #include #include /* missing from on old systems */ #include #include #include "constants.h" #include "defs.h" #include "state.h" #include "connections.h" #include "kernel.h" #include "log.h" #include "server.h" #include "timer.h" #include "packet.h" #include "demux.h" /* needs packet.h */ #include "rcv_whack.h" #include "keys.h" #include "adns.h" /* needs */ #include "dnskey.h" /* needs keys.h and adns.h */ #include "whack.h" /* for RC_LOG_SERIOUS */ #include "pluto.h" #include #include #include "kameipsec.h" #include "nat_traversal.h" /* * Server main loop and socket initialization routines. */ static const int on = TRUE; /* by-reference parameter; constant, we hope */ /* control (whack) socket */ int ctl_fd = NULL_FD; /* file descriptor of control (whack) socket */ struct sockaddr_un ctl_addr = { AF_UNIX, DEFAULT_CTLBASE CTL_SUFFIX }; /* info (showpolicy) socket */ int policy_fd = NULL_FD; struct sockaddr_un info_addr= { AF_UNIX, DEFAULT_CTLBASE INFO_SUFFIX }; /* Initialize the control socket. * Note: this is called very early, so little infrastructure is available. * It is important that the socket is created before the original * Pluto process returns. */ err_t init_ctl_socket(void) { err_t failed = NULL; delete_ctl_socket(); /* preventative medicine */ ctl_fd = socket(AF_UNIX, SOCK_STREAM, 0); if (ctl_fd == -1) failed = "create"; else if (fcntl(ctl_fd, F_SETFD, FD_CLOEXEC) == -1) failed = "fcntl FD+CLOEXEC"; else if (setsockopt(ctl_fd, SOL_SOCKET, SO_REUSEADDR, (const void *)&on, sizeof(on)) < 0) failed = "setsockopt"; else { /* to keep control socket secure, use umask */ mode_t ou = umask(~S_IRWXU); if (bind(ctl_fd, (struct sockaddr *)&ctl_addr , offsetof(struct sockaddr_un, sun_path) + strlen(ctl_addr.sun_path)) < 0) failed = "bind"; umask(ou); } /* 5 is a haphazardly chosen limit for the backlog. * Rumour has it that this is the max on BSD systems. */ if (failed == NULL && listen(ctl_fd, 5) < 0) failed = "listen() on"; return failed == NULL? NULL : builddiag("could not %s control socket: %d %s" , failed, errno, strerror(errno)); } void delete_ctl_socket(void) { /* Is noting failure useful? Not when used as preventative medicine. */ unlink(ctl_addr.sun_path); } bool listening = FALSE; /* should we pay attention to IKE messages? */ struct iface *interfaces = NULL; /* public interfaces */ /* Initialize the interface sockets. */ static void mark_ifaces_dead(void) { struct iface *p; for (p = interfaces; p != NULL; p = p->next) p->change = IFN_DELETE; } static void free_dead_ifaces(void) { struct iface *p; bool some_dead = FALSE , some_new = FALSE; for (p = interfaces; p != NULL; p = p->next) { if (p->change == IFN_DELETE) { plog("shutting down interface %s/%s %s" , p->vname, p->rname, ip_str(&p->addr)); some_dead = TRUE; } else if (p->change == IFN_ADD) { some_new = TRUE; } } if (some_dead) { struct iface **pp; release_dead_interfaces(); for (pp = &interfaces; (p = *pp) != NULL; ) { if (p->change == IFN_DELETE) { *pp = p->next; /* advance *pp */ free(p->vname); free(p->rname); close(p->fd); free(p); } else { pp = &p->next; /* advance pp */ } } } /* this must be done after the release_dead_interfaces * in case some to the newly unoriented connections can * become oriented here. */ if (some_dead || some_new) check_orientations(); } void free_ifaces(void) { mark_ifaces_dead(); free_dead_ifaces(); } struct raw_iface { ip_address addr; char name[IFNAMSIZ + 20]; /* what would be a safe size? */ struct raw_iface *next; }; /* Called to handle --interface * Semantics: if specified, only these (real) interfaces are considered. */ static const char *pluto_ifn[10]; static int pluto_ifn_roof = 0; bool use_interface(const char *rifn) { if (pluto_ifn_roof >= (int)countof(pluto_ifn)) { return FALSE; } else { pluto_ifn[pluto_ifn_roof++] = rifn; return TRUE; } } #ifndef IPSECDEVPREFIX # define IPSECDEVPREFIX "ipsec" #endif static struct raw_iface * find_raw_ifaces4(void) { int j; /* index into buf */ struct ifconf ifconf; struct ifreq buf[300]; /* for list of interfaces -- arbitrary limit */ struct raw_iface *rifaces = NULL; int master_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); /* Get a UDP socket */ /* get list of interfaces with assigned IPv4 addresses from system */ if (master_sock == -1) exit_log_errno((e, "socket() failed in find_raw_ifaces4()")); if (setsockopt(master_sock, SOL_SOCKET, SO_REUSEADDR , (const void *)&on, sizeof(on)) < 0) exit_log_errno((e, "setsockopt() in find_raw_ifaces4()")); /* bind the socket */ { ip_address any; happy(anyaddr(AF_INET, &any)); setportof(htons(pluto_port), &any); if (bind(master_sock, sockaddrof(&any), sockaddrlenof(&any)) < 0) exit_log_errno((e, "bind() failed in find_raw_ifaces4()")); } /* Get local interfaces. See netdevice(7). */ ifconf.ifc_len = sizeof(buf); ifconf.ifc_buf = (void *) buf; zero(buf); if (ioctl(master_sock, SIOCGIFCONF, &ifconf) == -1) exit_log_errno((e, "ioctl(SIOCGIFCONF) in find_raw_ifaces4()")); /* Add an entry to rifaces for each interesting interface. */ for (j = 0; (j+1) * sizeof(*buf) <= (size_t)ifconf.ifc_len; j++) { struct raw_iface ri; const struct sockaddr_in *rs = (struct sockaddr_in *) &buf[j].ifr_addr; struct ifreq auxinfo; /* ignore all but AF_INET interfaces */ if (rs->sin_family != AF_INET) continue; /* not interesting */ /* build a NUL-terminated copy of the rname field */ memcpy(ri.name, buf[j].ifr_name, IFNAMSIZ); ri.name[IFNAMSIZ] = '\0'; /* ignore if our interface names were specified, and this isn't one */ if (pluto_ifn_roof != 0) { int i; for (i = 0; i != pluto_ifn_roof; i++) if (streq(ri.name, pluto_ifn[i])) break; if (i == pluto_ifn_roof) continue; /* not found -- skip */ } /* Find out stuff about this interface. See netdevice(7). */ zero(&auxinfo); /* paranoia */ memcpy(auxinfo.ifr_name, buf[j].ifr_name, IFNAMSIZ); if (ioctl(master_sock, SIOCGIFFLAGS, &auxinfo) == -1) exit_log_errno((e , "ioctl(SIOCGIFFLAGS) for %s in find_raw_ifaces4()" , ri.name)); if (!(auxinfo.ifr_flags & IFF_UP)) continue; /* ignore an interface that isn't UP */ /* ignore unconfigured interfaces */ if (rs->sin_addr.s_addr == 0) continue; happy(initaddr((const void *)&rs->sin_addr, sizeof(struct in_addr) , AF_INET, &ri.addr)); DBG(DBG_CONTROL, DBG_log("found %s with address %s" , ri.name, ip_str(&ri.addr))); ri.next = rifaces; rifaces = clone_thing(ri); } close(master_sock); return rifaces; } static struct raw_iface * find_raw_ifaces6(void) { /* Get list of interfaces with IPv6 addresses from system from /proc/net/if_inet6). * * Documentation of format? * RTFS: linux-2.2.16/net/ipv6/addrconf.c:iface_proc_info() * linux-2.4.9-13/net/ipv6/addrconf.c:iface_proc_info() * * Sample from Gerhard's laptop: * 00000000000000000000000000000001 01 80 10 80 lo * 30490009000000000000000000010002 02 40 00 80 ipsec0 * 30490009000000000000000000010002 07 40 00 80 eth0 * fe80000000000000025004fffefd5484 02 0a 20 80 ipsec0 * fe80000000000000025004fffefd5484 07 0a 20 80 eth0 * * Each line contains: * - IPv6 address: 16 bytes, in hex, no punctuation * - ifindex: 1 byte, in hex * - prefix_len: 1 byte, in hex * - scope (e.g. global, link local): 1 byte, in hex * - flags: 1 byte, in hex * - device name: string, followed by '\n' */ struct raw_iface *rifaces = NULL; static const char proc_name[] = "/proc/net/if_inet6"; FILE *proc_sock = fopen(proc_name, "r"); if (proc_sock == NULL) { DBG(DBG_CONTROL, DBG_log("could not open %s", proc_name)); } else { for (;;) { struct raw_iface ri; unsigned short xb[8]; /* IPv6 address as 8 16-bit chunks */ char sb[8*5]; /* IPv6 address as string-with-colons */ unsigned int if_idx; /* proc field, not used */ unsigned int plen; /* proc field, not used */ unsigned int scope; /* proc field, used to exclude link-local */ unsigned int dad_status; /* proc field, not used */ /* ??? I hate and distrust scanf -- DHR */ int r = fscanf(proc_sock , "%4hx%4hx%4hx%4hx%4hx%4hx%4hx%4hx" " %02x %02x %02x %02x %20s\n" , xb+0, xb+1, xb+2, xb+3, xb+4, xb+5, xb+6, xb+7 , &if_idx, &plen, &scope, &dad_status, ri.name); /* ??? we should diagnose any problems */ if (r != 13) break; /* ignore addresses with link local scope. * From linux-2.4.9-13/include/net/ipv6.h: * IPV6_ADDR_LINKLOCAL 0x0020U * IPV6_ADDR_SCOPE_MASK 0x00f0U */ if ((scope & 0x00f0U) == 0x0020U) continue; snprintf(sb, sizeof(sb) , "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x" , xb[0], xb[1], xb[2], xb[3], xb[4], xb[5], xb[6], xb[7]); happy(ttoaddr(sb, 0, AF_INET6, &ri.addr)); if (!isunspecaddr(&ri.addr)) { DBG(DBG_CONTROL , DBG_log("found %s with address %s" , ri.name, sb)); ri.next = rifaces; rifaces = clone_thing(ri); } } fclose(proc_sock); } return rifaces; } #if 1 static int create_socket(struct raw_iface *ifp, const char *v_name, int port) { int fd = socket(addrtypeof(&ifp->addr), SOCK_DGRAM, IPPROTO_UDP); int fcntl_flags; if (fd < 0) { log_errno((e, "socket() in process_raw_ifaces()")); return -1; } #if 1 /* Set socket Nonblocking */ if ((fcntl_flags=fcntl(fd, F_GETFL)) >= 0) { if (!(fcntl_flags & O_NONBLOCK)) { fcntl_flags |= O_NONBLOCK; fcntl(fd, F_SETFL, fcntl_flags); } } #endif if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) { log_errno((e, "fcntl(,, FD_CLOEXEC) in process_raw_ifaces()")); close(fd); return -1; } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR , (const void *)&on, sizeof(on)) < 0) { log_errno((e, "setsockopt SO_REUSEADDR in process_raw_ifaces()")); close(fd); return -1; } /* To improve error reporting. See ip(7). */ #if defined(IP_RECVERR) && defined(MSG_ERRQUEUE) if (setsockopt(fd, SOL_IP, IP_RECVERR , (const void *)&on, sizeof(on)) < 0) { log_errno((e, "setsockopt IP_RECVERR in process_raw_ifaces()")); close(fd); return -1; } #endif /* With IPv6, there is no fragmentation after * it leaves our interface. PMTU discovery * is mandatory but doesn't work well with IKE (why?). * So we must set the IPV6_USE_MIN_MTU option. * See draft-ietf-ipngwg-rfc2292bis-01.txt 11.1 */ #ifdef IPV6_USE_MIN_MTU /* YUCK: not always defined */ if (addrtypeof(&ifp->addr) == AF_INET6 && setsockopt(fd, SOL_SOCKET, IPV6_USE_MIN_MTU , (const void *)&on, sizeof(on)) < 0) { log_errno((e, "setsockopt IPV6_USE_MIN_MTU in process_raw_ifaces()")); close(fd); return -1; } #endif #if defined(linux) && defined(KERNEL26_SUPPORT) { struct sadb_x_policy policy; int level, opt; policy.sadb_x_policy_len = sizeof(policy) / IPSEC_PFKEYv2_ALIGN; policy.sadb_x_policy_exttype = SADB_X_EXT_POLICY; policy.sadb_x_policy_type = IPSEC_POLICY_BYPASS; policy.sadb_x_policy_dir = IPSEC_DIR_INBOUND; policy.sadb_x_policy_reserved = 0; policy.sadb_x_policy_id = 0; policy.sadb_x_policy_reserved2 = 0; if (addrtypeof(&ifp->addr) == AF_INET6) { level = IPPROTO_IPV6; opt = IPV6_IPSEC_POLICY; } else { level = IPPROTO_IP; opt = IP_IPSEC_POLICY; } if (setsockopt(fd, level, opt , &policy, sizeof(policy)) < 0) { log_errno((e, "setsockopt IPSEC_POLICY in process_raw_ifaces()")); close(fd); return -1; } policy.sadb_x_policy_dir = IPSEC_DIR_OUTBOUND; if (setsockopt(fd, level, opt , &policy, sizeof(policy)) < 0) { log_errno((e, "setsockopt IPSEC_POLICY in process_raw_ifaces()")); close(fd); return -1; } } #endif setportof(htons(port), &ifp->addr); if (bind(fd, sockaddrof(&ifp->addr), sockaddrlenof(&ifp->addr)) < 0) { log_errno((e, "bind() for %s/%s %s:%u in process_raw_ifaces()" , ifp->name, v_name , ip_str(&ifp->addr), (unsigned) port)); close(fd); return -1; } setportof(htons(pluto_port), &ifp->addr); return fd; } #endif static void process_raw_ifaces(struct raw_iface *rifaces) { struct raw_iface *ifp; /* Find all virtual/real interface pairs. * For each real interface... */ for (ifp = rifaces; ifp != NULL; ifp = ifp->next) { struct raw_iface *v = NULL; /* matching ipsecX interface */ bool after = FALSE; /* has vfp passed ifp on the list? */ bool bad = FALSE; struct raw_iface *vfp; /* ignore if virtual (ipsec*) interface */ if (strneq(ifp->name, IPSECDEVPREFIX, sizeof(IPSECDEVPREFIX)-1)) { continue; } for (vfp = rifaces; vfp != NULL; vfp = vfp->next) { if (vfp == ifp) { after = TRUE; } else if (sameaddr(&ifp->addr, &vfp->addr)) { /* Different entries with matching IP addresses. * Many interesting cases. */ if (strneq(vfp->name, IPSECDEVPREFIX, sizeof(IPSECDEVPREFIX)-1)) { if (v != NULL && !streq(v->name, vfp->name)) { loglog(RC_LOG_SERIOUS , "ipsec interfaces %s and %s share same address %s" , v->name, vfp->name, ip_str(&ifp->addr)); bad = TRUE; } else { v = vfp; /* current winner */ } } else { /* ugh: a second real interface with the same IP address * "after" allows us to avoid double reporting. */ #if defined(linux) && defined(KERNEL26_SUPPORT) { if (after) { bad = TRUE; break; } continue; } #endif if (after) { loglog(RC_LOG_SERIOUS , "IP interfaces %s and %s share address %s!" , ifp->name, vfp->name, ip_str(&ifp->addr)); } bad = TRUE; } } } if (bad) continue; #if defined(linux) && defined(KERNEL26_SUPPORT) { v = ifp; goto add_entry; } #endif /* what if we didn't find a virtual interface? */ if (v == NULL) { DBG(DBG_CONTROL, DBG_log("IP interface %s %s has no matching ipsec* interface -- ignored" , ifp->name, ip_str(&ifp->addr))); continue; } /* We've got all we need; see if this is a new thing: * search old interfaces list. */ #if defined(linux) && defined(KERNEL26_SUPPORT) add_entry: #endif { struct iface **p = &interfaces; for (;;) { struct iface *q = *p; /* search is over if at end of list */ if (q == NULL) { /* matches nothing -- create a new entry */ int fd = create_socket(ifp, v->name, pluto_port); if (fd < 0) break; if (nat_traversal_support_non_ike && addrtypeof(&ifp->addr) == AF_INET) { nat_traversal_espinudp_socket(fd, ESPINUDP_WITH_NON_IKE); } q = malloc_thing(struct iface); zero(q); q->rname = clone_str(ifp->name); q->vname = clone_str(v->name); q->addr = ifp->addr; q->fd = fd; q->next = interfaces; q->change = IFN_ADD; interfaces = q; plog("adding interface %s/%s %s:%d" , q->vname, q->rname, ip_str(&q->addr), pluto_port); if (nat_traversal_support_port_floating && addrtypeof(&ifp->addr) == AF_INET) { fd = create_socket(ifp, v->name, NAT_T_IKE_FLOAT_PORT); if (fd < 0) break; nat_traversal_espinudp_socket(fd, ESPINUDP_WITH_NON_ESP); q = malloc_thing(struct iface); zero(q); q->rname = clone_str(ifp->name); q->vname = clone_str(v->name); q->addr = ifp->addr; setportof(htons(NAT_T_IKE_FLOAT_PORT), &q->addr); q->fd = fd; q->next = interfaces; q->change = IFN_ADD; q->ike_float = TRUE; interfaces = q; plog("adding interface %s/%s %s:%d", q->vname, q->rname, ip_str(&q->addr), NAT_T_IKE_FLOAT_PORT); } break; } /* search over if matching old entry found */ if (streq(q->rname, ifp->name) && streq(q->vname, v->name) && sameaddr(&q->addr, &ifp->addr)) { /* matches -- rejuvinate old entry */ q->change = IFN_KEEP; /* look for other interfaces to keep (due to NAT-T) */ for (q = q->next ; q ; q = q->next) { if (streq(q->rname, ifp->name) && streq(q->vname, v->name) && sameaddr(&q->addr, &ifp->addr)) { q->change = IFN_KEEP; } } break; } /* try again */ p = &q->next; } /* for (;;) */ } } /* delete the raw interfaces list */ while (rifaces != NULL) { struct raw_iface *t = rifaces; rifaces = t->next; free(t); } } void find_ifaces(void) { mark_ifaces_dead(); process_raw_ifaces(find_raw_ifaces4()); process_raw_ifaces(find_raw_ifaces6()); free_dead_ifaces(); /* ditch remaining old entries */ if (interfaces == NULL) loglog(RC_LOG_SERIOUS, "no public interfaces found"); } void show_ifaces_status(void) { struct iface *p; for (p = interfaces; p != NULL; p = p->next) whack_log(RC_COMMENT, "interface %s/%s %s:%d" , p->vname, p->rname, ip_str(&p->addr), ntohs(portof(&p->addr))); } void show_debug_status(void) { #ifdef DEBUG whack_log(RC_COMMENT, "debug options: %s" , bitnamesof(debug_bit_names, cur_debugging)); #endif } static volatile sig_atomic_t sighupflag = FALSE; static void huphandler(int sig UNUSED) { sighupflag = TRUE; } static volatile sig_atomic_t sigtermflag = FALSE; static void termhandler(int sig UNUSED) { sigtermflag = TRUE; } /* call_server listens for incoming ISAKMP packets and Whack messages, * and handles timer events. */ void call_server(void) { struct iface *ifp; /* catch SIGHUP and SIGTERM */ { int r; struct sigaction act; act.sa_handler = &huphandler; sigemptyset(&act.sa_mask); act.sa_flags = 0; /* no SA_ONESHOT, no SA_RESTART, no nothing */ r = sigaction(SIGHUP, &act, NULL); passert(r == 0); act.sa_handler = &termhandler; r = sigaction(SIGTERM, &act, NULL); passert(r == 0); } for (;;) { fd_set readfds; fd_set writefds; int ndes, events_fd; /* wait for next interesting thing */ for (;;) { long next_time = next_event(); /* time to any pending timer event */ int maxfd = ctl_fd; if (sigtermflag) exit_pluto(0); if (sighupflag) { /* Ignorant folks think poking any daemon with SIGHUP * is polite. We catch it and tell them otherwise. * There is one use: unsticking a hung recvfrom. * This sticking happens sometimes -- kernel bug? */ sighupflag = FALSE; plog("Pluto ignores SIGHUP -- perhaps you want \"whack --listen\""); } FD_ZERO(&readfds); FD_ZERO(&writefds); FD_SET(ctl_fd, &readfds); /* the only write file-descriptor of interest */ if (adns_qfd != NULL_FD && unsent_ADNS_queries) { if (maxfd < adns_qfd) maxfd = adns_qfd; FD_SET(adns_qfd, &writefds); } if (adns_afd != NULL_FD) { if (maxfd < adns_afd) maxfd = adns_afd; FD_SET(adns_afd, &readfds); } events_fd = pluto->events->get_event_fd(pluto->events); if (maxfd < events_fd) maxfd = events_fd; FD_SET(events_fd, &readfds); if (listening) { for (ifp = interfaces; ifp != NULL; ifp = ifp->next) { if (maxfd < ifp->fd) maxfd = ifp->fd; passert(!FD_ISSET(ifp->fd, &readfds)); FD_SET(ifp->fd, &readfds); } } if (next_time == -1) { /* select without timer */ ndes = select(maxfd + 1, &readfds, &writefds, NULL, NULL); } else if (next_time == 0) { /* timer without select: there is a timer event pending, * and it should fire now so don't bother to do the select. */ ndes = 0; /* signify timer expiration */ } else { /* select with timer */ struct timeval tm; tm.tv_sec = next_time; tm.tv_usec = 0; ndes = select(maxfd + 1, &readfds, &writefds, NULL, &tm); } if (ndes != -1) break; /* success */ if (errno != EINTR) exit_log_errno((e, "select() failed in call_server()")); /* retry if terminated by signal */ } /* figure out what is interesting */ if (ndes == 0) { /* timer event */ DBG(DBG_CONTROL, DBG_log(BLANK_FORMAT); DBG_log("*time to handle event")); handle_timer_event(); passert(GLOBALS_ARE_RESET()); } else { /* at least one file descriptor is ready */ if (adns_qfd != NULL_FD && FD_ISSET(adns_qfd, &writefds)) { passert(ndes > 0); send_unsent_ADNS_queries(); passert(GLOBALS_ARE_RESET()); ndes--; } if (adns_afd != NULL_FD && FD_ISSET(adns_afd, &readfds)) { passert(ndes > 0); DBG(DBG_CONTROL, DBG_log(BLANK_FORMAT); DBG_log("*received adns message")); handle_adns_answer(); passert(GLOBALS_ARE_RESET()); ndes--; } if (FD_ISSET(events_fd, &readfds)) { passert(ndes > 0); DBG(DBG_CONTROL, DBG_log(BLANK_FORMAT); DBG_log("*handling asynchronous events")); pluto->events->handle(pluto->events); passert(GLOBALS_ARE_RESET()); ndes--; } for (ifp = interfaces; ifp != NULL; ifp = ifp->next) { if (FD_ISSET(ifp->fd, &readfds)) { /* comm_handle will print DBG_CONTROL intro, * with more info than we have here. */ passert(ndes > 0); comm_handle(ifp); passert(GLOBALS_ARE_RESET()); ndes--; } } if (FD_ISSET(ctl_fd, &readfds)) { passert(ndes > 0); DBG(DBG_CONTROL, DBG_log(BLANK_FORMAT); DBG_log("*received whack message")); whack_handle(ctl_fd); passert(GLOBALS_ARE_RESET()); ndes--; } passert(ndes == 0); } } } /* * Local Variables: * c-basic-offset: 4 * End Variables: */