NetworkInador/lib/resolv_manager.c

487 lines
16 KiB
C

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include "resolv_manager.h"
#include "network-inador-private.h"
#include "utils.h"
#include "resolv_conf_parser.h"
#include "interfaces.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;
pos = pos->next;
}
}
void resolv_manager_process_resolvconf_entries (NetworkInadorHandle *handle, ResolvConfEntry *entries, int num_entries) {
int g;
ResolvConfEntry *entry;
FList *pos_entry;
int do_write = 0;
resolv_manager_clear_tag_on_all (handle);
for (g = 0; g < num_entries; g++) {
entry = NULL;
while (entry == NULL) {
pos_entry = resolv_parser_search_entry (handle->resolver_entries, entries[g].resolv_type, entries[g].ns_family, &entries[g].nameserver, entries[g].value, 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_RESOLVCONF;
entry->owner_interface_index = entries[g].owner_interface_index;
entry->tagged = 1;
strncpy (entry->owner_prog, entries[g].owner_prog, sizeof (entry->owner_prog));
printf ("/// Asociando una entrada %s existente del archivo al: %s\n", entry->value, entry->owner_prog);
#if 0
} else if (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 (entry->owner_interface_index != entries[g].owner_interface_index) {
entry->tagged = 1;
entry = NULL;
continue;
}
#endif
} else {
/* Existe la entrada, pero no la podemos tomar como nuestra, crear otra */
entry->tagged = 1;
entry = NULL;
continue;
}
}
/* Si no existe, crearla */
if (entry == NULL) {
entry = (ResolvConfEntry *) malloc (sizeof (ResolvConfEntry));
memcpy (entry, &entries[g], sizeof (ResolvConfEntry));
entry->file_order = g; /* TODO: Revisar esto del file order */
entry->tagged = 0;
entry->priority = 0;
entry->for_purge = 0;
/* Anexar al final de la lista */
handle->resolver_entries = f_list_append (handle->resolver_entries, entry);
/* TODO: Como creamos una entrada via el resolvconf, regenerar el archivo */
printf ("/// Nueva entrada %s via resolvconf, programando regeneración.\n", entry->value);
do_write = 1;
}
}
/* 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 {
printf ("/// La entrada %s del resolv.conf sigue en el archivo.\n", entry->value);
}
pos = 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 */
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_init (NetworkInadorHandle *handle) {
//handle->resolver_inotify_fd = inotify_init1 (IN_NONBLOCK | IN_CLOEXEC);
if (handle->resolver_inotify_fd >= 0) {
/* Debemos primero instalar el manejador de eventos de escritura sobre el resolv.conf */
/*
IN_CLOSE_WRITE Releer archivo.
IN_DELETE_SELF Dejar de hacer watch
IN_MOVE_SELF Dejar de hacer watch
*/
//handle->resolver_inotify_watch = inotify_add_watch (handle->resolver_inotify_fd, "/etc/resolv.conf",
}
handle->resolver_entries = NULL;
/* Luego, leer el resolv.conf */
resolv_manager_read_local_etc_resolv (handle);
}