/* * resolv_manager.c * This file is part of Network-inador * * Copyright (C) 2022 - 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 "resolv_manager.h" #include "network-inador-private.h" #include "utils.h" #include "resolv_conf_parser.h" #include "interfaces.h" #include "file_watcher.h" void resolv_manager_write (NetworkInadorHandle *handle); int resolv_manager_sort_entries (const void *a, const void *b, void * data) { const ResolvConfEntry *aa = (ResolvConfEntry *) a; const ResolvConfEntry *bb = (ResolvConfEntry *) b; /* Primero, las purgadas van al final */ if (aa->for_purge == 1 && bb->for_purge == 0) { return 1; } else if (aa->for_purge == 0 && bb->for_purge == 1) { return -1; } else if (aa->for_purge == 1) { return 0; } if (aa->tagged == 1 && bb->tagged == 0) { /* TODO: Revisar este orden por prioridades */ return -1; } else if (aa->tagged == 0 && bb->tagged == 1) { return 1; } else if (aa->tagged == 1) { /* Como los dos están en el archivo, comparar por el file_order */ return (aa->file_order - bb->file_order); } /* TODO: Estas entradas necesitan resolverse por prioridad de configuración */ return 0; } void resolv_manager_clear_entries_by_prog (NetworkInadorHandle *handle, const int iface_index, const char *prog) { FList *g; ResolvConfEntry *entry; int do_write = 0; g = handle->resolver_entries; while (g != NULL) { entry = (ResolvConfEntry *) g->data; if (entry->origin != RESOLV_ORIGIN_RESOLVCONF || entry->owner_interface_index != iface_index) { /* Solo borramos las entradas creadas via resolvconf y que pertenezcan a esta interfaz */ g = g->next; continue; } if (prog == NULL || strcmp (entry->owner_prog, prog) == 0) { /* Hay que eliminar esta entrada, coincide por la interfaz o nombre de programa */ printf ("/// Eliminando entrada %s por via resolvconf. Prog = «%s»\n", entry->value, prog); /*free (entry); handle->resolver_entries = f_list_delete_link (handle->resolver_entries, g);*/ entry->for_purge = 1; do_write = 1; } g = g->next; } /* Solicitar una escritura del archivo, via el resolvconf fuimos modificados */ if (do_write == 1) { resolv_manager_write (handle); } } void resolv_manager_clear_tag_on_all (NetworkInadorHandle *handle) { FList *pos; ResolvConfEntry *entry; /* Borrar el atributo "tagged" */ pos = handle->resolver_entries; while (pos != NULL) { entry = (ResolvConfEntry *) pos->data; entry->tagged = 0; entry->was_new = 0; pos = pos->next; } } void resolv_manager_process_resolvconf_entries (NetworkInadorHandle *handle, void *entries) { ResolvConfEntry *new_entry, *found_entry; FList *g, *pos_searched_entry; int do_write = 0; int n; resolv_manager_clear_tag_on_all (handle); g = entries; n = 0; while (g != NULL) { new_entry = (ResolvConfEntry *) g->data; found_entry = NULL; while (found_entry == NULL) { pos_searched_entry = resolv_parser_search_entry (handle->resolver_entries, new_entry->resolv_type, new_entry->ns_family, &new_entry->nameserver, new_entry->value, 1); if (pos_searched_entry == NULL) break; /* De ser posible, hacer conciliación */ found_entry = (ResolvConfEntry *) pos_searched_entry->data; if (found_entry->origin == RESOLV_ORIGIN_FILE) { /* Hacer esta entrada nuestra */ found_entry->origin = RESOLV_ORIGIN_RESOLVCONF; found_entry->owner_interface_index = new_entry->owner_interface_index; found_entry->tagged = 1; strncpy (found_entry->owner_prog, new_entry->owner_prog, sizeof (found_entry->owner_prog)); printf ("/// Asociando una entrada %s existente del archivo al: %s\n", found_entry->value, found_entry->owner_prog); #if 0 } else if (found_entry->origin == RESOLV_ORIGIN_DHCP) { /* Una entrada que coincide con la actual solo se puede re-asociar al resolvconf si pertenece a la misma interfaz */ if (found_entry->owner_interface_index != new_entry->owner_interface_index) { found_entry->tagged = 1; found_entry = NULL; continue; } #endif } else { /* Existe la entrada, pero no la podemos tomar como nuestra, crear otra */ found_entry->tagged = 1; found_entry = NULL; continue; } } /* Si no existe, crearla */ if (found_entry == NULL) { found_entry = (ResolvConfEntry *) malloc (sizeof (ResolvConfEntry)); memcpy (found_entry, new_entry, sizeof (ResolvConfEntry)); found_entry->file_order = n; /* TODO: Revisar esto del file order */ found_entry->tagged = 0; found_entry->priority = 0; found_entry->for_purge = 0; /* Anexar al final de la lista */ handle->resolver_entries = f_list_append (handle->resolver_entries, found_entry); /* TODO: Como creamos una entrada via el resolvconf, regenerar el archivo */ printf ("/// Nueva entrada %s via resolvconf, programando regeneración.\n", found_entry->value); do_write = 1; } g = g->next; } /* Si hubo actualización cambios, disparar ahora */ if (do_write == 1) { resolv_manager_write (handle); } } void resolv_manager_process_dhcp_nameservers (NetworkInadorHandle *handle, Interface *iface) { FList *pos_entry; int h; ResolvConfEntry *entry; int do_write = 0; /* Recorrer todas las entradas que sean tipo DHCP y marcar para eliminación */ resolv_manager_clear_tag_on_all (handle); pos_entry = handle->resolver_entries; while (pos_entry != NULL) { entry = (ResolvConfEntry *) pos_entry->data; if (entry->origin == RESOLV_ORIGIN_DHCP && entry->owner_interface_index == iface->index) { /* Nos interesa para eliminación */ entry->for_purge = 1; } pos_entry = pos_entry->next; } /* Ahora, procesar la información de DHCP buscando entradas (viejas o de archivo) para reciclar. * Si no, crear */ for (h = 0; h < iface->dhcpc.dns_c; h++) { entry = NULL; while (entry == NULL) { pos_entry = resolv_parser_search_entry (handle->resolver_entries, RESOLV_TYPE_NAMESERVER, AF_INET, &iface->dhcpc.dns[h], "", 1); if (pos_entry == NULL) break; /* De ser posible, hacer conciliación */ entry = (ResolvConfEntry *) pos_entry->data; if (entry->origin == RESOLV_ORIGIN_FILE) { /* Hacer esta entrada nuestra */ entry->origin = RESOLV_ORIGIN_DHCP; entry->owner_interface_index = iface->index; entry->tagged = 1; entry->owner_prog[0] = 0; printf ("/// Asociando una entrada %s existente del archivo al DHCP (%s)\n", entry->value, iface->name); } else if (entry->origin == RESOLV_ORIGIN_DHCP && entry->owner_interface_index == iface->index) { /* Una entrada vieja coincide, reciclar */ entry->tagged = 1; entry->for_purge = 0; } else { /* Existe la entrada, pero no la podemos tomar como nuestra, buscar otra */ entry->tagged = 1; entry = NULL; continue; } } /* Si la entrada no se pudo conciliar, crear una */ if (entry == NULL) { entry = (ResolvConfEntry *) malloc (sizeof (ResolvConfEntry)); entry->resolv_type = RESOLV_TYPE_NAMESERVER; entry->origin = RESOLV_ORIGIN_DHCP; entry->owner_interface_index = iface->index; entry->owner_prog[0] = 0; entry->file_order = h; entry->tagged = 0; entry->priority = 0; entry->for_purge = 0; entry->ns_family = AF_INET; memcpy (&entry->nameserver, &iface->dhcpc.dns[h], 4); inet_ntop (AF_INET, &entry->nameserver, entry->value, sizeof (entry->value)); /* Anexar al final de la lista */ handle->resolver_entries = f_list_append (handle->resolver_entries, entry); /* Como creamos una entrada via el dhcp, regenerar el archivo */ printf ("/// Nueva entrada %s via DHCP (%s), programando regeneración.\n", entry->value, iface->name); do_write = 1; } } /* Si tiene un nombre de dominio, agregar como entrada tipo search */ if (iface->dhcpc.domain_name[0] != 0) { entry = NULL; while (entry == NULL) { pos_entry = resolv_parser_search_entry (handle->resolver_entries, RESOLV_TYPE_SEARCH, AF_INET, NULL, iface->dhcpc.domain_name, 1); if (pos_entry == NULL) break; /* De ser posible, hacer conciliación */ entry = (ResolvConfEntry *) pos_entry->data; if (entry->origin == RESOLV_ORIGIN_FILE) { /* Hacer esta entrada nuestra */ entry->origin = RESOLV_ORIGIN_DHCP; entry->owner_interface_index = iface->index; entry->tagged = 1; entry->owner_prog[0] = 0; printf ("/// Asociando una entrada search %s existente del archivo al DHCP (%s)\n", entry->value, iface->name); } else if (entry->origin == RESOLV_ORIGIN_DHCP && entry->owner_interface_index == iface->index) { /* Una entrada vieja coincide, reciclar */ entry->tagged = 1; entry->for_purge = 0; } else { /* Existe la entrada, pero no la podemos tomar como nuestra, buscar otra */ entry->tagged = 1; entry = NULL; continue; } } /* Si la entrada no se pudo conciliar, crear una */ if (entry == NULL) { entry = (ResolvConfEntry *) malloc (sizeof (ResolvConfEntry)); entry->resolv_type = RESOLV_TYPE_SEARCH; entry->origin = RESOLV_ORIGIN_DHCP; entry->owner_interface_index = iface->index; entry->owner_prog[0] = 0; entry->file_order = h; entry->tagged = 0; entry->priority = 0; entry->for_purge = 0; entry->ns_family = AF_INET; memset (&entry->nameserver, 0, sizeof (entry->nameserver)); strncpy (entry->value, iface->dhcpc.domain_name, sizeof (entry->value)); /* Anexar al final de la lista */ handle->resolver_entries = f_list_append (handle->resolver_entries, entry); /* Como creamos una entrada via el dhcp, regenerar el archivo */ printf ("/// Nueva entrada search %s via DHCP (%s), programando regeneración.\n", entry->value, iface->name); do_write = 1; } } /* Después de recorrer las entradas, revisar si quedaron entradas por purgar. * Si hay entradas por purgar, programar una reescritura de archivo */ pos_entry = handle->resolver_entries; while (pos_entry != NULL) { entry = (ResolvConfEntry *) pos_entry->data; if (entry->for_purge == 1) { do_write = 1; printf ("/// Entrada %s eliminada via DHCP (%s), programando regeneración.\n", entry->value, iface->name); //break; } pos_entry = pos_entry->next; } if (do_write) { resolv_manager_write (handle); } } void resolv_manager_clear_dhcp_nameservers (NetworkInadorHandle *handle, Interface *iface) { FList *pos_entry; int h; ResolvConfEntry *entry; int do_write = 0; /* Recorrer todas las entradas que sean tipo DHCP y marcar para eliminación */ resolv_manager_clear_tag_on_all (handle); pos_entry = handle->resolver_entries; while (pos_entry != NULL) { entry = (ResolvConfEntry *) pos_entry->data; if (entry->origin == RESOLV_ORIGIN_DHCP && entry->owner_interface_index == iface->index) { /* Nos interesa para eliminación */ printf ("/// Entrada %s eliminada via DHCP (%s), programando regeneración.\n", entry->value, iface->name); entry->for_purge = 1; do_write = 1; } pos_entry = pos_entry->next; } if (do_write) { resolv_manager_write (handle); } } void resolv_manager_read_local_etc_resolv (NetworkInadorHandle *handle) { FILE *fd; FList *pos, *next; ResolvConfEntry *entry; fd = fopen ("/etc/resolv.conf", "r"); if (fd == NULL) return; printf ("/// Ejecutando lectura de resolv.conf\n"); /* Etiquetar las entradas con origen del archivo como "ya no en el archivo", para que cuando se lea el archivo, nos queden las que vamos a eliminar */ resolv_manager_clear_tag_on_all (handle); handle->resolver_entries = resolv_parser_parse_local_file (handle->resolver_entries, fd, RESOLV_ORIGIN_FILE); fclose (fd); /* A la salida, borrar las que ya no siguen en el archivo */ pos = handle->resolver_entries; while (pos != NULL) { next = pos->next; entry = (ResolvConfEntry *) pos->data; if (entry->origin == RESOLV_ORIGIN_FILE && entry->tagged == 0) { /* Esta entrada se va, la eliminaron */ printf ("/// La entrada %s del resolv.conf fué eliminada del archivo. Purgando nosotros.\n", entry->value); entry->for_purge = 1; handle->resolver_entries = f_list_delete_link (handle->resolver_entries, pos); free (entry); entry = NULL; } else { if (entry->was_new) { printf ("/// La entrada %s es nueva en el resolv.conf\n", entry->value); } else { printf ("/// La entrada %s del resolv.conf sigue en el archivo.\n", entry->value); } } pos = next; } /* Reordenar las entradas. TODO: Falta leer los ficheros de configuración del resolv.conf */ handle->resolver_entries = f_list_sort_with_data (handle->resolver_entries, resolv_manager_sort_entries, NULL); } void resolv_manager_write (NetworkInadorHandle *handle) { FILE *fd; FList *pos, *next; ResolvConfEntry *entry; Interface *iface; /* Antes de hacer la escritura, siempre hacemos una lectura */ printf ("/// Ejecutando pre-lectura de resolv.conf para poderlo modificar\n"); resolv_manager_read_local_etc_resolv (handle); /* Intentar la escritura */ fd = fopen ("/etc/resolv.conf", "w"); if (fd != NULL) { fprintf (fd, "# Autogenered by NetworkInador, you can change this file, changes are preserved"); fprintf (fd, "\n\n"); pos = handle->resolver_entries; while (pos != NULL) { entry = (ResolvConfEntry *) pos->data; if (entry->for_purge == 1) { /* Omitir */ pos = pos->next; continue; } iface = _interfaces_locate_by_index (handle->interfaces, entry->owner_interface_index); /* Escribir esta entrada "buena" al resolv.conf */ if (entry->origin == RESOLV_ORIGIN_FILE) { fprintf (fd, "# This entry was previously on file, preserving"); } else if (entry->origin == RESOLV_ORIGIN_DHCP) { fprintf (fd, "# This entry was added via DHCP (on %s) by NetworkInador", (iface != NULL ? iface->name : "unknown")); } else if (entry->origin == RESOLV_ORIGIN_RESOLVCONF) { fprintf (fd, "# This entry was added via resolvconf (%s.%s)", (iface != NULL ? iface->name : "unknown"), entry->owner_prog); } else if (entry->origin == RESOLV_ORIGIN_SLAAC_RDNSS) { fprintf (fd, "# This entry was added via RDNSS (IPv6)"); } fprintf (fd, "\n"); if (entry->resolv_type == RESOLV_TYPE_NAMESERVER) { fprintf (fd, "nameserver %s\n", entry->value); } else if (entry->resolv_type == RESOLV_TYPE_DOMAIN) { fprintf (fd, "domain %s\n", entry->value); } else if (entry->resolv_type == RESOLV_TYPE_SEARCH) { fprintf (fd, "search %s\n", entry->value); } else if (entry->resolv_type == RESOLV_TYPE_SORTLIST) { fprintf (fd, "sortlist %s\n", entry->value); } else if (entry->resolv_type == RESOLV_TYPE_OPTIONS) { fprintf (fd, "options %s\n", entry->value); } pos = pos->next; } fclose (fd); } /* Purgar las entradas marcadas para purge */ pos = handle->resolver_entries; while (pos != NULL) { next = pos->next; entry = (ResolvConfEntry *) pos->data; if (entry->for_purge == 1) { handle->resolver_entries = f_list_delete_link (handle->resolver_entries, pos); free (entry); entry = NULL; } pos = next; } } void resolv_manager_notify_close_write_cb (NetworkInadorHandle *handle, const char *path, void *data) { /* Cuando se nos notifique acerca de un cambio en el resolv.conf re-leer el archivo */ resolv_manager_read_local_etc_resolv (handle); } void resolv_manager_init (NetworkInadorHandle *handle) { network_inador_file_watcher_add_file (handle, "/etc/resolv.conf", resolv_manager_notify_close_write_cb, NULL); handle->resolver_entries = NULL; /* Luego, leer el resolv.conf */ resolv_manager_read_local_etc_resolv (handle); }