/* * ni-iface-helper.c * This file is part of Network Inador * * Copyright (C) 2021 - Félix Arreola Rodríguez * * Network Inador 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. * * Network Inador 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 Network Inador; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include "network-inador-manager.h" #define COMMAND_SOCKET_PATH "/tmp/network-inador.socket" extern char** environ; int debug (int argc, char *argv[]) { char **item; int fd; fd = open ("/tmp/var_dhcp.env", O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd < 0) { return -1; } for (item = environ; *item; item++) { char *name, *val, **p; /* Split on the = */ name = strdup (*item); val = strchr (name, '='); if (!val || val == name) { free (name); continue; } *val++ = '\0'; /* Ignore non-DCHP-related environment variables for (p = (char **) ignore; *p; p++) { if (strncmp (name, *p, strlen (*p)) == 0) goto next; }*/ write (fd, "Name: <", 7); write (fd, name, strlen (name)); write (fd, ">, Value: <", 11); write (fd, val, strlen (val)); write (fd, ">\n", 2); } close (fd); return 0; } enum { DHCP_CLIENT_SELECTING, DHCP_CLIENT_BOUND, DHCP_CLIENT_RENEWED, DHCP_CLIENT_EXPIRED, DHCP_CLIENT_FAILED, DHCP_CLIENT_KILLED, }; uint32_t utils_ip4_netmask_to_prefix (uint32_t netmask) { uint32_t prefix; uint8_t v; const uint8_t *p = (uint8_t *) &netmask; if (p[3]) { prefix = 24; v = p[3]; } else if (p[2]) { prefix = 16; v = p[2]; } else if (p[1]) { prefix = 8; v = p[1]; } else { prefix = 0; v = p[0]; } while (v) { prefix++; v <<= 1; } return prefix; } char *retrieve_env (const char *name) { char *bus, *dup, *get; get = getenv (name); if (get == NULL) { return NULL; } bus = strchr (get, '='); if (!bus || bus == get) { return NULL; } dup = strdup (&get[1]); if (dup == NULL) { return NULL; } return dup; } int wait_for_ack_or_error (int s) { unsigned char buffer[128]; int ret; ret = recv (s, buffer, sizeof (buffer), 0); if (ret < 3) { return -1; } if (buffer[0] == NET_INADOR_TYPE_RESPONSE_ERROR) { return -1; } if (buffer[0] == NET_INADOR_TYPE_RESPONSE && buffer[1] == NET_INADOR_RESPONSE_EXECUTED) { return 0; } return -1; } void send_basic (int s, char *interface, int tipo) { unsigned char buffer[128]; buffer[0] = NET_INADOR_TYPE_COMMAND; buffer[1] = NET_INADOR_COMMAND_DHCP_CLIENT_FEED; buffer[2] = AF_INET; if (tipo == DHCP_CLIENT_SELECTING) { buffer[3] = NET_INADOR_DHCP_STATUS_SELECTING; } else if (tipo == DHCP_CLIENT_EXPIRED) { buffer[3] = NET_INADOR_DHCP_STATUS_EXPIRED; } else if (tipo == DHCP_CLIENT_FAILED) { buffer[3] = NET_INADOR_DHCP_STATUS_FAILED; } buffer[4] = strlen (interface); memset (&buffer[5], 0, 11); strncpy (&buffer[16], interface, buffer[4]); send (s, buffer, 16 + buffer[4], 0); if (wait_for_ack_or_error (s) < 0) { return; } } void send_bound_renew (int s, int is_bound, char *interface, struct in_addr *ip, int prefix, struct in_addr *gateways, int gw_c, struct in_addr *broadcast, struct in_addr *dhcp_server, uint32_t lease_time, struct in_addr *dns, int dns_c, char *domain_name) { unsigned char buffer[128]; struct in_addr empty; int pos, g; memset (&empty, 0, sizeof (empty)); buffer[0] = NET_INADOR_TYPE_COMMAND; buffer[1] = NET_INADOR_COMMAND_DHCP_CLIENT_FEED; buffer[2] = AF_INET; if (is_bound) { buffer[3] = NET_INADOR_DHCP_STATUS_BOUND; } else { buffer[3] = NET_INADOR_DHCP_STATUS_RENEWED; } buffer[4] = strlen (interface); memset (&buffer[5], 0, 11); buffer[6] = prefix; if (lease_time != 0) { memcpy (&buffer[12], &lease_time, 4); } pos = 16; /* Copiar el nombre de la interfaz */ strncpy (&buffer[16], interface, buffer[4]); pos = 16 + buffer[4]; if (memcmp (&empty, ip, sizeof (empty)) != 0) { /* Tenemos una IP */ buffer[5] = 4; memcpy (&buffer[pos], ip, 4); pos += 4; } /* Procesar las múltiples rutas posibles */ buffer[7] = 4 * gw_c; for (g = 0; g < gw_c; g++) { memcpy (&buffer[pos], &gateways[g], 4); pos += 4; } if (memcmp (&empty, broadcast, sizeof (empty)) != 0) { /* Tenemos broadcast */ buffer[8] = 4; memcpy (&buffer[pos], broadcast, 4); pos += 4; } if (memcmp (&empty, dhcp_server, sizeof (empty)) != 0) { /* Tenemos IP del servidor DHCP */ buffer[9] = 4; memcpy (&buffer[pos], dhcp_server, 4); pos += 4; } /* Procesar los múltiples dns */ buffer[10] = 4 * dns_c; for (g = 0; g < dns_c; g++) { memcpy (&buffer[pos], &dns[g], 4); pos += 4; } /* Procesar el nombre de dominio */ if (domain_name != NULL && domain_name[0] != 0) { buffer[11] = strlen (domain_name); memcpy (&buffer[pos], domain_name, buffer[11]); pos += buffer[11]; } send (s, buffer, pos, 0); if (wait_for_ack_or_error (s) < 0) { return; } } int parse_ip (char *var, struct in_addr *addr) { int res; if (var == NULL) { return -1; } res = inet_pton (AF_INET, var, addr); if (res <= 0) { memset (addr, 0, sizeof (*addr)); return -1; } return 0; } int parse_multiple_ip (char *var, struct in_addr *addr, int max) { int res; char buffer[512]; char *inicio, *bus; int c = 0; size_t offset; if (var == NULL) return 0; inicio = var; while (inicio[0] != 0) { bus = strchr (inicio, ' '); if (bus == NULL) break; /* Desboardamiento */ offset = bus - inicio; if (offset + 1 > sizeof (buffer)) { return 0; } strncpy (buffer, inicio, offset); buffer[offset] = 0; inicio = &var[offset + 1]; res = parse_ip (buffer, &addr[c]); if (res < 0) continue; c++; if (c == max) return c; } /* Parsear lo último, si es que queda algo */ if (inicio[0] != 0) { res = parse_ip (inicio, &addr[c]); if (res < 0) { return c; } c++; } return c; } int parse_int (char *var, int *entero) { int res; if (var == NULL) { return -1; } res = sscanf (var, "%i", entero); if (res <= 0) { *entero = 0; return -1; } return 0; } enum { IFACE_ISC_DHCLIENT = 1, IFACE_BUSYBOX_UDHCPC }; const char *isc_dhcp_reasons[] = { "MEDIUM", "PREINIT", "BOUND", "RENEW", "REBIND", "REBOOT", "EXPIRE", "FAIL", "STOP", "RELEASE", "NBI", "TIMEOUT", NULL }; const char *busybox_dhcp_argv[] = { "deconfig", "bound", "renew", "nak" }; int main (int argc, char *argv[]) { char *reason, *interface; int s, ret, tipo; struct sockaddr_un path_dest; struct in_addr ip, netmask, broadcast, dhcp_server; struct in_addr gateways[7]; struct in_addr dns[7]; int gw_c, dns_c; int prefix, lease_time; debug (argc, argv); tipo = 0; /* Tratar de determinar si estamos siendo corridos por un el DHCP del ISC o por el udhcpc busybox */ reason = getenv ("reason"); if (reason != NULL) { s = 0; while (isc_dhcp_reasons[s] != NULL) { if (strcmp (reason, isc_dhcp_reasons[s]) == 0) { /* Es un ISC DHCP Client */ tipo = IFACE_ISC_DHCLIENT; break; } s++; } } if (tipo == 0 && argc > 1) { /* Intentar revisar si es un busybox udhcpc */ s = 0; while (busybox_dhcp_argv[s] != NULL) { if (strcmp (argv[1], busybox_dhcp_argv[s]) == 0) { tipo = IFACE_BUSYBOX_UDHCPC; reason = argv[1]; break; } s++; } } if (tipo == 0) { return 1; } interface = getenv ("interface"); if (reason == NULL || interface == NULL) { return 1; } /* Intentar abrir el socket a la escucha */ s = socket (AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); if (s < 0) { return 1; } path_dest.sun_family = AF_UNIX; strncpy (path_dest.sun_path, COMMAND_SOCKET_PATH, sizeof (path_dest.sun_path)); ret = connect (s, (struct sockaddr *) &path_dest, sizeof (path_dest)); if (ret < 0) { perror ("Connect"); close (s); return 1; } if (strcmp (reason, "PREINIT") == 0 || // ISC DHCP strcmp (reason, "deconfig") == 0 // Busybox udhcpc ) { send_basic (s, interface, DHCP_CLIENT_SELECTING); } if (strcmp (reason, "FAIL") == 0) { send_basic (s, interface, DHCP_CLIENT_FAILED); } else if (strcmp (reason, "BOUND") == 0 || strcmp (reason, "REBOOT") == 0 || strcmp (reason, "RENEW") == 0 || strcmp (reason, "REBIND") == 0 || // 4 de ISC DHCP strcmp (reason, "bound") == 0 || strcmp (reason, "renew") == 0 // 2 de Busybox udhcpc ) { /* * Los valores más importantes recibidos: * new_domain_name_servers -> los DNS * new_expiry -> el timestamp de la hora * new_domain_name -> DNS search name * new_routers -> Lista de routers * new_subnet_mask -> Máscara de red en formato largo * new_broadcast_address -> Dirección broadcast * new_ip_address -> Dirección IP * new_dhcp_lease_time -> Tiempo en segundos */ /* Parsear y revisar las direcciones IP recibidas */ char *s_ip, *s_mask, *s_gateway, *s_broadcast, *s_lease_time, *s_dhcp_server, *s_dns, *s_domain_name; if (tipo == IFACE_ISC_DHCLIENT) { s_ip = getenv ("new_ip_address"); s_mask = getenv ("new_subnet_mask"); s_gateway = getenv ("new_routers"); s_broadcast = getenv ("new_broadcast_address"); s_lease_time = getenv ("new_dhcp_lease_time"); s_dhcp_server = getenv ("new_dhcp_server_identifier"); s_dns = getenv ("new_domain_name_servers"); s_domain_name = getenv ("new_domain_name"); } else if (tipo == IFACE_BUSYBOX_UDHCPC) { s_ip = getenv ("ip"); s_mask = getenv ("subnet"); s_gateway = getenv ("router"); s_broadcast = getenv ("broadcast"); s_lease_time = getenv ("lease"); s_dhcp_server = getenv ("serverid"); s_dns = getenv ("dns"); s_domain_name = getenv ("domain"); } /* TODO: Faltan los DNS */ if (parse_ip (s_ip, &ip) < 0) { close (s); return -1; } if (parse_ip (s_mask, &netmask) < 0) { close (s); return -1; } prefix = utils_ip4_netmask_to_prefix (netmask.s_addr); gw_c = parse_multiple_ip (s_gateway, gateways, 7); if (parse_ip (s_broadcast, &broadcast) < 0) { memset (&broadcast, 0, sizeof (broadcast)); } if (parse_ip (s_dhcp_server, &dhcp_server) < 0) { memset (&dhcp_server, 0, sizeof (dhcp_server)); } if (parse_int (s_lease_time, &lease_time) < 0) { lease_time = 0; } dns_c = parse_multiple_ip (s_dns, dns, 7); int is_bound = 0; if (strcmp (reason, "BOUND") == 0 || strcmp (reason, "REBOOT") == 0 || // Del ISC dhcp strcmp (reason, "bound") == 0 // del busybox ) { is_bound = 1; } send_bound_renew (s, is_bound, interface, &ip, prefix, gateways, gw_c, &broadcast, &dhcp_server, lease_time, dns, dns_c, s_domain_name); } else if (strcmp (reason, "EXPIRE") == 0 || strcmp (reason, "STOP") == 0 || strcmp (reason, "RELEASE") == 0) { /* Los mismos valores, pero con old_ */ send_basic (s, interface, DHCP_CLIENT_EXPIRED); } else { send_basic (s, interface, DHCP_CLIENT_FAILED); close (s); return -1; } close (s); return 0; }