/* netcfg-common.c - Shared functions used to configure the network for the debian-installer. Copyright (C) 2000-2002 David Kimdon and others (see debian/copyright) 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. 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "netcfg.h" #if defined(WIRELESS) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include #define SYSCLASSNET "/sys/class/net/" #endif /* __linux__ */ #ifdef __FreeBSD_kernel__ #include #define LO_IF "lo0" #else #define LO_IF "lo" #endif /* network config */ char hostname[MAXHOSTNAMELEN + 1]; char domain[MAXHOSTNAMELEN + 1]; int have_domain = 0; /* File descriptors for ioctls and such */ int skfd = 0; #ifdef WIRELESS int wfd = 0; #endif /* Count the number of contiguous 1 bits in a 32-bit integer, starting from * the MSB. */ static unsigned int count_bits(uint32_t num) { int count = 0; while (num & 0x80000000) { count++; num <<= 1; } return count; } /* convert a netmask string (eg 255.255.255.0 or ffff:ff::) in +src+ into * the length (24) in +dst+. Return 0 if some sort of failure, or 1 on * success. */ int inet_ptom (int af, const char *src, unsigned int *dst) { union inX_addr addr; if (!empty_str(src)) { if (inet_pton (af, src, &addr) < 0) { *dst = 0; return 0; } } if (af == AF_INET) { *dst = count_bits(ntohl(addr.in4.s_addr)); return 1; } else if (af == AF_INET6) { int i, count; for (i = 0, *dst = 0; i < 4; i++) { count = count_bits(htonl(addr.in6.s6_addr32[i])); *dst += count; if (count != 32) break; /* Don't go any further if the mask has finished */ } return 1; } else { *dst = 0; return 0; } } /* convert a length (24) in +src+ into the string netmask (255.255.255.0) in * +dst+. The length of +dst+ is given in +len+, to ensure we don't * overrun the buffer +dst+. +dst+ should always be at least NETCFG_ADDRSTRLEN * bytes long. * * Returns the address of +dst+ on success, and NULL on failure. */ const char *inet_mtop (int af, unsigned int src, char *dst, socklen_t len) { if (af == AF_INET) { struct in_addr addr; inet_mton(af, src, &addr); return inet_ntop(af, &addr, dst, len); } else if (af == AF_INET6) { struct in6_addr *addr6; inet_mton(af, src, &addr6); return inet_ntop(af, &addr6, dst, len); } else { di_warning("Unknown address family passed to inet_mtop: %i", af); return NULL; } } /* convert a mask length (eg 24) in +src+ into the struct in_addr it corresponds * to. */ void inet_mton (int af, unsigned int src, void *dst) { in_addr_t mask = 0; struct in_addr *addr; struct in6_addr *addr6; if (af == AF_INET) { addr = (struct in_addr *)dst; for(; src; src--) mask |= 1 << (32 - src); addr->s_addr = htonl(mask); } else if (af == AF_INET6) { unsigned int byte = 0; addr6 = (struct in6_addr *)dst; /* Clear out the address */ memset(addr6->s6_addr, 0, 16); while (src > 7) { addr6->s6_addr[byte++] = 0xff; src -= 8; } for (; src; src--) addr6->s6_addr[byte] |= 1 << (8 - src); } } void open_sockets (void) { #ifdef WIRELESS wfd = iw_sockets_open(); #endif skfd = socket (AF_INET, SOCK_DGRAM, 0); } #ifdef __linux__ /* Returns non-zero if this interface has an enabled kill switch, otherwise * zero. */ int check_kill_switch(const char *if_name) { char *temp, *linkbuf; const char *killname; char killstate; size_t len; int linklen, killlen; int fd = -1; int ret = 0; /* longest string we need */ len = strlen(SYSCLASSNET) + strlen(if_name) + strlen("/device/rf_kill") + 1; temp = malloc(len); snprintf(temp, len, SYSCLASSNET "%s/driver", if_name); linkbuf = malloc(1024); /* probably OK ... I hate readlink() */ linklen = readlink(temp, linkbuf, 1024); if (linklen < 0) goto out; if (strncmp(linkbuf + linklen - 8, "/ipw2100", 8) == 0) killname = "rf_kill"; else if (strncmp(linkbuf + linklen - 8, "/ipw2200", 8) == 0) killname = "rf_kill"; else goto out; snprintf(temp, len, SYSCLASSNET "%s/device/%s", if_name, killname); di_info("Checking RF kill switch: %s", temp); fd = open(temp, O_RDONLY); if (fd == -1) goto out; killlen = read(fd, &killstate, 1); if (killlen < 0) { di_error("Failed to read RF kill state: %s", strerror(errno)); goto out; } else if (killlen == 0) { di_warning("RF kill state file empty"); goto out; } if (killstate == '2') { di_info("RF kill switch enabled"); ret = 1; } out: free(temp); free(linkbuf); if (fd != -1) close(fd); return ret; } #else /* !__linux__ */ int check_kill_switch(const char *if_name) { (void)if_name; return 0; } #endif /* __linux__ */ #if defined(WIRELESS) int is_raw_80211(const char *iface) { struct ifreq ifr; struct sockaddr sa; strncpy(ifr.ifr_name, iface, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1]='\0'; if (skfd && ioctl(skfd, SIOCGIFHWADDR, &ifr) < 0) { di_warning("Unable to retrieve interface type."); return 0; } sa = * (struct sockaddr *) &ifr.ifr_hwaddr; switch (sa.sa_family) { case ARPHRD_IEEE80211: case ARPHRD_IEEE80211_PRISM: case ARPHRD_IEEE80211_RADIOTAP: return 1; default: return 0; } } #endif #if defined(__s390__) // Layer 3 qeth on s390(x) cannot do arping to test gateway reachability. int is_layer3_qeth(const char *iface) { const int bufsize = 1024; int retval = 0; char* path; char* buf; size_t len; ssize_t slen; char* driver; int fd; // This is sufficient for both /driver and /layer2. len = strlen(SYSCLASSNET) + strlen(iface) + strlen("/device/driver") + 1; path = malloc(len); snprintf(path, len, SYSCLASSNET "%s/device/driver", iface); // lstat() on sysfs symlinks does not provide size information. buf = malloc(bufsize); slen = readlink(path, buf, bufsize - 1); if (slen < 0) { di_error("Symlink %s cannot be resolved: %s", path, strerror(errno)); goto out; } buf[slen] = '\0'; driver = strrchr(buf, '/') + 1; if (strcmp(driver, "qeth") != 0) { di_info("no qeth found: %s", driver); goto out; } snprintf(path, len, SYSCLASSNET "%s/device/layer2", iface); fd = open(path, O_RDONLY); if (fd == -1) { di_error("%s cannot be opened: %s", path, strerror(errno)); goto out; } slen = read(fd, buf, 1); if (slen == -1) { di_error("Read from %s failed: %s", path, strerror(errno)); close(fd); goto out; } if (buf[0] == '0') { // driver == 'qeth' && layer2 == 0 retval = 1; } close(fd); out: free(buf); free(path); return retval; } #else int is_layer3_qeth(const char *iface __attribute__((unused))) { return 0; } #endif int qsort_strcmp(const void *a, const void *b) { const char **ia = (const char **)a; const char **ib = (const char **)b; return strcmp(*ia, *ib); } #ifdef __GNU__ #include #include #include /* On Hurd, the IP stack (pfinet) does not know the list of network interfaces * before we configure them, so we cannot use getifaddrs(). Instead we try * possible names for network interfaces and check whether they exists by * attempting to open the kernel device. */ int get_all_ifs (int all __attribute__ ((unused)), char*** ptr) { static const char *const fmt[] = { "eth%d", "wl%d", NULL }; mach_port_t device_master, file_master; device_t device; int err; char **list; int num, i, j; char name[3 + 3 * sizeof (int) + 1]; char devname[5 + sizeof(name)]; err = get_privileged_ports (0, &device_master); if (err) return 0; num = 0; list = malloc(sizeof *list); for (i = 0; fmt[i]; i++) for (j = 0;; j++) { char *thename; sprintf (name, fmt[i], j); sprintf (devname, "/dev/%s", name); err = device_open (device_master, D_READ, name, &device); if (err == 0) thename = name; else { file_master = file_name_lookup (devname, O_READ | O_WRITE, 0); if (file_master == MACH_PORT_NULL) break; err = device_open (file_master, D_READ, name, &device); mach_port_deallocate (mach_task_self (), file_master); if (err != 0) break; thename = devname; } device_close (device); mach_port_deallocate (mach_task_self (), device); list = realloc (list, (num + 2) * sizeof *list); list[num++] = strdup(thename); } list[num] = NULL; mach_port_deallocate (mach_task_self (), device_master); *ptr = list; return num; } #else int get_all_ifs (int all, char*** ptr) { struct ifaddrs *ifap, *ifa; char ibuf[512]; char** list = NULL; size_t len = 0; ibuf[sizeof(ibuf)-1]='\0'; if (getifaddrs(&ifap) == -1) return 0; for (ifa = ifap; ifa; ifa = ifa->ifa_next) { strncpy(ibuf, ifa->ifa_name, sizeof(ibuf)-1); if (ifa->ifa_flags & IFF_LOOPBACK) /* ignore loopback devices */ continue; #if defined(__linux__) if (!strncmp(ibuf, "sit", 3)) /* ignore tunnel devices */ continue; #endif #if defined(__FreeBSD_kernel__) if (!strncmp(ibuf, "pfsync", 6)) /* ignore pfsync devices */ continue; if (!strncmp(ibuf, "pflog", 5)) /* ignore pflog devices */ continue; if (!strncmp(ibuf, "usbus", 5)) /* ignore usbus devices */ continue; #endif #if defined(WIRELESS) if (is_raw_80211(ibuf)) continue; #endif if (all || ifa->ifa_flags & IFF_UP) { int found = 0; size_t i; for (i = 0 ; i < len ; i++) { if (!strcmp(ibuf, list[i])) { found = 1; } } if (!found) { list = realloc(list, sizeof(char*) * (len + 2)); list[len] = strdup(ibuf); len++; } } } /* OK, now sort the list and terminate it if necessary */ if (list != NULL) { qsort(list, len, sizeof(char *), qsort_strcmp); list[len] = NULL; } freeifaddrs(ifap); *ptr = list; return len; } #endif #ifdef __linux__ short find_in_stab(const char *if_name) { FILE *dn = NULL; char buf[128]; size_t len = strlen(if_name); if (access(STAB, F_OK) == -1) return 0; if (!(dn = popen("grep -v '^Socket' " STAB " | cut -f5", "r"))) return 0; while (fgets (buf, 128, dn) != NULL) { if (!strncmp(buf, if_name, len)) { pclose(dn); return 1; } } pclose(dn); return 0; } #else /* !__linux__ */ /* Stub function for platforms not supporting /var/run/stab. */ short find_in_stab(const char *if_name) { (void)if_name; return 0; } #endif /* __linux__ */ char *find_in_devnames(const char* iface) { FILE* dn = NULL; char buf[512], *result = NULL; size_t len = strlen(iface); if (!(dn = fopen(DEVNAMES, "r"))) return NULL; while (fgets(buf, 512, dn) != NULL) { char *ptr = strchr(buf, ':'), *desc = ptr + 1; if (!ptr) { result = NULL; /* corrupt */ break; } else if (!strncmp(buf, iface, len)) { result = strdup(desc); break; } } fclose(dn); if (result) { len = strlen(result); if (result[len - 1] == '\n') result[len - 1] = '\0'; } return result; } char *get_ifdsc(struct debconfclient *client, const char *if_name) { char template[256], *ptr = NULL; if ((ptr = find_in_devnames(if_name)) != NULL) { debconf_metaget(client, "netcfg/internal-wireless", "description"); if (is_wireless_iface(if_name)) { size_t len = strlen(ptr) + strlen(client->value) + 4; ptr = realloc(ptr, len); di_snprintfcat(ptr, len, " (%s)", client->value); } return ptr; /* already strdup'd */ } if (strlen(if_name) < 100) { if (!is_wireless_iface(if_name)) { /* strip away the number from the interface (eth0 -> eth) */ char *ifp = strdup(if_name), *ptr = ifp; while ((*ptr < '0' || *ptr > '9') && *ptr != '\0') ptr++; *ptr = '\0'; sprintf(template, "netcfg/internal-%s", ifp); free(ifp); if (debconf_metaget(client, template, "description") == CMD_SUCCESS && client->value != NULL) { return strdup(client->value); } } else { strcpy(template, "netcfg/internal-wifi"); debconf_metaget(client, template, "description"); return strdup(client->value); } } debconf_metaget(client, "netcfg/internal-unknown-iface", "description"); if (client->value != NULL) return strdup(client->value); else return strdup("Unknown interface"); } int iface_is_hotpluggable(const char *if_name) { FILE* f = NULL; char buf[256]; size_t len = strlen(if_name); if (!(f = fopen(DEVHOTPLUG, "r"))) { di_info("No hotpluggable devices are present in the system."); return 0; } while (fgets(buf, 256, f) != NULL) { if (!strncmp(buf, if_name, len)) { di_info("Detected %s as a hotpluggable device", if_name); fclose(f); return 1; } } fclose(f); di_info("Hotpluggable devices available, but %s is not one of them", if_name); return 0; } FILE *file_open(char *path, const char *opentype) { FILE *fp; if ((fp = fopen(path, opentype))) return fp; else { fprintf(stderr, "%s\n", path); perror("fopen"); return NULL; } } static char *get_bootif(void) { #ifdef __linux__ #define PROC_CMDLINE "/proc/cmdline" FILE *cmdline_file; char *cmdline = NULL; size_t dummy; const char *s; char *bootif = NULL; /* Look for BOOTIF= entry in kernel command line. */ cmdline_file = file_open(PROC_CMDLINE, "r"); if (!cmdline_file) { di_error("Failed to open " PROC_CMDLINE ": %s", strerror(errno)); return NULL; } if (getline(&cmdline, &dummy, cmdline_file) < 0) { di_error("Failed to read line from " PROC_CMDLINE ": %s", strerror(errno)); fclose(cmdline_file); return NULL; } s = cmdline; while ((s = strstr(s, "BOOTIF=")) != NULL) { if (s == cmdline || s[-1] == ' ') { size_t bootif_len; char *subst; s += sizeof("BOOTIF=") - 1; bootif_len = strcspn(s, " \n"); if (bootif_len != (ETH_ALEN * 3 - 1) + 3) continue; bootif = strndup(s + 3, bootif_len - 3); /* skip hardware type */ for (subst = bootif; *subst; subst++) if (*subst == '-') *subst = ':'; break; } s++; } free(cmdline); fclose(cmdline_file); if (!bootif) di_info("Could not find valid BOOTIF= entry in " PROC_CMDLINE); return bootif; #undef PROC_CMDLINE #else /* !__linux__ */ return NULL; #endif /* __linux__ */ } static unsigned char *parse_bootif(const char *bootif, int quiet) { int i; const char *s; unsigned char *bootif_addr = malloc(ETH_ALEN); /* Parse supplied address. */ for (i = 0, s = bootif; i < ETH_ALEN && s; i++) { unsigned long bootif_byte; errno = 0; bootif_byte = strtol(s, (char **) &s, 16); if (errno || bootif_byte >= 256) { if (!quiet) di_error("couldn't parse link-layer address '%s'", bootif); free(bootif_addr); return NULL; } bootif_addr[i] = (unsigned char) bootif_byte; if (i < ETH_ALEN - 1 && *s++ != ':') { if (!quiet) di_error("couldn't parse link-layer address '%s'", bootif); free(bootif_addr); return NULL; } } return bootif_addr; } static char *find_bootif_iface(const char *bootif, const unsigned char *bootif_addr) { #ifdef __GNU__ /* TODO: Use device_get_status(NET_ADDRESS), see pfinet/ethernet.c */ (void)bootif; (void)bootif_addr; return NULL; #else struct ifaddrs *ifap, *ifa; char *ret = NULL; /* TODO: this won't work on the Hurd as getifaddrs doesn't return * unconfigured interfaces. See comment to get_all_ifs. */ if (getifaddrs(&ifap) < 0) { di_error("getifaddrs failed: %s", strerror(errno)); return NULL; } for (ifa = ifap; ifa; ifa = ifa->ifa_next) { #if defined(__FreeBSD_kernel__) struct sockaddr_dl *sdl; #else struct sockaddr_ll *sll; #endif if (ifa->ifa_flags & IFF_LOOPBACK) continue; #if defined(__linux__) if (!strncmp(ifa->ifa_name, "sit", 3)) /* ignore tunnel devices */ continue; #endif #if defined(WIRELESS) if (is_raw_80211(ifa->ifa_name)) continue; #endif #if defined(__FreeBSD_kernel__) if (ifa->ifa_addr->sa_family != AF_LINK) continue; sdl = (struct sockaddr_dl *) ifa->ifa_addr; if (!sdl) /* no link-layer address */ continue; if (sdl->sdl_alen != ETH_ALEN) /* not Ethernet */ continue; if (memcmp(bootif_addr, LLADDR(sdl), ETH_ALEN) != 0) continue; #else if (ifa->ifa_addr->sa_family != AF_PACKET) continue; sll = (struct sockaddr_ll *) ifa->ifa_addr; if (!sll) /* no link-layer address */ continue; if ((sll->sll_hatype != ARPHRD_ETHER && sll->sll_hatype != ARPHRD_IEEE802) || sll->sll_halen != ETH_ALEN) /* not Ethernet */ continue; if (memcmp(bootif_addr, sll->sll_addr, ETH_ALEN) != 0) continue; #endif di_info("Found interface %s with link-layer address %s", ifa->ifa_name, bootif); ret = strdup(ifa->ifa_name); break; } freeifaddrs(ifap); if (!ret) di_error("Could not find any interface with address %s", bootif); return ret; #endif } void netcfg_die(struct debconfclient *client) { debconf_progress_stop(client); debconf_capb(client); debconf_input(client, "high", "netcfg/error"); debconf_go(client); exit(1); } /** * @brief Ask which interface to configure * @param client - client * @param interface - set the +name+ field to the answer * @param numif - number of interfaces found. * @param defif - default interface from link detection. */ int netcfg_get_interface(struct debconfclient *client, char **interface, int *numif, const char *defif) { char *inter = NULL, **ifs; size_t len; int ret, i, asked; int num_interfaces = 0; unsigned char *bootif_addr; char *bootif_iface = NULL; char *ptr = NULL; char *ifdsc = NULL; char *old_selection = NULL; if (*interface) { free(*interface); *interface = NULL; } if (!(ptr = malloc(128))) goto error; len = 128; *ptr = '\0'; num_interfaces = get_all_ifs(1, &ifs); /* Remember old interface selection, in case it's preseeded. */ debconf_get(client, "netcfg/choose_interface"); old_selection = strdup(client->value); /* If netcfg/choose_interface is preseeded to a link-layer address in * the form aa:bb:cc:dd:ee:ff, or if BOOTIF is set and matches an * interface, override any provided default from link detection. */ bootif_addr = parse_bootif(old_selection, 1); if (bootif_addr) { bootif_iface = find_bootif_iface(old_selection, bootif_addr); if (bootif_iface) { free(old_selection); old_selection = strdup(bootif_iface); } free(bootif_addr); } else { char *bootif = get_bootif(); if (bootif) { bootif_addr = parse_bootif(bootif, 0); if (bootif_addr) { bootif_iface = find_bootif_iface(bootif, bootif_addr); free(bootif_addr); } free(bootif); } } if (bootif_iface) { /* Did we actually get back an interface we know about? */ for (i = 0; i < num_interfaces; i++) { if (strcmp(ifs[i], bootif_iface) == 0) { defif = ifs[i]; break; } } free (bootif_iface); } /* If no default was provided, use the first in the list of interfaces. */ if (! defif && num_interfaces > 0) { defif = ifs[0]; } for (i = 0; i < num_interfaces; i++) { size_t newchars; char *temp = NULL; inter = ifs[i]; interface_down(inter); ifdsc = get_ifdsc(client, inter); newchars = strlen(inter) + strlen(ifdsc) + 5; /* ": , " + NUL */ if (len < (strlen(ptr) + newchars)) { if (!(ptr = realloc(ptr, len + newchars + 128))) goto error; len += newchars + 128; } temp = malloc(newchars); snprintf(temp, newchars, "%s: %s", inter, ifdsc); if (num_interfaces > 1 && ((strcmp(defif, inter) == 0) || (strcmp(defif, temp) == 0))) debconf_set(client, "netcfg/choose_interface", temp); di_snprintfcat(ptr, len, "%s, ", temp); free(temp); free(ifdsc); } if (num_interfaces == 0) { debconf_input(client, "high", "netcfg/no_interfaces"); ret = debconf_go(client); free(ptr); free(old_selection); *numif = 0; return ret; } else if (num_interfaces == 1) { inter = ptr; *numif = 1; free(old_selection); } else if (num_interfaces > 1) { *numif = num_interfaces; /* remove the trailing ", ", which confuses cdebconf */ ptr[strlen(ptr) - 2] = '\0'; debconf_subst(client, "netcfg/choose_interface", "ifchoices", ptr); free(ptr); asked = (debconf_input(client, "critical", "netcfg/choose_interface") == CMD_SUCCESS); ret = debconf_go(client); /* If the question is not asked, honor preseeded interface name. * However, if it was preseeded to "auto", or there was no old value, * leave it set to defif. */ if (!asked && strlen(old_selection) && strcmp(old_selection, "auto") != 0) { debconf_set(client, "netcfg/choose_interface", old_selection); } free(old_selection); if (ret) return ret; debconf_get(client, "netcfg/choose_interface"); inter = client->value; if (!inter) netcfg_die(client); } /* grab just the interface name, not the description too */ *interface = inter; /* Note that the question may be preseeded to just the interface name, * with no colon after it. Allow for this case. */ ptr = strchr(inter, ':'); if (ptr != NULL) { *ptr = '\0'; } *interface = strdup(*interface); /* Free allocated memory */ while (ifs && *ifs) free(*ifs++); return 0; error: if (ptr) free(ptr); netcfg_die(client); return RETURN_TO_MAIN; /* unreachable */ } /* * Verify that the hostname conforms to RFC 1123 s2.1, * and RFC 1034 s3.5. * @return 1 on success, 0 on failure. */ short valid_hostname (const char *hname) { static const char *valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-"; size_t len; assert(hname != NULL); len = strlen(hname); if ((len < 1) || (len > MAXHOSTNAMELEN) || (strspn(hname, valid_chars) != len) || (hname[len - 1] == '-') || (hname[0] == '-')) { return 0; } else return 1; } /* * Verify that the domain name (or FQDN) conforms to RFC 1123 s2.1, and * RFC1034 s3.5. * @return 1 on success, 0 on failure. */ short valid_domain (const char *dname) { static const char *valid_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-."; size_t len; assert(dname != NULL); len = strlen(dname); if ((len < 1) || (len > MAXHOSTNAMELEN) || (strspn(dname, valid_chars) != len) || (dname[len - 1] == '-') || (dname[0] == '-') || (dname[len - 1] == '.') || (dname[0] == '.') || strstr(dname, "..")) { return 0; } else return 1; } /* * Write the hostname to the given string (must be capable of storing at * least MAXHOSTNAMELEN bytes. * * @return 0 on success, 30 on BACKUP being selected. */ int netcfg_get_hostname(struct debconfclient *client, char *template, char *hostname, short accept_domain) { char *s, buf[1024]; for(;;) { if (accept_domain) have_domain = 0; debconf_input(client, "high", template); if (debconf_go(client) == CMD_GOBACK) return GO_BACK; debconf_get(client, template); strncpy(hostname, client->value, MAXHOSTNAMELEN); if (!valid_domain(hostname)) { di_info("%s is an invalid domain", hostname); debconf_subst(client, "netcfg/invalid_hostname", "hostname", hostname); snprintf(buf, sizeof(buf), "%i", MAXHOSTNAMELEN); debconf_subst(client, "netcfg/invalid_hostname", "maxhostnamelen", buf); debconf_input(client, "high", "netcfg/invalid_hostname"); debconf_go(client); debconf_set(client, template, "debian"); *hostname = '\0'; continue; } if (accept_domain && (s = strchr(hostname, '.'))) { di_info("Detected we have an FQDN; splitting and setting domain"); if (s[1] == '\0') { /* "somehostname." <- . should be ignored */ *s = '\0'; } else { /* assume we have a valid domain name given */ strncpy(domain, s + 1, MAXHOSTNAMELEN); debconf_set(client, "netcfg/get_domain", domain); have_domain = 1; *s = '\0'; } } if (!valid_hostname(hostname)) { di_info("%s is an invalid hostname", hostname); debconf_subst(client, "netcfg/invalid_hostname", "hostname", hostname); snprintf(buf, sizeof(buf), "%i", MAXHOSTNAMELEN); debconf_subst(client, "netcfg/invalid_hostname", "maxhostnamelen", buf); debconf_input(client, "high", "netcfg/invalid_hostname"); debconf_go(client); debconf_set(client, template, "debian"); *hostname = '\0'; } else { break; } } return 0; } /* @brief Get the domainname. * @return 0 for success, with *domain = domain, GO_BACK for 'goback', */ int netcfg_get_domain(struct debconfclient *client, char domain[]) { int ret; if (have_domain == 1) { debconf_get(client, "netcfg/get_domain"); assert (!empty_str(client->value)); strncpy(domain, client->value, MAXHOSTNAMELEN); return 0; } debconf_input (client, "high", "netcfg/get_domain"); ret = debconf_go(client); if (ret) return ret; debconf_get (client, "netcfg/get_domain"); *domain = '\0'; if (!empty_str(client->value)) { const char *start = client->value; while (*start == '.') ++start; /* trim leading dots */ strncpy(domain, start, MAXHOSTNAMELEN); } return 0; } void netcfg_write_loopback (void) { struct netcfg_interface lo; netcfg_interface_init(&lo); lo.name = LO_IF; lo.loopback = 1; netcfg_write_interface(NULL); netcfg_write_interface(&lo); } /* * ipaddress.s_addr may be 0 * domain may be null * interface may be null * hostname may _not_ be null */ void netcfg_write_common(const char *ipaddress, const char *hostname, const char *domain) { FILE *fp; char *domain_nodot = NULL; if (empty_str(hostname)) return; if (domain) { char *end; /* strip trailing dots */ domain_nodot = strdup(domain); end = domain_nodot + strlen(domain_nodot) - 1; while (end >= domain_nodot && *end == '.') *end-- = '\0'; } /* Currently busybox, hostname is not available. */ if (sethostname (hostname, strlen(hostname) + 1) < 0) { /* ignore errors */ } if ((fp = file_open(HOSTNAME_FILE, "w"))) { fprintf(fp, "%s\n", hostname); fclose(fp); } if ((fp = file_open(HOSTS_FILE, "w"))) { fprintf(fp, "127.0.0.1\tlocalhost"); if (!empty_str(ipaddress)) { if (domain_nodot && !empty_str(domain_nodot)) fprintf(fp, "\n%s\t%s.%s\t%s\n", ipaddress, hostname, domain_nodot, hostname); else fprintf(fp, "\n%s\t%s\n", ipaddress, hostname); } else { #if defined(__linux__) || defined(__GNU__) if (domain_nodot && !empty_str(domain_nodot)) fprintf(fp, "\n127.0.1.1\t%s.%s\t%s\n", hostname, domain_nodot, hostname); else fprintf(fp, "\n127.0.1.1\t%s\n", hostname); #else fprintf(fp, "\t%s\n", hostname); #endif } fprintf(fp, "\n" IPV6_HOSTS); fclose(fp); } free(domain_nodot); } void deconfigure_network(struct netcfg_interface *iface) { /* deconfiguring network interfaces */ interface_down(LO_IF); if (iface) interface_down(iface->name); } void loop_setup(void) { static int afpacket_notloaded = 1; deconfigure_network(NULL); #if defined(__FreeBSD_kernel__) (void)afpacket_notloaded; /* GNU/kFreeBSD currently uses the ifconfig command */ di_exec_shell_log("ifconfig "LO_IF" up"); di_exec_shell_log("ifconfig "LO_IF" 127.0.0.1 netmask 255.0.0.0"); #else if (afpacket_notloaded) afpacket_notloaded = di_exec_shell("modprobe af_packet"); /* should become 0 */ di_exec_shell_log("ip link set "LO_IF" up"); di_exec_shell_log("ip -f inet addr flush dev "LO_IF); di_exec_shell_log("ip addr add 127.0.0.1/8 dev "LO_IF); #endif } /* Determines the IP address of the interface (either from the static * configuration, or by querying the interface directly if using some sort * of autoconfiguration), then uses that address to request rDNS lookup * using the currently configured nameservers. We return the name in * +hostname+ if one is found, and return 1, otherwise we leave the * +hostname+ alone and return 0. */ int get_hostname_from_dns (const struct netcfg_interface *interface, char *hostname, const size_t max_hostname_len) { int err = 1; if (!empty_str(interface->ipaddress)) { /* Static configuration assumed */ struct sockaddr_in sin; struct sockaddr_in6 sin6; di_debug("Getting default hostname from rDNS lookup of static-configured address %s", interface->ipaddress); if (interface->address_family == AF_INET) { sin.sin_family = AF_INET; sin.sin_port = 0; inet_pton(AF_INET, interface->ipaddress, &sin.sin_addr); err = getnameinfo((struct sockaddr *) &sin, sizeof(sin), hostname, max_hostname_len, NULL, 0, NI_NAMEREQD); } else if (interface->address_family == AF_INET6) { sin6.sin6_family = AF_INET6; sin6.sin6_port = 0; inet_pton(AF_INET6, interface->ipaddress, &sin6.sin6_addr); err = getnameinfo((struct sockaddr *) &sin6, sizeof(sin6), hostname, max_hostname_len, NULL, 0, NI_NAMEREQD); } else { di_warning("Unknown address family in interface passed to seed_hostname_from_dns(): %i", interface->address_family); return 0; } if (err) { di_debug("getnameinfo() returned %i (%s)", err, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); } if (err == 0) { /* We found a name! We found a name! */ di_debug("Hostname found: %s", hostname); } } else { /* Autoconfigured interface; we need to find the IP address ourselves */ struct ifaddrs *ifa_head, *ifa; char tmpbuf[NETCFG_ADDRSTRLEN]; if (getifaddrs(&ifa_head) == -1) { di_warning("getifaddrs() failed: %s", strerror(errno)); return 0; } for (ifa = ifa_head; ifa != NULL; ifa = ifa->ifa_next) { if (strcmp(ifa->ifa_name, interface->name) != 0) { /* This isn't the interface you're looking for */ continue; } if (!ifa->ifa_addr) { /* This isn't even a record with an address... bugger that */ continue; } if (ifa->ifa_addr->sa_family != AF_INET && ifa->ifa_addr->sa_family != AF_INET6) { /* Not an IPv4 or IPv6 address... don't know what to do with it */ continue; } di_debug("Getting default hostname from rDNS lookup of autoconfigured address %s", inet_ntop(ifa->ifa_addr->sa_family, (ifa->ifa_addr->sa_family == AF_INET) ? (void *)(&((struct sockaddr_in *)ifa->ifa_addr)->sin_addr) : (void *)(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr), tmpbuf, sizeof(tmpbuf) ) ); err = getnameinfo(ifa->ifa_addr, (ifa->ifa_addr->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), hostname, max_hostname_len, NULL, 0, NI_NAMEREQD); if (err) { di_debug("getnameinfo() returned %i (%s)", err, err == EAI_SYSTEM ? strerror(errno) : gai_strerror(err)); } if (err == 0) { /* We found a name! We found a name! */ di_debug("Hostname found: %s", hostname); break; } } } return !err; } void interface_up (const char *if_name) { struct ifreq ifr; strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1]='\0'; if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >= 0) { di_info("Activating interface %s", if_name); strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1); ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); ioctl(skfd, SIOCSIFFLAGS, &ifr); } else { di_info("Getting flags for interface %s failed, not activating interface.", if_name); } } void interface_down (const char *if_name) { struct ifreq ifr; strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1); ifr.ifr_name[IFNAMSIZ-1]='\0'; if (skfd && ioctl(skfd, SIOCGIFFLAGS, &ifr) >= 0) { di_info("Taking down interface %s", if_name); strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1); ifr.ifr_flags &= ~IFF_UP; ioctl(skfd, SIOCSIFFLAGS, &ifr); } else { di_info("Getting flags for interface %s failed, not taking down interface.", if_name); } } void parse_args (int argc, char ** argv) { if (argc == 2) { if (!strcmp(basename(argv[0]), "ptom")) { unsigned int ret; if (inet_ptom(AF_INET, argv[1], &ret) > 0) { printf("%d\n", ret); exit(EXIT_SUCCESS); } } if (!strcmp(argv[1], "write_loopback")) { netcfg_write_loopback(); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE); } } void reap_old_files (void) { static char* remove[] = { INTERFACES_FILE, HOSTS_FILE, HOSTNAME_FILE, NETWORKS_FILE, RESOLV_FILE, DHCLIENT_CONF, DOMAIN_FILE, 0 }; char **ptr = remove; while (*ptr) unlink(*ptr++); } /* Convert a space-separated list of nameservers in a single string (as might * be entered into, say, debconf), and store them in the interface. */ void netcfg_nameservers_to_array(const char *nameservers, struct netcfg_interface *interface) { char *save, *ptr, *ns; unsigned int i; union inX_addr addr; if (nameservers) { save = ptr = strdup(nameservers); for (i = 0; i < NETCFG_NAMESERVERS_MAX; i++) { int af; ns = strtok_r(ptr, " \n\t", &ptr); if (ns) { /* The double conversion here is to both validate that we've * been given an IP address, and to ensure that the address * is in it's canonical form and will fit in the size of the * array element provided. */ if (inet_pton (AF_INET, ns, &addr)) { /* v4! */ af = AF_INET; } else { /* v6? */ if (inet_pton (AF_INET6, ns, &addr)) { af = AF_INET6; } else { af = -1; fprintf(stderr, "Failed to parse %s as an IP address", ns); } } if (af != -1) { inet_ntop (af, &addr, interface->nameservers[i], NETCFG_ADDRSTRLEN); } else { /* Dud in this slot; empty it */ *(interface->nameservers[i]) = '\0'; } } else *(interface->nameservers[i]) = '\0'; } free(save); } else { /* Empty out all the nameserver strings */ for (i = 0; i < NETCFG_NAMESERVERS_MAX; i++) *(interface->nameservers[i]) = '\0'; } } int netcfg_get_nameservers (struct debconfclient *client, char **nameservers, char *default_nameservers) { char *ptr; int ret; debconf_get(client,"netcfg/get_nameservers"); if (*nameservers) ptr = *nameservers; else if (strlen(client->value)) ptr = client->value; else if (default_nameservers) ptr = default_nameservers; else ptr = ""; debconf_set(client, "netcfg/get_nameservers", ptr); debconf_input(client, "critical", "netcfg/get_nameservers"); ret = debconf_go(client); if (ret) return ret; debconf_get(client, "netcfg/get_nameservers"); ptr = client->value; if (*nameservers) free(*nameservers); *nameservers = NULL; if (ptr) *nameservers = strdup(ptr); return 0; } void netcfg_update_entropy (void) { #ifdef __linux__ di_exec_shell("ip addr show >/dev/random"); #endif } /* Attempt to find out whether we've got link on an interface. Don't try to * bring the interface up or down, we leave that to the caller. Use a * progress bar so the user knows what's going on. Return true if we got * link, and false otherwise. */ int netcfg_detect_link(struct debconfclient *client, const struct netcfg_interface *interface) { char arping[256]; int count, rv = 0; int link_waits = 12; /* default for netcfg/link_wait_timeout times 4 */ int gw_tries = NETCFG_GATEWAY_REACHABILITY_TRIES; const char *if_name = interface->name; const char *gateway = interface->gateway; if (!empty_str(gateway)) sprintf(arping, "arping -c 1 -w 1 -f -I %s %s", if_name, gateway); /* Ask for link detection timeout. */ int ok = 0; debconf_capb(client, ""); while (!ok) { debconf_input(client, "low", "netcfg/link_wait_timeout"); debconf_go(client); debconf_get(client, "netcfg/link_wait_timeout"); char *ptr, *end_ptr; ptr = client->value; if (!empty_str(ptr)) { link_waits = strtol(ptr, &end_ptr, 10); /* The input contains a single positive integer. */ if (*end_ptr == '\0' && link_waits > 0) { ok = 1; link_waits *= 4; } } if (!ok) { if (!empty_str(ptr)) { di_info("The value %s provided is not valid", ptr); } else { di_info("No value provided"); } debconf_input (client, "critical", "netcfg/bad_link_wait_timeout"); debconf_go (client); debconf_set(client, "netcfg/link_wait_timeout", "3"); } } di_info("Waiting time set to %d", link_waits / 4); debconf_capb(client, "progresscancel"); debconf_subst(client, "netcfg/link_detect_progress", "interface", if_name); debconf_progress_start(client, 0, link_waits, "netcfg/link_detect_progress"); for (count = 0; count < link_waits; count++) { usleep(250000); if (debconf_progress_set(client, count) == CMD_PROGRESSCANCELLED) { /* User cancelled on us... bugger */ rv = 0; di_info("Detecting link on %s was cancelled", if_name); break; } if (ethtool_lite(if_name) == 1) /* ethtool-lite's CONNECTED */ { di_info("Found link on %s", if_name); if (!empty_str(gateway) && !is_wireless_iface(if_name) && !is_layer3_qeth(if_name)) { for (count = 0; count < gw_tries; count++) { if (di_exec_shell_log(arping) == 0) break; } di_info("Gateway reachable on %s", if_name); } rv = 1; break; } } if (count == link_waits) { di_info("Reached timeout for link detection on %s", if_name); } debconf_progress_stop(client); debconf_capb(client, "backup"); return rv; } void netcfg_interface_init(struct netcfg_interface *iface) { memset(iface, 0, sizeof(*iface)); iface->name = NULL; iface->dhcp = -1; iface->dhcpv6 = -1; iface->address_family = -1; /* I hope nobody uses -1 for AF_INET */ iface->slaac = -1; iface->v6_stateful_config = -1; iface->v6_stateless_config = -1; iface->loopback = -1; iface->mode = MANAGED; } /* Parse an IP address (v4 or v6), with optional CIDR netmask, into * +interface+. Return 1 if all went well, and return 0 if something * went wrong (the "IP address" wasn't, for example). In the event * something went wrong, +interface+ is guaranteed to remain * unchanged. */ int netcfg_parse_cidr_address(const char *address, struct netcfg_interface *interface) { struct in_addr addr; struct in6_addr addr6; int ok; char *maskptr, *addrstr, addrbuf[NETCFG_ADDRSTRLEN]; int i; strncpy(addrbuf, address, NETCFG_ADDRSTRLEN-1); addrbuf[NETCFG_ADDRSTRLEN-1]='\0'; addrstr = strtrim(addrbuf); if ((maskptr = strchr(addrstr, '/'))) { /* Houston, we have a netmask; split it into bits */ *maskptr = '\0'; maskptr++; /* Verify that the mask is OK */ for (i = 0; maskptr[i]; i++) { if (!isdigit(maskptr[i])) { /* That's not good; bomb out early */ return 0; } } } ok = inet_pton (AF_INET, addrstr, &addr); if (ok) { interface->address_family = AF_INET; inet_ntop(AF_INET, &addr, interface->ipaddress, INET_ADDRSTRLEN); } else { /* Potential IPv6 address */ ok = inet_pton (AF_INET6, addrstr, &addr6); if (ok) { interface->address_family = AF_INET6; inet_ntop(AF_INET6, &addr6, interface->ipaddress, INET6_ADDRSTRLEN); } } if (ok && maskptr) { interface->masklen = atoi(maskptr); } else { interface->masklen = 0; } return ok; } void netcfg_network_address(const struct netcfg_interface *interface, char *network) { union inX_addr ipaddr, mask, net; inet_pton(interface->address_family, interface->ipaddress, &ipaddr); inet_mton(interface->address_family, interface->masklen, &mask); if (interface->address_family == AF_INET) { net.in4.s_addr = ipaddr.in4.s_addr & mask.in4.s_addr; } else if (interface->address_family == AF_INET6) { int i; for (i = 0; i < 4; i++) { net.in6.s6_addr32[i] = ipaddr.in6.s6_addr32[i] & mask.in6.s6_addr32[i]; } } inet_ntop(interface->address_family, &net, network, NETCFG_ADDRSTRLEN); } void netcfg_broadcast_address(const struct netcfg_interface *interface, char *broadcast) { struct in_addr broad, net, mask; char network[INET_ADDRSTRLEN]; /* IPv6 has no concept of broadcast addresses */ if (interface->address_family != AF_INET) { broadcast[0] = '\0'; return; } netcfg_network_address(interface, network); inet_pton(AF_INET, network, &net); inet_mton(AF_INET, interface->masklen, &mask); broad.s_addr = (net.s_addr | ~mask.s_addr); inet_ntop(AF_INET, &broad, broadcast, INET_ADDRSTRLEN); } /* Validate that the given gateway address actually lies within the given * network. Standard boolean return. */ int netcfg_gateway_reachable(const struct netcfg_interface *interface) { union inX_addr net, mask, gw_addr; char network[NETCFG_ADDRSTRLEN]; netcfg_network_address(interface, network); inet_pton(interface->address_family, network, &net); inet_mton(interface->address_family, interface->masklen, &mask); inet_pton(interface->address_family, interface->gateway, &gw_addr); if (interface->address_family == AF_INET) { if (interface->masklen == 32) { /* Special case for single-address networks. We'll handle * routing manually in this case. */ return 1; } return (gw_addr.in4.s_addr && ((gw_addr.in4.s_addr & mask.in4.s_addr) == net.in4.s_addr)); } else if (interface->address_family == AF_INET6) { if (interface->masklen == 128) { /* Special case for single-address networks. We'll handle * routing manually in this case. */ return 1; } if ((ntohs(gw_addr.in6.s6_addr16[0]) & 0xffc0) == (0xfe80 & 0xffc0)) { return 1; } int i; for (i = 0; i < 4; i++) { if ((gw_addr.in6.s6_addr32[i] & mask.in6.s6_addr32[i]) != net.in6.s6_addr32[i]) { return 0; } } return 1; } else { /* Unknown address family */ fprintf(stderr, "Unknown address family given to netcfg_gateway_unreachable\n"); return 0; } } /* Take an FQDN (or possibly a bare hostname) and use it to preseed the get_hostname * and get_domain debconf variables. */ void preseed_hostname_from_fqdn(struct debconfclient *client, char *buf) { char *dom; if (valid_domain(buf)) { di_debug("%s is a valid FQDN", buf); dom = strchr(buf, '.'); if (dom) { di_debug("We have a real FQDN"); *dom++ = '\0'; } debconf_set(client, "netcfg/get_hostname", buf); if (!have_domain && dom != NULL) { di_debug("Preseeding domain as well: %s", dom); debconf_set(client, "netcfg/get_domain", dom); have_domain = 1; } else if (have_domain && !empty_str(domain)) { /* Global var 'domain' is holding a temporary domain name, * presumably glommed from DHCP. Use it as default instead. */ di_debug("Preseeding domain from global: %s", domain); debconf_set(client, "netcfg/get_domain", domain); } } } /* Classic rtrim... strip off trailing whitespace from a string */ void rtrim(char *s) { int n; n = strlen(s) - 1; while (n >= 0 && isspace(s[n])) { s[n] = '\0'; n--; } } char *strtrim(char *s) { size_t len; len = strlen(s); if (!len) return s; rtrim(s); while (*s && isspace(*s)) s++; return s; }