From e407f939f4fc4560ad89d40ca759436740f4738d Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 20 Sep 2018 12:33:51 +0100 Subject: [PATCH] Add a utility library and number of utility programs Add a utility library to do parsing of cellserv database and DNS lookups for server details. This will be of use to the kafs-utils package as well. Parts of it may also be generally useful, so it may get moved into keyutils. Add a number of utility programs: (1) kafs-check-config: Sysadmin tool. Check the cellserv database and allow it to be dumped. (2) kafs-preload: Systemd auxiliary program. Preload the in-kernel cell database from the configuration. (3) dns_afsdb: Request-key upcall program. Fetch the VL server list and addresses lists for a cell on behalf of the kernel. Add a systemd service to configure the kafs dynamic root by preloading cell names into the cell database. Signed-off-by: David Howells --- .gitignore | 6 + Makefile | 14 +- conf/afs.mount | 1 + conf/kafs-config.service | 9 + conf/kafs_dns.conf | 1 + redhat/kafs-client.spec | 63 +++- src/Makefile | 100 +++++-- src/Makefile.config | 63 ++++ src/aklog-kafs.c | 4 +- src/dns_afsdb.h | 11 + src/dns_afsdb_text.c | 105 +++++++ src/dns_afsdb_v1.c | 103 +++++++ src/dns_main.c | 419 ++++++++++++++++++++++++++ src/dns_resolver.h | 116 ++++++++ src/include/kafs/cellserv.h | 154 ++++++++++ src/include/kafs/profile.h | 154 ++++++++++ src/include/kafs/reporting.h | 29 ++ src/kafs-check-config.c | 156 ++++++++++ src/lib_cell_lookup.c | 218 ++++++++++++++ src/lib_cellserv.c | 420 ++++++++++++++++++++++++++ src/lib_dns_lookup.c | 559 +++++++++++++++++++++++++++++++++++ src/lib_object.c | 164 ++++++++++ src/lib_profile.c | 512 ++++++++++++++++++++++++++++++++ src/preload-cells.c | 154 ++++++++++ src/version.lds | 28 ++ 25 files changed, 3514 insertions(+), 49 deletions(-) create mode 100644 conf/kafs-config.service create mode 100644 conf/kafs_dns.conf create mode 100644 src/Makefile.config create mode 100644 src/dns_afsdb.h create mode 100644 src/dns_afsdb_text.c create mode 100644 src/dns_afsdb_v1.c create mode 100644 src/dns_main.c create mode 100644 src/dns_resolver.h create mode 100644 src/include/kafs/cellserv.h create mode 100644 src/include/kafs/profile.h create mode 100644 src/include/kafs/reporting.h create mode 100644 src/kafs-check-config.c create mode 100644 src/lib_cell_lookup.c create mode 100644 src/lib_cellserv.c create mode 100644 src/lib_dns_lookup.c create mode 100644 src/lib_object.c create mode 100644 src/lib_profile.c create mode 100644 src/preload-cells.c create mode 100644 src/version.lds diff --git a/.gitignore b/.gitignore index 34b3993..47d6dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ aklog-kafs +kafs-check-config +kafs-preload +kafs-dns +*.o +*.os +libkafs_client.so* diff --git a/Makefile b/Makefile index 56f70f0..3f9b55c 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ CFLAGS = -g -O2 -Wall -Wsign-compare +MKDIR = mkdir INSTALL = install DESTDIR = -ETCDIR = /etc/kafs +ETCDIR = /etc BINDIR = /usr/bin +LIBEXECDIR = /usr/libexec MANDIR = /usr/share/man DATADIR = /usr/share/kafs-client +UNITDIR = /usr/lib/systemd/system SPECFILE = redhat/kafs-client.spec LNS := ln -sf @@ -53,9 +56,12 @@ install: all $(INSTALL) -D -m 0644 man/aklog-kafs.1 $(DESTDIR)$(MAN1)/aklog-kafs.1 $(INSTALL) -D -m 0644 man/aklog.1 $(DESTDIR)$(MAN1)/aklog.1 $(INSTALL) -D -m 0644 conf/cellservdb.conf $(DESTDIR)$(DATADIR)/cellservdb.conf - $(INSTALL) -D -m 0644 conf/etc.conf $(DESTDIR)$(ETCDIR)/cellservdb.conf - mkdir -m755 $(DESTDIR)$(ETCDIR)/cellservdb.d - mkdir -m755 $(DESTDIR)/afs + $(INSTALL) -D -m 0644 conf/etc.conf $(DESTDIR)$(ETCDIR)/kafs/cellservdb.conf + $(INSTALL) -D -m 0644 conf/kafs_dns.conf $(DESTDIR)$(ETCDIR)/request-key.d/kafs_dns.conf + $(INSTALL) -D -m 0644 conf/kafs-config.service $(DESTDIR)$(UNITDIR)/kafs-config.service + $(INSTALL) -D -m 0644 conf/afs.mount $(DESTDIR)$(UNITDIR)/afs.mount + $(MKDIR) -m755 $(DESTDIR)$(ETCDIR)/kafs/cellservdb.d + $(MKDIR) -m755 $(DESTDIR)/afs ############################################################################### # diff --git a/conf/afs.mount b/conf/afs.mount index 8e72726..082efd7 100644 --- a/conf/afs.mount +++ b/conf/afs.mount @@ -1,6 +1,7 @@ [Unit] Description=kAFS Dynamic Root mount ConditionPathExists=/afs +Wants=kafs-config.service [Mount] What=none diff --git a/conf/kafs-config.service b/conf/kafs-config.service new file mode 100644 index 0000000..f837e33 --- /dev/null +++ b/conf/kafs-config.service @@ -0,0 +1,9 @@ +[Unit] +Description=Preload AFS Cell Database +After=local-fs.target +DefaultDependencies=no + +[Service] +Type=oneshot +ExecStartPre=/sbin/modprobe -q kafs +ExecStart=/usr/libexec/kafs-preload diff --git a/conf/kafs_dns.conf b/conf/kafs_dns.conf new file mode 100644 index 0000000..7e16d79 --- /dev/null +++ b/conf/kafs_dns.conf @@ -0,0 +1 @@ +create dns_resolver afsdb:* * /usr/libexec/kafs-dns %k diff --git a/redhat/kafs-client.spec b/redhat/kafs-client.spec index 66faa2c..db77f6d 100644 --- a/redhat/kafs-client.spec +++ b/redhat/kafs-client.spec @@ -1,4 +1,6 @@ # % define buildid .local +%global libapivermajor 0 +%global libapiversion %{libapivermajor}.1 Name: kafs-client Version: 0.1 @@ -17,7 +19,6 @@ BuildRequires: openssl-devel # kAFS config files: # /etc/kafs/cellservdb.conf # -%global confdir %{_sysconfdir}/kafs %global datadir %{_datarootdir}/kafs Requires: keyutils # >= 1.5.11 @@ -32,6 +33,22 @@ Requires: selinux-policy-base >= 3.7.19-5 Provide basic AFS-compatible tools for kAFS and systemd scripts to mount the dynamic root on /afs and preload the cell database. +%package libs +Summary: Library of routines for dealing with kAFS +Requires: %{name}%{?_isa} = %{version}-%{release} + +%description libs +Provide a library of shareable routines for dealing with the kAFS +filesystem. These provide things like configuration parsing and DNS lookups. + +%package libs-devel +Summary: Library of routines for dealing with kAFS +Requires: %{name}-libs%{?_isa} = %{version}-%{release} + +%description libs-devel +Provide a library of shareable routines for dealing with the kAFS +filesystem. These provide things like configuration parsing and DNS lookups. + # # We generate a compatibility package that makes kafs look like OpenAFS, but it # needs to be uninstalled be able to install OpenAFS or Auristor. @@ -53,32 +70,39 @@ another AFS implementation (such as OpenAFS). %build make all \ - ETCDIR=%{etcdir} \ + ETCDIR=%{_sysconfdir} \ BINDIR=%{_bindir} \ - MANDIR=%{_mandir} \ + SBINDIR=%{_sbindir} \ DATADIR=%{datadir} \ + INCLUDEDIR=%{_includedir} \ + LIBDIR=%{_libdir} \ + LIBEXECDIR=%{_libexecdir} \ + MANDIR=%{_mandir} \ CFLAGS="-Wall -Werror $RPM_OPT_FLAGS $RPM_LD_FLAGS $ARCH_OPT_FLAGS" %install mkdir -p %{buildroot}%{_bindir} mkdir -p %{buildroot}%{_unitdir} mkdir -p %{buildroot}%{_mandir}/man1 -mkdir -p %{buildroot}%{confdir} -mkdir -p %{buildroot}%{datadir} +mkdir -p %{buildroot}%{_sysconfdir} +mkdir -p %{buildroot}%{_datarootdir} make DESTDIR=%{buildroot} install \ - ETCDIR=%{confdir} \ - DATADIR=%{datadir} \ + ETCDIR=%{_sysconfdir} \ + BINDIR=%{_bindir} \ SBINDIR=%{_sbindir} \ + DATADIR=%{datadir} \ + INCLUDEDIR=%{_includedir} \ + LIBDIR=%{_libdir} \ + LIBEXECDIR=%{_libexecdir} \ MANDIR=%{_mandir} \ - CFLAGS="-Wall $RPM_OPT_FLAGS -Werror" - -%{__install} -m 644 conf/afs.mount %{buildroot}%{_unitdir}/afs.mount -%{__install} -m 644 conf/etc.conf %{buildroot}%{confdir}/cellservdb.conf + CFLAGS="-Wall -Werror $RPM_OPT_FLAGS $RPM_LD_FLAGS $ARCH_OPT_FLAGS" # Compat ln -s aklog-kafs %{buildroot}/%{_bindir}/aklog +%ldconfig_scriptlets libs + %post %systemd_post afs.mount @@ -93,12 +117,23 @@ ln -s aklog-kafs %{buildroot}/%{_bindir}/aklog %license LICENCE.GPL /afs %{_bindir}/aklog-kafs +%{_sbindir}/kafs-check-config %{_unitdir}/* %{_mandir}/man1/aklog-kafs.1* -%{confdir} +%{_libexecdir}/kafs-preload +%{_libexecdir}/kafs-dns +%{_sysconfdir}/request-key.d/kafs_dns.conf + +%files libs +%{_libdir}/libkafs_client.so.%{libapiversion} +%{_libdir}/libkafs_client.so.%{libapivermajor} %{datadir} -%config(noreplace) %{confdir}/cellservdb.conf -%config(noreplace) %{confdir}/cellservdb.d +%config(noreplace) %{_sysconfdir}/kafs/cellservdb.conf +%config(noreplace) %{_sysconfdir}/kafs/cellservdb.d + +%files libs-devel +%{_libdir}/libkafs_client.so +%{_includedir}/* %files compat %{_bindir}/aklog diff --git a/src/Makefile b/src/Makefile index 1edabfa..16365b7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,45 +1,78 @@ -CFLAGS = -g -O2 -Wall -Wsign-compare -INSTALL = install -DESTDIR = -ETCDIR = /etc/kafs -BINDIR = /usr/bin -DATADIR = /usr/share/kafs-client -SPECFILE = ../redhat/kafs-client.spec +CFLAGS = -g -Wall -Wsign-compare -LNS := ln -sf +include Makefile.config -############################################################################### -# -# Determine the current package version from the specfile -# -############################################################################### -VERSION := $(word 2,$(shell grep "^Version:" $(SPECFILE))) -CPPFLAGS += -DVERSION="\"$(VERSION)\"" +all: lib progs + +CPPFLAGS += -Iinclude -DETCDIR=\"$(ETCDIR)\" ############################################################################### # -# Guess at the appropriate word size +# Build a library to do config parsing and other stuff. # ############################################################################### -BUILDFOR := $(shell file /usr/bin/make | sed -e 's!.*ELF \(32\|64\)-bit.*!\1!')-bit +lib: $(DEVELLIB) + +$(DEVELLIB): $(SONAME) + $(LNS) $< $@ + +$(SONAME): $(LIBNAME) + $(LNS) $< $@ + +LIB_HEADERS := $(wildcard include/kafs/*.h) +LIB_FILES := \ + lib_cell_lookup.c \ + lib_cellserv.c \ + lib_dns_lookup.c \ + lib_object.c \ + lib_profile.c + +LIBVERS := -shared -Wl,-soname,$(SONAME) -Wl,--version-script,version.lds +LIB_OBJS := $(patsubst %.c,%.os,$(LIB_FILES)) -ifeq ($(BUILDFOR),32-bit) -CFLAGS += -m32 -else -ifeq ($(BUILDFOR),64-bit) -CFLAGS += -m64 -endif -endif +%.os: %.c + $(CC) $(CPPFLAGS) $(CFLAGS) -fPIC -o $@ -c $< + +$(LIBNAME): $(LIB_OBJS) version.lds Makefile + $(CC) $(CFLAGS) -fPIC $(LDFLAGS) $(LIBVERS) -o $@ $(LIB_OBJS) $(LIBLIBS) \ + -lresolv + +$(LIB_OBJS) : $(LIB_HEADERS) Makefile + +LIB_DEPENDENCY := $(DEVELLIB) +LDFLAGS += -L. + +#%.o: %.c $(LIB_HEADERS) Makefile +# $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $< ############################################################################### # # Build stuff # ############################################################################### -all: aklog-kafs +progs: \ + aklog-kafs \ + kafs-check-config \ + kafs-preload \ + kafs-dns + +aklog-kafs: aklog-kafs.o + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< -lkrb5 -lcrypto -lkeyutils + +kafs-check-config: kafs-check-config.o $(DEVELLIB) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ kafs-check-config.o -lkafs_client + +kafs-preload: preload-cells.o $(DEVELLIB) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ preload-cells.o -lkafs_client + +KAFS_DNS_OBJS := dns_main.o dns_afsdb_text.o dns_afsdb_v1.o +kafs-dns: $(KAFS_DNS_OBJS) $(DEVELLIB) + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(KAFS_DNS_OBJS) \ + -lkafs_client -lkeyutils -aklog-kafs: aklog-kafs.c Makefile - $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< -lkrb5 -lcrypto -lkeyutils +kafs-check-config.o: $(LIB_HEADERS) +preload-cells.o: $(LIB_HEADERS) +$(KAFS_DNS_OBJS): $(LIB_HEADERS) ############################################################################### # @@ -47,7 +80,15 @@ aklog-kafs: aklog-kafs.c Makefile # ############################################################################### install: all + mkdir -p $(DESTDIR)$(INCLUDEDIR)/kafs + $(INSTALL) -D -m 0644 $(LIB_HEADERS) $(DESTDIR)$(INCLUDEDIR)/kafs/ + $(INSTALL) -D -m 0755 $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(LIBNAME) + $(LNS) $(LIBNAME) $(DESTDIR)$(LIBDIR)/$(SONAME) + $(LNS) $(LIBDIR)/$(SONAME) $(DESTDIR)$(LIBDIR)/$(DEVELLIB) $(INSTALL) -D -m 0755 aklog-kafs $(DESTDIR)$(BINDIR)/aklog-kafs + $(INSTALL) -D -m 0755 kafs-check-config $(DESTDIR)$(SBINDIR)/kafs-check-config + $(INSTALL) -D -m 0755 kafs-preload $(DESTDIR)$(LIBEXECDIR)/kafs-preload + $(INSTALL) -D -m 0755 kafs-dns $(DESTDIR)$(LIBEXECDIR)/kafs-dns ############################################################################### # @@ -55,8 +96,9 @@ install: all # ############################################################################### clean: - $(RM) aklog-kafs - $(RM) *.o *~ + $(RM) aklog-kafs kafs-check-config kafs-preload kafs-dns + $(RM) $(DEVELLIB) $(SONAME) $(LIBNAME) + $(RM) *.o *~ *.os distclean: clean diff --git a/src/Makefile.config b/src/Makefile.config new file mode 100644 index 0000000..7f4004a --- /dev/null +++ b/src/Makefile.config @@ -0,0 +1,63 @@ +INSTALL = install +DESTDIR = +ETCDIR = /etc +BINDIR = /usr/bin +LIBEXECDIR = /usr/libexec +INCLUDEDIR = /usr/include +DATADIR = /usr/share/kafs-client +SPECFILE = ../redhat/kafs-client.spec + +ifeq ($(origin LIBDIR),undefined) +LIBDIR := $(shell ldd /usr/bin/make | grep '\(/libc\)' | sed -e 's!.*\(/.*\)/libc[.].*!\1!') +endif + +LNS := ln -sf + +############################################################################### +# +# Determine the current package version from the specfile +# +############################################################################### +VERSION := $(word 2,$(shell grep "^Version:" $(SPECFILE))) +CPPFLAGS += -DVERSION="\"$(VERSION)\"" + +############################################################################### +# +# Determine the current library version from the version script +# +############################################################################### +libversion := $(filter KAFS_CLIENT_%,$(shell grep ^KAFS_CLIENT_ version.lds)) +libversion := $(lastword $(libversion)) +libversion := $(lastword $(libversion)) +APIVERSION := $(subst KAFS_CLIENT_,,$(libversion)) +vernumbers := $(subst ., ,$(APIVERSION)) +APIMAJOR := $(firstword $(vernumbers)) + +DEVELLIB := libkafs_client.so +SONAME := $(DEVELLIB).$(APIMAJOR) +LIBNAME := $(DEVELLIB).$(APIVERSION) + +############################################################################### +# +# Guess at the appropriate word size +# +############################################################################### +BUILDFOR := $(shell file /usr/bin/make | sed -e 's!.*ELF \(32\|64\)-bit.*!\1!')-bit + +ifeq ($(BUILDFOR),32-bit) +CFLAGS += -m32 +else +ifeq ($(BUILDFOR),64-bit) +CFLAGS += -m64 +endif +endif + +############################################################################### +# +# Guess at the appropriate lib directory and word size +# +############################################################################### +ifeq ($(origin LIBDIR),undefined) +LIBDIR := $(shell ldd /usr/bin/make | grep '\(/libc\)' | sed -e 's!.*\(/.*\)/libc[.].*!\1!') +endif +BUILDFOR := $(shell file /usr/bin/make | sed -e 's!.*ELF \(32\|64\)-bit.*!\1!')-bit diff --git a/src/aklog-kafs.c b/src/aklog-kafs.c index 8e4cd47..27e4dc2 100644 --- a/src/aklog-kafs.c +++ b/src/aklog-kafs.c @@ -145,7 +145,7 @@ static void key_derivation_function(krb5_creds *creds, uint8_t *session_key) const EVP_MD *algo = EVP_md5(); /* We use HMAC-MD5 */ struct kdf_data kdf_data = rxkad_kdf_data; - + for (i = 1; i <= 255; i++) { /* K(i) = PRF(Ks, [i]_2 || Label || 0x00 || [L]_2) */ kdf_data.i_2 = i; @@ -276,7 +276,7 @@ int main(int argc, char **argv) for (p = cell; *p; p++) *p = tolower(*p); - + ret = asprintf(&princ, "afs/%s@%s", cell, realm); OSERROR(ret, "asprintf"); ret = asprintf(&desc, "afs@%s", cell); diff --git a/src/dns_afsdb.h b/src/dns_afsdb.h new file mode 100644 index 0000000..83f3a71 --- /dev/null +++ b/src/dns_afsdb.h @@ -0,0 +1,11 @@ +struct kafs_lookup_context; + +extern void *kafs_generate_text_payload(void *result, + const char *cell_name, + unsigned int *_ttl, + struct kafs_lookup_context *ctx); + +extern void *kafs_generate_v1_payload(void *result, + const char *cell_name, + unsigned int *_ttl, + struct kafs_lookup_context *ctx); diff --git a/src/dns_afsdb_text.c b/src/dns_afsdb_text.c new file mode 100644 index 0000000..b1911ed --- /dev/null +++ b/src/dns_afsdb_text.c @@ -0,0 +1,105 @@ +/* + * Generate a set of addresses as a text string. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#include +#include +#include +#include +#include +#include "dns_afsdb.h" + +static void store_char(char **_b, char n) +{ + *(*_b)++ = n; +} + +static void store_string(char **_b, const char *p) +{ + unsigned int len = strlen(p); + + memcpy(*_b, p, len); + *_b += len; +} + +/* + * Generate the payload to pass to the kernel as v1 server bundle. + */ +static void emit_text_str(char **_b, + struct kafs_server_list *vls, + unsigned short default_port) +{ + struct kafs_server_addr *addr; + struct kafs_server *server; + const char *p; + unsigned int i, j; + char buf[100]; + bool need_sep = false; + + for (i = 0; i < vls->nr_servers; i++) { + server = &vls->servers[i]; + + for (j = 0; j < server->nr_addrs; j++) { + addr = &server->addrs[j]; + + if (need_sep) + store_char(_b, ','); + need_sep = true; + + switch (addr->sin.sin_family) { + case AF_INET: + p = inet_ntop(AF_INET, &addr->sin.sin_addr, + buf, sizeof(buf)); + if (p) { + store_char(_b, '['); + store_string(_b, buf); + store_char(_b, ']'); + } + break; + case AF_INET6: + p = inet_ntop(AF_INET6, &addr->sin6.sin6_addr, + buf, sizeof(buf)); + if (p) { + store_char(_b, '['); + store_string(_b, buf); + store_char(_b, ']'); + } + break; + default: + continue; + } + + if (server->port && server->port != default_port) { + sprintf(buf, "%u", server->port); + store_char(_b, '+'); + store_string(_b, buf); + } + } + } + + store_char(_b, 0); +} + +void *kafs_generate_text_payload(void *result, + const char *cell_name, + unsigned int *_ttl, + struct kafs_lookup_context *ctx) +{ + struct kafs_cell *cell; + char *b = result; + + ctx->report.what = cell_name; + cell = kafs_lookup_cell(cell_name, ctx); + if (!cell) + return NULL; + + if (cell->vlservers) + emit_text_str(&b, cell->vlservers, 7003); + return b; +} diff --git a/src/dns_afsdb_v1.c b/src/dns_afsdb_v1.c new file mode 100644 index 0000000..93c6077 --- /dev/null +++ b/src/dns_afsdb_v1.c @@ -0,0 +1,103 @@ +/* + * Generate a v1 servers and addresses list. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#include +#include +#include +#include +#include "dns_afsdb.h" +#include "dns_resolver.h" + +static void store_u8(unsigned char **_b, unsigned char n) +{ + *(*_b)++ = n; +} + +static void store_u16(unsigned char **_b, unsigned short n) +{ + *(*_b)++ = (n >> 0) & 0xff; + *(*_b)++ = (n >> 8) & 0xff; +} + +static void store_octets(unsigned char **_b, const void *p, size_t n) +{ + memcpy(*_b, p, n); + *_b += n; +} + +/* + * Generate the payload to pass to the kernel as v1 server bundle. + */ +static void emit_v1(unsigned char **_b, struct kafs_server_list *vls) +{ + struct kafs_server_addr *addr; + struct kafs_server *server; + unsigned int i, j, n; + + store_u8 (_b, 0); /* It's not a string */ + store_u8 (_b, DNS_PAYLOAD_IS_SERVER_LIST); + store_u8 (_b, 1); /* Encoding version */ + store_u8 (_b, vls->source); + store_u8 (_b, vls->status); + store_u8 (_b, vls->nr_servers); + + for (i = 0; i < vls->nr_servers; i++) { + server = &vls->servers[i]; + + n = strlen(server->name); + store_u16(_b, n); + store_u16(_b, server->pref); + store_u16(_b, server->weight); + store_u16(_b, server->port); + store_u8 (_b, server->source); + store_u8 (_b, server->status); + store_u8 (_b, server->protocol); + store_u8 (_b, server->nr_addrs); + store_octets(_b, server->name, n); + + for (j = 0; j < server->nr_addrs; j++) { + addr = &server->addrs[j]; + + switch (addr->sin.sin_family) { + case AF_INET: + store_u8(_b, DNS_ADDRESS_IS_IPV4); + store_octets(_b, &addr->sin.sin_addr, 4); + break; + case AF_INET6: + store_u8(_b, DNS_ADDRESS_IS_IPV6); + store_octets(_b, &addr->sin6.sin6_addr, 16); + break; + default: + store_u8(_b, 0); + continue; + } + } + } +} + +void *kafs_generate_v1_payload(void *result, + const char *cell_name, + unsigned int *_ttl, + struct kafs_lookup_context *ctx) +{ + struct kafs_cell *cell; + unsigned char *b = result; + + ctx->report.what = cell_name; + cell = kafs_lookup_cell(cell_name, ctx); + if (!cell) + return NULL; + + if (_ttl) + *_ttl = cell->vlservers->ttl; + if (cell->vlservers) + emit_v1(&b, cell->vlservers); + return b; +} diff --git a/src/dns_main.c b/src/dns_main.c new file mode 100644 index 0000000..0580d3a --- /dev/null +++ b/src/dns_main.c @@ -0,0 +1,419 @@ +/* + * DNS Resolver Module User-space Helper for AFSDB records + * + * Copyright (C) Wang Lei (wang840925@gmail.com) 2010 + * Authors: Wang Lei (wang840925@gmail.com) + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * This is a userspace tool for querying AFSDB RR records in the DNS on behalf + * of the kernel, and converting the VL server addresses to IPv4 format so that + * they can be used by the kAFS filesystem. + * + * As some function like res_init() should use the static liberary, which is a + * bug of libresolv, that is the reason for cifs.upcall to reimplement. + * + * To use this program, you must tell /sbin/request-key how to invoke it. You + * need to have the keyutils package installed and something like the following + * lines added to your /etc/request-key.conf file: + * + * #OP TYPE DESCRIPTION CALLOUT INFO PROGRAM ARG1 ARG2 ARG3 ... + * ====== ============ =========== ============ ========================== + * create dns_resolver afsdb:* * /sbin/key.dns_resolver %k + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dns_afsdb.h" + +static const char *DNS_PARSE_VERSION = "2.0"; +static const char prog[] = "dns_afsdb"; +static const char key_type[] = "dns_resolver"; +static const char afsdb_query_type[] = "afsdb:"; +static key_serial_t key; +static int debug_mode; +static bool one_addr_only = true; +static unsigned int output_version = 0; + +/* + * Print an error to stderr or the syslog, negate the key being created and + * exit + */ +void error(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + if (isatty(2)) { + fputs("E: ", stderr); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + } else { + vsyslog(LOG_ERR, fmt, va); + } + va_end(va); + + /* + * on error, negatively instantiate the key ourselves so that we can + * make sure the kernel doesn't hang it off of a searchable keyring + * and interfere with the next attempt to instantiate the key. + */ + if (!debug_mode) + keyctl_negate(key, 1, KEY_REQKEY_DEFL_DEFAULT); + + exit(1); +} + +#define error(FMT, ...) error("Error: " FMT, ##__VA_ARGS__) + +/* + * Just print an error to stderr or the syslog + */ +static void print_error(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + if (isatty(2)) { + fputs("E: ", stderr); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + } else { + vsyslog(LOG_ERR, fmt, va); + } + va_end(va); +} + +/* + * Print status information + */ +static void verbose(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + if (isatty(1)) { + fputs("I: ", stdout); + vfprintf(stdout, fmt, va); + fputc('\n', stdout); + } else { + vsyslog(LOG_INFO, fmt, va); + } + va_end(va); +} + +/* + * Print usage details, + */ +static __attribute__((noreturn)) +void usage(void) +{ + if (isatty(2)) { + fprintf(stderr, "Usage: %s [OPTION]... \n", + prog); + fprintf(stderr, " %s -D [OPTION]... \n", + prog); + fprintf(stderr, " %s -V\n", + prog); + + fprintf(stderr, "\n"); + fprintf(stderr, "Where [OPTION].. is a combination of one or more of:\n"); + fprintf(stderr, "\t-c \n"); + fprintf(stderr, "\t-N dns\n"); + fprintf(stderr, "\t-N vls-afsdb\n"); + fprintf(stderr, "\t-N vls-srv\n"); + fprintf(stderr, "\t-N vls-all\n"); + fprintf(stderr, "\t-N vl-host\n"); + fprintf(stderr, "\t-o \n"); + fprintf(stderr, "\t-v\n"); + } else { + verbose("Usage: %s [-vv] ", prog); + } + exit(2); +} + +/* + * Parse the callout info string. + */ +static void parse_callout(char *options, struct kafs_lookup_context *ctx) +{ + char *k, *val; + + ctx->want_ipv4_addrs = true; + ctx->want_ipv6_addrs = true; + + if (!*options) { + /* legacy mode */ + ctx->want_ipv6_addrs = false; + return; + } + + do { + k = options; + options = strchr(options, ' '); + if (!options) + options = k + strlen(k); + else + *options++ = '\0'; + if (!*k) + continue; + if (strchr(k, ',')) + error("Option name '%s' contains a comma", k); + + val = strchr(k, '='); + if (val) + *val++ = '\0'; + + if (ctx->report.verbose) + ctx->report.verbose("Opt %s", k); + + if (strcmp(k, "ipv4") == 0) { + ctx->want_ipv4_addrs = true; + ctx->want_ipv6_addrs = false; + } else if (strcmp(k, "ipv6") == 0) { + ctx->want_ipv4_addrs = false; + ctx->want_ipv6_addrs = true; + } else if (strcmp(k, "list") == 0) { + one_addr_only = false; + } else if (strcmp(k, "srv") == 0) { + output_version = atoi(val); + } + } while (*options); +} + +const struct option long_options[] = { + { "conf", 0, NULL, 'c' }, + { "debug", 0, NULL, 'D' }, + { "no", 0, NULL, 'N' }, + { "output", 0, NULL, 'o' }, + { "verbose", 0, NULL, 'v' }, + { "version", 0, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; + +/* + * + */ +int main(int argc, char *argv[]) +{ + struct kafs_lookup_context ctx = { .report.error = print_error, }; + const char *dump_file = NULL; + const char *filev[10], **filep = NULL; + char *keyend, *p; + char *callout_info = NULL; + char *buf = NULL, *name, *result, *r_end; + unsigned int ttl; + size_t ktlen; + int ret, filec = 0; + + if (argc > 1 && strcmp(argv[1], "--help") == 0) + usage(); + + openlog(prog, 0, LOG_DAEMON); + + while ((ret = getopt_long(argc, argv, "Dvc:N:o:V:", long_options, NULL)) != -1) { + switch (ret) { + case 'c': + if (filec >= 9) { + fprintf(stderr, "Max 9 files\n"); + exit(2); + } + filev[filec++] = optarg; + break; + case 'D': + debug_mode = 1; + break; + case 'V': + printf("version: %s from %s (%s)\n", + DNS_PARSE_VERSION, + keyutils_version_string, + keyutils_build_string); + exit(0); + case 'v': + if (!ctx.report.verbose) + ctx.report.verbose = verbose; + else + ctx.report.verbose2 = verbose; + break; + case 'N': + if (strcmp(optarg, "vls-srv") == 0) { + ctx.no_vls_srv = true; + } else if (strcmp(optarg, "vls-afsdb") == 0) { + ctx.no_vls_afsdb = true; + } else if (strcmp(optarg, "vls-all") == 0) { + ctx.no_vls_srv = true; + ctx.no_vls_afsdb = true; + } else if (strcmp(optarg, "vl-host") == 0) { + ctx.no_vl_host = true; + } else if (strcmp(optarg, "dns") == 0) { + ctx.no_vls_srv = true; + ctx.no_vls_afsdb = true; + ctx.no_vl_host = true; + } else { + fprintf(stderr, "Unknown restriction '-N %s'\n", optarg); + usage(); + } + break; + case 'o': + dump_file = optarg; + break; + default: + if (!isatty(2)) + syslog(LOG_ERR, "unknown option: %c", ret); + usage(); + } + } + + argc -= optind; + argv += optind; + + if (!debug_mode) { + if (argc != 1) + usage(); + + /* get the key ID */ + if (!**argv) + error("Invalid blank key ID"); + key = strtol(*argv, &p, 10); + if (*p) + error("Invalid key ID format"); + + /* get the key description (of the form "x;x;x;x;:") */ + ret = keyctl_describe_alloc(key, &buf); + if (ret == -1) + error("keyctl_describe_alloc failed: %m"); + + /* get the callout_info (which can supply options) */ + ret = keyctl_read_alloc(KEY_SPEC_REQKEY_AUTH_KEY, (void **)&callout_info); + if (ret == -1) + error("Invalid key callout_info read: %m"); + } else { + if (argc != 2) + usage(); + + ret = asprintf(&buf, "%s;-1;-1;0;%s", key_type, argv[0]); + if (ret < 0) + error("Error %m"); + callout_info = argv[1]; + } + + ret = 1; + verbose("Key description: '%s'", buf); + verbose("Callout info: '%s'", callout_info); + + p = strchr(buf, ';'); + if (!p) + error("Badly formatted key description '%s'", buf); + ktlen = p - buf; + + /* make sure it's the type we are expecting */ + if (ktlen != sizeof(key_type) - 1 || + memcmp(buf, key_type, ktlen) != 0) + error("Key type is not supported: '%*.*s'", ktlen, ktlen, buf); + + keyend = buf + ktlen + 1; + + /* the actual key description follows the last semicolon */ + keyend = rindex(keyend, ';'); + if (!keyend) + error("Invalid key description: %s", buf); + keyend++; + + if (memcmp(keyend, afsdb_query_type, sizeof(afsdb_query_type) - 1) != 0) + error("Only 'afsdb' supported: %s", buf); + name = keyend + sizeof(afsdb_query_type) - 1; + + verbose("Do AFS VL server query for:'%s' mask:'%s'", name, callout_info); + + parse_callout(callout_info, &ctx); + + /* Anything we create must fit into 1MiB buffer */ + result = mmap(NULL, 1024 * 1024, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON, -1, 0); + if (result == MAP_FAILED) + error("mmap: %m"); + + if (filec > 0) { + filev[filec] = NULL; + filep = filev; + } + + if (kafs_init_lookup_context(&ctx) < 0) + exit(1); + + if (kafs_init_celldb(filep, &ctx.report) < 0) + exit(ctx.report.bad_config ? 3 : 1); + + /* Generate the payload */ + switch (output_version) { + case 0: + r_end = kafs_generate_text_payload(result, name, &ttl, &ctx); + break; + + case 1: + default: + r_end = kafs_generate_v1_payload(result, name, &ttl, &ctx); + break; + } + + if (!r_end) + error("failed"); + + verbose("version %u %zu", output_version, r_end - result); + + if (dump_file) { + int fd = open(dump_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd == -1) { + perror(dump_file); + exit(1); + } + + if (write(fd, result, r_end - result) != r_end - result) { + perror(dump_file); + exit(1); + } + close(fd); + } + + /* Set the key's expiry time from the minimum TTL encountered and then + * pass the data to the key. + */ + if (!debug_mode) { + if (ttl != UINT_MAX) { + ret = keyctl_set_timeout(key, ttl); + if (ret == -1) + error("keyctl_set_timeout: %m"); + } + + ret = keyctl_instantiate(key, result, r_end - result, 0); + if (ret == -1) + error("keyctl_instantiate: %m"); + } + + verbose("Success (%zu bytes)", r_end - result); + return 0; +} diff --git a/src/dns_resolver.h b/src/dns_resolver.h new file mode 100644 index 0000000..80af53a --- /dev/null +++ b/src/dns_resolver.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* DNS resolver interface definitions. + * + * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#ifndef _UAPI_LINUX_DNS_RESOLVER_H +#define _UAPI_LINUX_DNS_RESOLVER_H + +#include + +/* + * Type of payload. + */ +enum dns_payload_content_type { + DNS_PAYLOAD_IS_SERVER_LIST = 0, /* List of servers, requested by srv=1 */ +}; + +/* + * Type of address that might be found in an address record. + */ +enum dns_payload_address_type { + DNS_ADDRESS_IS_IPV4 = 0, /* 4-byte AF_INET address */ + DNS_ADDRESS_IS_IPV6 = 1, /* 16-byte AF_INET6 address */ +}; + +/* + * Type of protocol used to access a server. + */ +enum dns_payload_protocol_type { + DNS_SERVER_PROTOCOL_UNSPECIFIED = 0, + DNS_SERVER_PROTOCOL_UDP = 1, /* Use UDP to talk to the server */ + DNS_SERVER_PROTOCOL_TCP = 2, /* Use TCP to talk to the server */ +}; + +/* + * Source of record included in DNS resolver payload. + */ +enum dns_record_source { + DNS_RECORD_UNAVAILABLE = 0, /* No source available (empty record) */ + DNS_RECORD_FROM_CONFIG = 1, /* From local configuration data */ + DNS_RECORD_FROM_DNS_A = 2, /* From DNS A or AAAA record */ + DNS_RECORD_FROM_DNS_AFSDB = 3, /* From DNS AFSDB record */ + DNS_RECORD_FROM_DNS_SRV = 4, /* From DNS SRV record */ + DNS_RECORD_FROM_NSS = 5, /* From NSS */ + NR__dns_record_source +}; + +/* + * Status of record included in DNS resolver payload. + */ +enum dns_lookup_status { + DNS_LOOKUP_NOT_DONE = 0, /* No lookup has been made */ + DNS_LOOKUP_GOOD = 1, /* Good records obtained */ + DNS_LOOKUP_GOOD_WITH_BAD = 2, /* Good records, some decoding errors */ + DNS_LOOKUP_BAD = 3, /* Couldn't decode results */ + DNS_LOOKUP_GOT_NOT_FOUND = 4, /* Got a "Not Found" result */ + DNS_LOOKUP_GOT_LOCAL_FAILURE = 5, /* Local failure during lookup */ + DNS_LOOKUP_GOT_TEMP_FAILURE = 6, /* Temporary failure during lookup */ + DNS_LOOKUP_GOT_NS_FAILURE = 7, /* Name server failure */ + NR__dns_lookup_status +}; + +/* + * Header at the beginning of binary format payload. + */ +struct dns_payload_header { + __u8 zero; /* Zero byte: marks this as not being text */ + __u8 content; /* enum dns_payload_content_type */ + __u8 version; /* Encoding version */ +} __attribute__((packed)); + +/* + * Header at the beginning of a V1 server list. This is followed directly by + * the server records. Each server records begins with a struct of type + * dns_server_list_v1_server. + */ +struct dns_server_list_v1_header { + struct dns_payload_header hdr; + __u8 source; /* enum dns_record_source */ + __u8 status; /* enum dns_lookup_status */ + __u8 nr_servers; /* Number of server records following this */ +} __attribute__((packed)); + +/* + * Header at the beginning of each V1 server record. This is followed by the + * characters of the name with no NUL-terminator, followed by the address + * records for that server. Each address record begins with a struct of type + * struct dns_server_list_v1_address. + */ +struct dns_server_list_v1_server { + __u16 name_len; /* Length of name (LE) */ + __u16 priority; /* Priority (as SRV record) (LE) */ + __u16 weight; /* Weight (as SRV record) (LE) */ + __u16 port; /* UDP/TCP port number (LE) */ + __u8 source; /* enum dns_record_source */ + __u8 status; /* enum dns_lookup_status */ + __u8 protocol; /* enum dns_payload_protocol_type */ + __u8 nr_addrs; +} __attribute__((packed)); + +/* + * Header at the beginning of each V1 address record. This is followed by the + * bytes of the address, 4 for IPV4 and 16 for IPV6. + */ +struct dns_server_list_v1_address { + __u8 address_type; /* enum dns_payload_address_type */ +} __attribute__((packed)); + +#endif /* _UAPI_LINUX_DNS_RESOLVER_H */ diff --git a/src/include/kafs/cellserv.h b/src/include/kafs/cellserv.h new file mode 100644 index 0000000..c48acf0 --- /dev/null +++ b/src/include/kafs/cellserv.h @@ -0,0 +1,154 @@ +/* + * Cell server database parser. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ + +#ifndef _KAFS_CELLSERV_H +#define _KAFS_CELLSERV_H + +#include +#include +#include +#include "reporting.h" + +struct kafs_profile; +struct kafs_profile_parse; + +enum kafs_server_type { + kafs_server_is_untyped, + kafs_server_is_afs_vlserver, + kafs_server_is_afs_ptserver, +}; + +enum kafs_record_source { + kafs_record_unavailable, + kafs_record_from_config, + kafs_record_from_dns_a, + kafs_record_from_dns_afsdb, + kafs_record_from_dns_srv, + kafs_record_from_nss, + nr__kafs_record_source +}; + +enum kafs_lookup_status { + kafs_lookup_not_done, + kafs_lookup_good, + kafs_lookup_good_with_bad, + kafs_lookup_bad, + kafs_lookup_got_not_found, + kafs_lookup_got_local_failure, + kafs_lookup_got_temp_failure, + kafs_lookup_got_ns_failure, + nr__kafs_lookup_status +}; + +struct kafs_server_addr { + union { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + }; +}; + +struct kafs_server { + char *name; + struct kafs_server_addr *addrs; + unsigned int max_addrs; + unsigned int nr_addrs; + unsigned short port; + unsigned short pref; + unsigned short weight; + unsigned char protocol; + bool borrowed_name; + bool borrowed_addrs; + enum kafs_record_source source : 8; + enum kafs_lookup_status status : 8; + enum kafs_server_type type : 8; +}; + +struct kafs_server_list { + unsigned int nr_servers; + unsigned int max_servers; + unsigned int ttl; + enum kafs_record_source source : 8; + enum kafs_lookup_status status : 8; + struct kafs_server *servers; +}; + +struct kafs_cell { + char *name; + char *desc; + char *realm; + bool use_dns; + bool show_cell; + bool borrowed_name; + bool borrowed_desc; + bool borrowed_realm; + struct kafs_server_list *vlservers; +}; + +struct kafs_cell_db { + unsigned int nr_cells; + struct kafs_cell *cells[]; +}; + +struct kafs_lookup_context { + struct kafs_report report; + struct __res_state res; + bool want_ipv4_addrs; + bool want_ipv6_addrs; + bool no_vls_afsdb; + bool no_vls_srv; + bool no_vl_host; +}; + +/* + * object.c + */ +extern int kafs_init_lookup_context(struct kafs_lookup_context *ctx); +extern void kafs_clear_lookup_context(struct kafs_lookup_context *ctx); +extern struct kafs_server_list *kafs_alloc_server_list(struct kafs_report *report); +extern void kafs_free_server_list(struct kafs_server_list *sl); +extern void kafs_free_cell(struct kafs_cell *cell); +extern void kafs_transfer_addresses(struct kafs_server *to, + const struct kafs_server *from); +extern int kafs_transfer_server_list(struct kafs_server_list *to, + const struct kafs_server_list *from); +extern void kafs_transfer_cell(struct kafs_cell *to, + const struct kafs_cell *from); + +/* + * cellserv.c + */ +extern struct kafs_cell_db *kafs_cellserv_parse_conf(const struct kafs_profile *prof, + struct kafs_report *report); +extern void kafs_cellserv_dump(const struct kafs_cell_db *db); +extern const char *kafs_record_source(enum kafs_record_source source); +extern const char *kafs_lookup_status(enum kafs_lookup_status status); +extern void kafs_dump_cell(const struct kafs_cell *cell); + +/* + * dns_lookup.c + */ +extern int kafs_dns_lookup_addresses(struct kafs_server_list *sl, + struct kafs_lookup_context *ctx); +extern int kafs_dns_lookup_vlservers(struct kafs_server_list *vsl, + const char *cell_name, + struct kafs_lookup_context *ctx); + +/* + * cell_lookup.c + */ +extern struct kafs_cell_db *kafs_cellserv_db; +extern struct kafs_profile kafs_cellserv_profile; +extern int kafs_init_celldb(const char *const *files, + struct kafs_report *report); +extern struct kafs_cell *kafs_lookup_cell(const char *cell_name, + struct kafs_lookup_context *ctx); + +#endif /* _KAFS_CELLSERV_H */ diff --git a/src/include/kafs/profile.h b/src/include/kafs/profile.h new file mode 100644 index 0000000..8e7bb81 --- /dev/null +++ b/src/include/kafs/profile.h @@ -0,0 +1,154 @@ +/* + * Kerberos-style profile file parser. + * + * Copyright (C) 2018 David Howells (dhowells@redhat.com) + * + * 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. + */ + +#ifndef _KAFS_PROFILE_H +#define _KAFS_PROFILE_H + +#include +#include "reporting.h" + +enum kafs_profile_value_type { + kafs_profile_value_is_list, + kafs_profile_value_is_string, +}; + +struct kafs_profile { + enum kafs_profile_value_type type : 8; + bool final; + bool dummy; + unsigned int nr_relations; + unsigned int line; + const char *file; + char *name; + char *value; + struct kafs_profile *parent; + struct kafs_profile **relations; +}; + +extern void kafs_profile_dump(const struct kafs_profile *p, + unsigned int depth); +extern int kafs_profile_parse_file(struct kafs_profile *prof, + const char *filename, + struct kafs_report *report); +extern int kafs_profile_parse_dir(struct kafs_profile *prof, + const char *dirname, + struct kafs_report *report); +extern const struct kafs_profile * +kafs_profile_find_first_child(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + struct kafs_report *report); + +typedef int (*kafs_profile_iterator)(const struct kafs_profile *child, + void *data, + struct kafs_report *report); +extern int kafs_profile_iterate(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + kafs_profile_iterator iterator, + void *data, + struct kafs_report *report); +extern int kafs_profile_count(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + unsigned int *_nr); + +/* + * Constant matching. + */ +struct kafs_constant_table { + const char *name; + int value; +}; + +extern int kafs_lookup_constant2(const struct kafs_constant_table tbl[], + size_t tbl_size, + const char *name, + int not_found); +#define kafs_lookup_constant(t, n, nf) \ + kafs_lookup_constant2(t, sizeof(t)/sizeof(t[0]), (n), (nf)) + +extern int kafs_lookup_bool(const char *name, int not_found); + + +/* + * Convenience relation parsers. + */ +static inline const char *kafs_profile_get_string(const struct kafs_profile *prof, + const char *name, + struct kafs_report *report) +{ + const struct kafs_profile *p; + + p = kafs_profile_find_first_child(prof, kafs_profile_value_is_string, name, + report); + + return p ? p->value : NULL; +} + +static inline bool kafs_profile_get_bool(const struct kafs_profile *prof, + const char *name, + struct kafs_report *report) +{ + const struct kafs_profile *p; + int tmp; + + p = kafs_profile_find_first_child(prof, kafs_profile_value_is_string, name, + report); + if (!p || !p->value) + return false; + + tmp = kafs_lookup_bool(p->value, -1); + if (tmp == -1) { + report->error("%s:%u: Invalid bool value", p->file, p->line); + return false; + } + + return tmp; +} + +static inline int kafs_profile_iterate_list(const struct kafs_profile *prof, + const char *name, + kafs_profile_iterator iterator, + void *data, + struct kafs_report *report) +{ + return kafs_profile_iterate(prof, kafs_profile_value_is_list, + name, iterator, data, report); +} + +static inline int kafs_profile_count_list(const struct kafs_profile *prof, + const char *name, + unsigned int *_nr) +{ + return kafs_profile_count(prof, kafs_profile_value_is_list, + name, _nr); +} + +static inline int kafs_profile_iterate_strings(const struct kafs_profile *prof, + const char *name, + kafs_profile_iterator iterator, + void *data, + struct kafs_report *report) +{ + return kafs_profile_iterate(prof, kafs_profile_value_is_string, + name, iterator, data, report); +} + +static inline int kafs_profile_count_strings(const struct kafs_profile *prof, + const char *name, + unsigned int *_nr) +{ + return kafs_profile_count(prof, kafs_profile_value_is_string, + name, _nr); +} + +#endif /* _KAFS_PROFILE_H */ diff --git a/src/include/kafs/reporting.h b/src/include/kafs/reporting.h new file mode 100644 index 0000000..f3c1482 --- /dev/null +++ b/src/include/kafs/reporting.h @@ -0,0 +1,29 @@ +/* + * Error reporting context. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ + +#ifndef _KAFS_REPORTING_H +#define _KAFS_REPORTING_H + +struct kafs_report { + void (*error)(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + void (*verbose)(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + void (*verbose2)(const char *fmt, ...) + __attribute__((format(printf, 1, 2))); + const char *what; + int line; + bool bad_config; /* T if bad config encountered */ + bool bad_error; /* T if fatal system error encountered */ + bool abandon_alloc; /* T to not clean up on error */ +}; + +#endif /* _KAFS_REPORTING_H */ diff --git a/src/kafs-check-config.c b/src/kafs-check-config.c new file mode 100644 index 0000000..ddb5a70 --- /dev/null +++ b/src/kafs-check-config.c @@ -0,0 +1,156 @@ +/* + * kAFS filesystem configuration checker. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#include +#include +#include +#include +#include +#include +#include + +static void error_report(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + va_end(va); +} + +static void verbose(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + printf("[V] "); + vprintf(fmt, va); + putchar('\n'); + va_end(va); +} + +static __attribute__((noreturn)) +void usage(const char *prog) +{ + fprintf(stderr, + "Usage: %s [-46PDvv] [-c ]* [-N ] []*\n", + prog); + fprintf(stderr, "\n"); + fprintf(stderr, "Where restrictions are one or more of:\n"); + fprintf(stderr, "\t-N dns\n"); + fprintf(stderr, "\t-N vls-afsdb\n"); + fprintf(stderr, "\t-N vls-srv\n"); + fprintf(stderr, "\t-N vls-all\n"); + fprintf(stderr, "\t-N vl-host\n"); + exit(2); +} + +int main(int argc, char *argv[]) +{ + struct kafs_lookup_context ctx = { + .report.error = error_report, + .want_ipv4_addrs = true, + .want_ipv6_addrs = true, + }; + const char *filev[10], **filep = NULL; + bool dump_profile = false, dump_db = false; + int opt, filec = 0; + + if (argc > 1 && strcmp(argv[1], "--help") == 0) + usage(argv[0]); + + while (opt = getopt(argc, argv, "46PDc:vN:"), + opt != -1) { + switch (opt) { + case 'c': + if (filec >= 9) { + fprintf(stderr, "Max 9 files\n"); + exit(2); + } + filev[filec++] = optarg; + break; + case 'v': + if (!ctx.report.verbose) + ctx.report.verbose = verbose; + else + ctx.report.verbose2 = verbose; + break; + case 'P': + dump_profile = true; + break; + case 'D': + dump_db = true; + break; + case '4': + ctx.want_ipv4_addrs = true; + ctx.want_ipv6_addrs = false; + break; + case '6': + ctx.want_ipv4_addrs = false; + ctx.want_ipv6_addrs = true; + break; + case 'N': + if (strcmp(optarg, "vl-srv") == 0) { + ctx.no_vls_srv = true; + } else if (strcmp(optarg, "vl-afsdb") == 0) { + ctx.no_vls_afsdb = true; + } else if (strcmp(optarg, "vl-all") == 0) { + ctx.no_vls_srv = true; + ctx.no_vls_afsdb = true; + } else if (strcmp(optarg, "vl-host") == 0) { + ctx.no_vl_host = true; + } else if (strcmp(optarg, "dns") == 0) { + ctx.no_vls_srv = true; + ctx.no_vls_afsdb = true; + ctx.no_vl_host = true; + } else { + fprintf(stderr, "Unknown restriction '-N %s'\n", optarg); + usage(argv[0]); + } + break; + default: + usage(argv[0]); + } + } + + argc -= optind; + argv += optind; + + if (filec > 0) { + filev[filec] = NULL; + filep = filev; + } + + if (kafs_init_lookup_context(&ctx) < 0) + exit(1); + + if (kafs_init_celldb(filep, &ctx.report) < 0) + exit(ctx.report.bad_config ? 3 : 1); + + if (dump_profile) + kafs_profile_dump(&kafs_cellserv_profile, 0); + if (dump_db) + kafs_cellserv_dump(kafs_cellserv_db); + + for (; *argv; argv++) { + struct kafs_cell *cell; + + cell = kafs_lookup_cell(*argv, &ctx); + if (cell) { + printf("\n"); + printf("=== Found cell %s ===\n", cell->name); + kafs_dump_cell(cell); + } + } + + kafs_clear_lookup_context(&ctx); + return 0; +} diff --git a/src/lib_cell_lookup.c b/src/lib_cell_lookup.c new file mode 100644 index 0000000..6240bb6 --- /dev/null +++ b/src/lib_cell_lookup.c @@ -0,0 +1,218 @@ +/* + * Cell database access. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static const char *const kafs_std_cellservdb[] = { + ETCDIR "/kafs/cellservdb.conf", + NULL +}; + +struct kafs_cell_db *kafs_cellserv_db; +struct kafs_profile kafs_cellserv_profile = { .name = "" }; + +#define verbose(r, fmt, ...) \ + do { \ + if ((r)->verbose) \ + (r)->verbose(fmt, ## __VA_ARGS__); \ + } while(0) + +/* + * Allocate a cell record. + */ +struct kafs_cell *kafs_alloc_cell(const char *cell_name, + struct kafs_lookup_context *ctx) +{ + struct kafs_cell *cell; + + cell = calloc(1, sizeof(*cell)); + if (!cell) + goto error; + + cell->name = strdup(cell_name); + if (!cell->name) + goto error; + + return cell; + +error: + ctx->report.error("%m"); + return NULL; +} + +/* + * Initialise the cell database. + */ +int kafs_init_celldb(const char *const *files, struct kafs_report *report) +{ + + if (!files) + files = kafs_std_cellservdb; + + for (; *files; files++) + if (kafs_profile_parse_file(&kafs_cellserv_profile, *files, report) == -1) + return -1; + + kafs_cellserv_db = kafs_cellserv_parse_conf(&kafs_cellserv_profile, report); + if (!kafs_cellserv_db) + return -1; + + return 0; +} + +/* + * Deal with an unconfigured cell. + */ +static int kafs_unconfigured_cell(struct kafs_cell *cell, + struct kafs_lookup_context *ctx) +{ + struct kafs_server_list *vsl; + + verbose(&ctx->report, "%s: Cell not found in config", cell->name); + + vsl = kafs_alloc_server_list(&ctx->report); + if (!vsl) + return -1; + cell->vlservers = vsl; + + if (kafs_dns_lookup_vlservers(vsl, cell->name, ctx) < 0 || + kafs_dns_lookup_addresses(vsl, ctx) < 0) + return -1; + + verbose(&ctx->report, "DNS query AFSDB RR results:%u ttl:%u", + vsl->nr_servers, vsl->ttl); + return 0; +} + +/* + * Look up a cell in configuration and DNS. + * + * The rules are: + * + * (*) Look up the cell in the configuration first. + * + * (*) If there's no cell in the config, we have to try and build it entirely + * from the DNS. + * + * (*) Else: + * + * (*) We look at the configured no_dns setting: + * + * (*) If true, we use the list of servers listed in the config + * + * (*) If false, we try to replace that with one derived from the DNS. + * + * (*) For each server: + * + * (*) we try to look up a list of addresses in NSS/DNS. + * + * (*) If that fails, we use the list of addresses from the config. + */ +struct kafs_cell *kafs_lookup_cell(const char *cell_name, + struct kafs_lookup_context *ctx) +{ + const struct kafs_server_list *cvsl; + struct kafs_server_list *vsl; + const struct kafs_cell *conf_cell; + struct kafs_cell *cell; + unsigned int i, j; + + if (!kafs_cellserv_db && kafs_init_celldb(NULL, &ctx->report) < 0) + return NULL; + + cell = kafs_alloc_cell(cell_name, ctx); + if (!cell) + return NULL; + + for (i = 0; i < kafs_cellserv_db->nr_cells; i++) { + conf_cell = kafs_cellserv_db->cells[i]; + + if (strcmp(cell_name, conf_cell->name) == 0) + goto cell_is_configured; + } + + if (kafs_unconfigured_cell(cell, ctx) < 0) + goto error; + return cell; + + /* Deal with the case where we have a configuration. */ +cell_is_configured: + verbose(&ctx->report, "%s: Found cell in config", cell_name); + + kafs_transfer_cell(cell, conf_cell); + + vsl = kafs_alloc_server_list(&ctx->report); + if (!vsl) + goto error; + cell->vlservers = vsl; + + /* The DNS overrides the configuration if indicated. */ + if (conf_cell->use_dns) { + verbose(&ctx->report, "Query DNS for server list"); + if (kafs_dns_lookup_vlservers(vsl, cell_name, ctx) < 0) + goto error; + + verbose(&ctx->report, "Looked up %u VL servers [%s, %s]", + vsl->nr_servers, + kafs_lookup_status(vsl->status), + kafs_record_source(vsl->source)); + } + + /* If we didn't get any servers, copy the server list from the + * configuration. + */ + if (vsl->nr_servers == 0) { + verbose(&ctx->report, "Use configured server list"); + if (kafs_transfer_server_list(vsl, conf_cell->vlservers) < 0) + goto error; + } + + /* Try and look up addresses for all the servers in the list. */ + if (kafs_dns_lookup_addresses(vsl, ctx) < 0) + goto error; + + /* Borrow addresses from the config for any server that didn't find any + * in the DNS. + */ + cvsl = conf_cell->vlservers; + if (cvsl) { + for (i = 0; i < vsl->nr_servers; i++) { + struct kafs_server *srv = &vsl->servers[i]; + + if (srv->nr_addrs) + continue; + + verbose(&ctx->report, "Borrow addresses for '%s'", srv->name); + for (j = 0; j < cvsl->nr_servers; j++) { + const struct kafs_server *csrv = &cvsl->servers[j]; + + if (strcmp(srv->name, csrv->name) == 0) { + verbose(&ctx->report, "From '%s' %u", + csrv->name, csrv->nr_addrs); + kafs_transfer_addresses(srv, csrv); + break; + } + } + } + } + + return cell; + +error: + if (!ctx->report.abandon_alloc) + kafs_free_cell(cell); + return NULL; +} diff --git a/src/lib_cellserv.c b/src/lib_cellserv.c new file mode 100644 index 0000000..1419532 --- /dev/null +++ b/src/lib_cellserv.c @@ -0,0 +1,420 @@ +/* + * Parse the profile tree into a cell server database + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "dns_resolver.h" + +#define report_error(r, fmt, ...) \ + ({ \ + r->error(fmt, ## __VA_ARGS__); \ + -1; \ + }) + +#define parse_error(r, fmt, ...) \ + ({ \ + r->bad_config = true; \ + r->error("%s:%u: " fmt, r->what, r->line, ## __VA_ARGS__); \ + -1; \ + }) + +#define verbose(r, fmt, ...) \ + do { \ + if (r->verbose) \ + r->verbose(fmt, ## __VA_ARGS__); \ + } while(0) + +#define verbose2(r, fmt, ...) \ + do { \ + if (r->verbose2) \ + r->verbose2(fmt, ## __VA_ARGS__); \ + } while(0) + +/* + * Parse an address. + */ +static int cellserv_parse_address(const struct kafs_profile *child, + void *data, + struct kafs_report *report) +{ + struct kafs_server *server = data; + struct kafs_server_addr *addr = &server->addrs[server->nr_addrs]; + const char *v = child->value; + + if (server->nr_addrs >= server->max_addrs) { + report_error(report, "%s: Address list overrun", server->name); + return 0; + } + + if (inet_pton(AF_INET, v, &addr->sin.sin_addr) == 1) { + addr->sin.sin_family = AF_INET; + addr->sin.sin_port = htons(server->port); + server->nr_addrs++; + return 0; + } + + if (v[0] == '[') { + char *p; + + v++; + p = strchr(v, ']'); + if (!p || p[1]) + goto invalid; + p[0] = 0; + } + + if (inet_pton(AF_INET6, v, &addr->sin6.sin6_addr) == 1) { + addr->sin6.sin6_family = AF_INET6; + addr->sin6.sin6_port = htons(server->port); + server->nr_addrs++; + return 0; + } + +invalid: + parse_error(report, "%s:%u: Invalid address '%s'", + child->file, child->line, child->value); + return 0; +} + +/* + * Parse a server definition. + */ +static int cellserv_parse_server(const struct kafs_profile *child, + void *data, + struct kafs_report *report) +{ + struct kafs_server_list *vsl = data; + struct kafs_server *server = &vsl->servers[vsl->nr_servers]; + unsigned long tmp; + unsigned int max_addrs = 0; + const char *p; + char *q; + + if (vsl->nr_servers >= vsl->max_servers) { + report_error(report, "%s: Server list overrun", server->name); + return 0; + } + + memset(server, 0, sizeof(*server)); + server->source = kafs_record_from_config; + + /* Strip off any protocol indicator */ + server->name = child->name; + if (strncmp(server->name, "udp/", 4) == 0) { + server->protocol = DNS_SERVER_PROTOCOL_UDP; + server->name += 4; + } else if (strncmp(server->name, "tcp/", 4) == 0) { + server->protocol = DNS_SERVER_PROTOCOL_TCP; + server->name += 4; + } + + if (!server->name[0]) + return 0; + + /* Strip any port number and square brackets */ + if (server->name[0] == '[') { + server->name++; + p = strchr(server->name, ']'); + if (!*p) + return 0; + *(char *)p = 0; + p++; + if (*p) { + if (*p != ':') + return 0; + p++; + goto extract_port; + } + } + + /* Look for foo.com:port or 1.2.3.4:port, but dodge 1:2:3:4 */ + p = strchr(server->name, ':'); + if (!p) + goto no_port; + *(char *)p = 0; + p++; + if (strchr(p, ':')) + goto no_port; + +extract_port: + tmp = strtoul(p, &q, 0); + if (*q) + goto unparseable; + if (tmp > 65536) + goto unparseable; + server->port = tmp; +no_port: + + p = kafs_profile_get_string(child, "port", report); + if (p) { + tmp = strtoul(p, &q, 0); + if (*q) + goto unparseable; + if (tmp > 65536) + goto unparseable; + server->port = tmp; + } + + /* Generate a list of addresses */ + if (kafs_profile_count_strings(child, "address", &max_addrs) < 0) + return -1; + + server->addrs = calloc(max_addrs, sizeof(struct kafs_server)); + if (!server->addrs) + return -1; + server->max_addrs = max_addrs; + + if (kafs_profile_iterate_strings(child, "address", + cellserv_parse_address, server, + report) < 0) + return -1; + + p = kafs_profile_get_string(child, "type", report); + if (p) { + if (strcmp(p, "vlserver") == 0) + server->type = kafs_server_is_afs_vlserver; + else if (strcmp(p, "ptserver") == 0) + server->type = kafs_server_is_afs_ptserver; + else + fprintf(stderr, "Unknown type '%s'\n", p); + } + + vsl->nr_servers++; + return 0; + +unparseable: + parse_error(report, "%s:%u: Invalid address\n", child->file, child->line); + return 0; +} + +/* + * Find any Volume Location servers listed for a cell. + */ +static int kafs_cellserv_parse_vl(const struct kafs_profile *child, + struct kafs_cell *cell, + struct kafs_report *report) +{ + const struct kafs_profile *servers; + struct kafs_server_list *vsl; + unsigned int max_servers = 0; + + /* Find any Volume Location servers listed for that cell */ + servers = kafs_profile_find_first_child(child, kafs_profile_value_is_list, + "servers", report); + if (!servers) { + verbose(report, "%s: No servers list", child->name); + return 0; + } + + if (kafs_profile_count_list(servers, NULL, &max_servers) < 0) + return -1; + + vsl = calloc(1, sizeof(*vsl)); + if (!vsl) + return -1; + vsl->source = kafs_record_from_config; + + cell->vlservers = vsl; + vsl->servers = calloc(max_servers, sizeof(struct kafs_server)); + if (!vsl->servers) + return -1; + + vsl->max_servers = max_servers; + return kafs_profile_iterate_list(servers, NULL, cellserv_parse_server, + vsl, report); +} + +/* + * Parse a cell definition. + */ +static int kafs_cellserv_parse_cell(const struct kafs_profile *child, + void *data, + struct kafs_report *report) +{ + struct kafs_cell_db *db = data; + struct kafs_cell *cell; + + cell = calloc(1, sizeof(*cell)); + if (!cell) + return -1; + cell->name = child->name; + cell->show_cell = kafs_profile_get_bool(child, "show_cell", report); + cell->use_dns = kafs_profile_get_bool(child, "use_dns", report); + cell->desc = (char *)kafs_profile_get_string(child, "description", report); + cell->realm = (char *)kafs_profile_get_string(child, "kerberos_realm", report); + cell->borrowed_name = true; + cell->borrowed_desc = true; + cell->borrowed_realm = true; + + verbose2(report, "CELL: %s: %s", cell->name, cell->desc); + db->cells[db->nr_cells] = cell; + db->nr_cells++; + + return kafs_cellserv_parse_vl(child, cell, report); +} + +/* + * Extract cell information from a kafs_profile parse tree. + */ +struct kafs_cell_db *kafs_cellserv_parse_conf(const struct kafs_profile *prof, + struct kafs_report *report) +{ + const struct kafs_profile *cells; + struct kafs_cell_db *db; + unsigned int nr_cells = 0; + + cells = kafs_profile_find_first_child(prof, kafs_profile_value_is_list, "cells", report); + if (!cells) { + report_error(report, "Cannot find [cells] section"); + return NULL; + } + + if (kafs_profile_count_list(cells, NULL, &nr_cells) < 0) + return NULL; + + db = calloc(1, sizeof(*db) + nr_cells * sizeof(struct kafs_cell *)); + if (!db) + return NULL; + if (!nr_cells) + return db; + + if (kafs_profile_iterate_list(cells, NULL, + kafs_cellserv_parse_cell, db, + report) == -1) + return NULL; + + return db; +} + +static const char *const kafs_record_sources[nr__kafs_record_source] = { + [kafs_record_unavailable] = "unavailable", + [kafs_record_from_config] = "config", + [kafs_record_from_dns_a] = "A", + [kafs_record_from_dns_afsdb] = "AFSDB", + [kafs_record_from_dns_srv] = "SRV", + [kafs_record_from_nss] = "nss", +}; + +static const char *const kafs_lookup_statuses[nr__kafs_lookup_status] = { + [kafs_lookup_not_done] = "no-lookup", + [kafs_lookup_good] = "good", + [kafs_lookup_good_with_bad] = "good/bad", + [kafs_lookup_bad] = "bad", + [kafs_lookup_got_not_found] = "not-found", + [kafs_lookup_got_local_failure] = "local-failure", + [kafs_lookup_got_temp_failure] = "temp-failure", + [kafs_lookup_got_ns_failure] = "ns-failure", +}; + +const char *kafs_record_source(enum kafs_record_source source) +{ + if (source >= nr__kafs_record_source) + return "unknown"; + return kafs_record_sources[source] ?: "unknown"; +} + +const char *kafs_lookup_status(enum kafs_lookup_status status) +{ + if (status >= nr__kafs_lookup_status) + return "unknown"; + return kafs_lookup_statuses[status] ?: "unknown"; +} + +/* + * Dump a server set. + */ +void kafs_dump_server_list(const struct kafs_server_list *sl, + const char *server_type) +{ + unsigned int j, k; + const char *p; + char buf[100]; + + for (j = 0; j < sl->nr_servers; j++) { + const struct kafs_server *srv = &sl->servers[j]; + + printf(" - %s %s [%s; %s]\n", + server_type, srv->name, + kafs_lookup_status(srv->status), + kafs_record_source(srv->source)); + + if (srv->type) + printf(" - %s\n", + srv->type == kafs_server_is_afs_vlserver ? + "VLServer" : "PTServer"); + if (srv->protocol) + printf(" - %s\n", + srv->protocol == DNS_SERVER_PROTOCOL_UDP ? "udp" : "tcp"); + if (srv->port || srv->pref || srv->weight) + printf(" - port %u, pref %u, weight %u\n", + srv->port, srv->pref, srv->weight); + + for (k = 0; k < srv->nr_addrs; k++) { + const struct kafs_server_addr *addr = &srv->addrs[k]; + + switch (addr->sin.sin_family) { + case AF_INET: + p = inet_ntop(AF_INET, &addr->sin.sin_addr, + buf, sizeof(buf)); + break; + case AF_INET6: + p = inet_ntop(AF_INET6, &addr->sin6.sin6_addr, + buf, sizeof(buf)); + break; + default: + p = NULL; + break; + } + + if (p) + printf(" - address %s\n", p); + } + } +} + +/* + * Dump a cell. + */ +void kafs_dump_cell(const struct kafs_cell *cell) +{ + const struct kafs_server_list *vsl = cell->vlservers; + + if (!cell->use_dns) + printf(" - use-dns=no\n"); + if (!cell->show_cell) + printf(" - show-cell=no\n"); + + if (vsl) { + printf(" - status: %s, from %s\n", + kafs_lookup_status(vsl->status), + kafs_record_source(vsl->source)); + kafs_dump_server_list(vsl, "VLSERVER"); + } +} + +/* + * Dump the parsed afs database. + */ +void kafs_cellserv_dump(const struct kafs_cell_db *db) +{ + unsigned int i; + + for (i = 0; i < db->nr_cells; i++) { + const struct kafs_cell *cell = db->cells[i]; + + printf("CELL %s\n", cell->name); + kafs_dump_cell(cell); + } +} diff --git a/src/lib_dns_lookup.c b/src/lib_dns_lookup.c new file mode 100644 index 0000000..d1b7852 --- /dev/null +++ b/src/lib_dns_lookup.c @@ -0,0 +1,559 @@ +/* + * Build a cell VL address set based on DNS records. + * + * Copyright (C) Wang Lei (wang840925@gmail.com) 2010 + * Authors: Wang Lei (wang840925@gmail.com) + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * This is a userspace tool for querying AFSDB RR records in the DNS on behalf + * of the kernel, and converting the VL server addresses to IPv4 format so that + * they can be used by the kAFS filesystem. + * + * As some function like res_init() should use the static liberary, which is a + * bug of libresolv, that is the reason for cifs.upcall to reimplement. + * + * To use this program, you must tell /sbin/request-key how to invoke it. You + * need to have the keyutils package installed and something like the following + * lines added to your /etc/request-key.conf file: + * + * #OP TYPE DESCRIPTION CALLOUT INFO PROGRAM ARG1 ARG2 ARG3 ... + * ====== ============ =========== ============ ========================== + * create dns_resolver afsdb:* * /sbin/key.dns_resolver %k + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "dns_resolver.h" + +#define AFS_VL_PORT 7003 /* volume location service port */ + +#define verbose(fmt, ...) \ + do { \ + if (ctx->report.verbose) \ + ctx->report.verbose(fmt, ## __VA_ARGS__); \ + } while(0) + +/* + * Perform address resolution on a hostname and add the resulting address as a + * string to the list of payload segments. + */ +static int kafs_resolve_addrs(struct kafs_server *server, + int socktype, + struct kafs_lookup_context *ctx) +{ + struct kafs_server_addr *addr; + struct addrinfo hints, *addrs, *ai; + int ret, count = 0; + + verbose("Resolve '%s'", server->name); + + server->source = kafs_record_from_nss; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = socktype; + if (ctx->want_ipv4_addrs && !ctx->want_ipv6_addrs) + hints.ai_family = AF_INET; + else if (ctx->want_ipv6_addrs && !ctx->want_ipv4_addrs) + hints.ai_family = AF_INET6; + + /* resolve name to ip */ + ret = getaddrinfo(server->name, NULL, &hints, &addrs); + if (ret) { + verbose("%s: getaddrinfo() = %d", server->name, ret); + switch (ret) { + case EAI_MEMORY: + case EAI_SYSTEM: + ctx->report.error("%s: getaddrinfo(): %m", server->name); + goto system_error; + case EAI_FAMILY: + case EAI_SOCKTYPE: + ctx->report.bad_error = true; + server->status = kafs_lookup_got_local_failure; + goto fail; + default: + server->status = kafs_lookup_got_local_failure; + /* Fall through. */ + fail: + ctx->report.error("%s: %s", server->name, gai_strerror(ret)); + return 0; + case EAI_FAIL: +#ifdef EAI_NODATA + case EAI_NODATA: +#endif + case EAI_NONAME: + case EAI_SERVICE: + server->status = kafs_lookup_got_not_found; + goto fail; + case EAI_AGAIN: + server->status = kafs_lookup_got_temp_failure; + goto fail; + } + } + + for (ai = addrs; ai; ai = ai->ai_next) + count++; + + server->addrs = calloc(count, sizeof(*addr)); + if (!server->addrs) { + ctx->report.error("%m"); + goto system_error; + } + + server->max_addrs = count; + server->source = kafs_record_from_nss; + server->status = kafs_lookup_good; + + for (ai = addrs; ai; ai = ai->ai_next) { + addr = &server->addrs[server->nr_addrs]; + + verbose("RR: %x,%x,%x,%x,%x,%s", + ai->ai_flags, ai->ai_family, + ai->ai_socktype, ai->ai_protocol, + ai->ai_addrlen, ai->ai_canonname); + + /* convert address to string */ + switch (ai->ai_family) { + case AF_INET: + if (!ctx->want_ipv4_addrs) + continue; + memcpy(&addr->sin, (struct sockaddr_in *)ai->ai_addr, + sizeof(addr->sin)); + server->nr_addrs++; + break; + case AF_INET6: + if (!ctx->want_ipv6_addrs) + continue; + memcpy(&addr->sin6, (struct sockaddr_in *)ai->ai_addr, + sizeof(addr->sin6)); + server->nr_addrs++; + break; + default: + verbose("Address of unknown family %u", ai->ai_family); + continue; + } + } + + freeaddrinfo(addrs); + return 0; + +system_error: + ctx->report.bad_error = true; + return -1; +} + +/* + * Go through all the servers records and look up addresses for them. + */ +int kafs_dns_lookup_addresses(struct kafs_server_list *ss, + struct kafs_lookup_context *ctx) +{ + struct kafs_server *server; + unsigned int i; + int ret; + + if (ss) { + verbose("NR_SERVERS %u", ss->nr_servers); + + if (ctx->no_vl_host) { + verbose("Use of DNS for FS server lookup is disabled."); + return 0; + } + + for (i = 0; i < ss->nr_servers; i++) { + server = &ss->servers[i]; + + /* Turn the hostname into IP addresses */ + ret = kafs_resolve_addrs(server, SOCK_DGRAM, ctx); + if (ret) + verbose("AFSDB RR can't resolve. subtype:1, server name:%s", + server->name); + else + verbose("NR_ADDRS %u", server->nr_addrs); + } + } + + return 0; +} + +/* + * Convert the outcome of an AFSDB record lookup into a set of server records. + */ +static int kafs_parse_afsdb(struct kafs_server_list *vsl, + const char *cell_name, + unsigned short subtype, + ns_msg handle, + ns_sect section, + struct kafs_lookup_context *ctx) +{ + struct kafs_server *server; + unsigned int rr_ttl, max_servers = 0, i; + ns_rr rr; + char buf[MAXDNAME]; + int rrnum, rr_subtype; + + verbose("AFSDB RR count is %d", ns_msg_count(handle, section)); + + /* Count the number of afsdb records */ + for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) { + if (ns_parserr(&handle, section, rrnum, &rr)) { + ctx->report.error("%s: afsdb parse failed", cell_name); + continue; + } + + if (ns_rr_type(rr) != ns_t_afsdb) + continue; + rr_subtype = ns_get16(ns_rr_rdata(rr)); + if (rr_subtype != subtype) + continue; + max_servers++; + } + + verbose("NR_SERVER %u", max_servers); + + vsl->max_servers = max_servers; + vsl->servers = calloc(max_servers, sizeof(struct kafs_server)); + if (!vsl->servers) + goto system_error; + + /* Look at all the resource records in this section. */ + for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) { + server = &vsl->servers[vsl->nr_servers]; + + /* Expand the resource record number rrnum into rr. */ + if (ns_parserr(&handle, section, rrnum, &rr)) { + ctx->report.error("%s: afsdb parse failed", cell_name); + vsl->status = kafs_lookup_bad; + continue; + } + + /* We're only interested in AFSDB records */ + if (ns_rr_type(rr) != ns_t_afsdb) + continue; + rr_subtype = ns_get16(ns_rr_rdata(rr)); + if (rr_subtype != subtype) + continue; + + /* Expand the name server's domain name */ + if (ns_name_uncompress(ns_msg_base(handle), + ns_msg_end(handle), + ns_rr_rdata(rr) + 2, + buf, + MAXDNAME) < 0) { + ctx->report.error("%s: afsdb uncompress failed", cell_name); + vsl->status = kafs_lookup_bad; + continue; + } + + rr_ttl = ns_rr_ttl(rr); + if (vsl->ttl > rr_ttl) + vsl->ttl = rr_ttl; + + /* Check the domain name we've just unpacked and add it to + * the list of VL servers if it is not a duplicate. + * If it is a duplicate, just ignore it. + */ + for (i = 0; i < vsl->nr_servers; i++) + if (strcasecmp(buf, vsl->servers[i].name) == 0) + continue; + + server->name = strdup(buf); + if (!server->name) + goto system_error; + server->port = AFS_VL_PORT; + server->protocol = DNS_SERVER_PROTOCOL_UDP; + + verbose("SERVER[%u] %s", vsl->nr_servers, server->name); + vsl->nr_servers++; + } + + if (vsl->nr_servers > 0 && vsl->status == kafs_lookup_bad) + vsl->status = kafs_lookup_good_with_bad; + if (vsl->nr_servers == 0 && vsl->status == kafs_lookup_good_with_bad) + vsl->status = kafs_lookup_got_not_found; + + return 0; + +system_error: + ctx->report.bad_error = true; + ctx->report.error("%m"); + return -1; +} + +/* + * Look up an AFSDB record to get the VL server addresses. + */ +static int dns_query_AFSDB(struct kafs_server_list *vsl, + const char *cell_name, + unsigned short subtype, + struct kafs_lookup_context *ctx) +{ + int response_len; /* buffer length */ + ns_msg handle; /* handle for response message */ + union { + HEADER hdr; + u_char buf[NS_PACKETSZ]; + } response; /* response buffers */ + + vsl->source = kafs_record_from_dns_afsdb; + vsl->status = kafs_lookup_good; + + verbose("Get AFSDB RR for cell name:'%s'", cell_name); + + /* query the dns for an AFSDB resource record */ + response_len = res_nquery(&ctx->res, + cell_name, + ns_c_in, + ns_t_afsdb, + response.buf, + sizeof(response)); + + if (response_len < 0) { + ctx->report.error("%s: %s", cell_name, hstrerror(h_errno)); + switch (h_errno) { + case HOST_NOT_FOUND: + case NO_DATA: + default: + vsl->status = kafs_lookup_got_not_found; + break; + case NO_RECOVERY: + vsl->status = kafs_lookup_got_ns_failure; + break; + case TRY_AGAIN: + vsl->status = kafs_lookup_got_temp_failure; + break; + } + return 0; + } + + if (ns_initparse(response.buf, response_len, &handle) < 0) { + ctx->report.error("%s: ns_initparse: %s", + cell_name, hstrerror(h_errno)); + vsl->status = kafs_lookup_bad; + return 0; + } + + /* look up the hostnames we've obtained to get the actual addresses */ + return kafs_parse_afsdb(vsl, cell_name, subtype, handle, ns_s_an, ctx); +} + +/* + * Convert the outcome of an SRV record lookup into a set of server records. + */ +static int kafs_parse_srv(struct kafs_server_list *vsl, + const char *domain_name, + ns_msg handle, + ns_sect section, + enum dns_payload_protocol_type protocol, + struct kafs_lookup_context *ctx) +{ + struct kafs_server *server; + unsigned int max_servers = 0, rr_ttl, i; + ns_rr rr; + char buf[MAXDNAME]; + int rrnum; + + verbose("SRV RR count is %d", ns_msg_count(handle, section)); + + /* Count the number of srv records */ + for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) { + if (ns_parserr(&handle, section, rrnum, &rr)) { + ctx->report.error("%s: ns_parserr", domain_name); + continue; + } + + if (ns_rr_type(rr) != ns_t_srv) + continue; + max_servers++; + } + + verbose("NR_SERVER %u", max_servers); + + vsl->max_servers = max_servers; + vsl->servers = calloc(max_servers, sizeof(struct kafs_server)); + if (!vsl->servers) + goto system_error; + + for (rrnum = 0; rrnum < ns_msg_count(handle, section); rrnum++) { + server = &vsl->servers[vsl->nr_servers]; + + /* Expand the resource record number rrnum into rr. */ + if (ns_parserr(&handle, section, rrnum, &rr)) { + ctx->report.error("%s: ns_parserr", domain_name); + vsl->status = kafs_lookup_bad; + continue; + } + + if (ns_rr_type(rr) != ns_t_srv) + continue; + + ns_get16(ns_rr_rdata(rr)); /* subtype */ + + /* Expand the name server's domain name */ + if (ns_name_uncompress(ns_msg_base(handle), + ns_msg_end(handle), + ns_rr_rdata(rr) + 6, + buf, + MAXDNAME) < 0) { + ctx->report.error("%s: ns_name_uncompress", domain_name); + vsl->status = kafs_lookup_bad; + continue; + } + + rr_ttl = ns_rr_ttl(rr); + if (vsl->ttl > rr_ttl) + vsl->ttl = rr_ttl; + + server->pref = ns_get16(ns_rr_rdata(rr)); + server->weight = ns_get16(ns_rr_rdata(rr) + 2); + server->port = ns_get16(ns_rr_rdata(rr) + 4); + verbose("rdata %u %u %u", server->pref, server->weight, server->port); + + /* Check the domain name we've just unpacked and add it to + * the list of VL servers if it is not a duplicate. + * If it is a duplicate, just ignore it. + */ + for (i = 0; i < vsl->nr_servers; i++) + if (strcasecmp(buf, vsl->servers[i].name) == 0) + continue; + + server->name = strdup(buf); + if (!server->name) + goto system_error; + server->port = AFS_VL_PORT; + server->protocol = protocol; + + verbose("SERVER[%u] %s", vsl->nr_servers, server->name); + vsl->nr_servers++; + } + + if (vsl->nr_servers > 0 && vsl->status == kafs_lookup_bad) + vsl->status = kafs_lookup_good_with_bad; + if (vsl->nr_servers == 0 && vsl->status == kafs_lookup_good_with_bad) + vsl->status = kafs_lookup_got_not_found; + + return 0; + +system_error: + ctx->report.bad_error = true; + ctx->report.error("%m"); + return -1; +} + +/* + * Look up an SRV record to get the VL server addresses [RFC 5864]. + */ +static int dns_query_SRV(struct kafs_server_list *vsl, + const char *domain_name, + const char *service_name, + const char *proto_name, + struct kafs_lookup_context *ctx) +{ + int response_len; /* buffer length */ + ns_msg handle; /* handle for response message */ + union { + HEADER hdr; + u_char buf[NS_PACKETSZ]; + } response; + enum dns_payload_protocol_type protocol; + char name[1024]; + + vsl->source = kafs_record_from_dns_srv; + + snprintf(name, sizeof(name), "_%s._%s.%s", + service_name, proto_name, domain_name); + + verbose("Get SRV RR for name:'%s'", name); + + response_len = res_nquery(&ctx->res, + name, + ns_c_in, + ns_t_srv, + response.buf, + sizeof(response)); + + if (response_len < 0) { + ctx->report.error("%s: dns: %s", + domain_name, hstrerror(h_errno)); + switch (h_errno) { + case HOST_NOT_FOUND: + case NO_DATA: + vsl->status = kafs_lookup_got_not_found; + break; + case NO_RECOVERY: + vsl->status = kafs_lookup_got_ns_failure; + break; + case TRY_AGAIN: + vsl->status = kafs_lookup_got_temp_failure; + break; + } + return 0; + } + + if (ns_initparse(response.buf, response_len, &handle) < 0) { + ctx->report.error("%s: ns_initparse: %s", + domain_name, hstrerror(h_errno)); + vsl->status = kafs_lookup_bad; + return 0; + } + + if (strcmp(proto_name, "udp") == 0) + protocol = DNS_SERVER_PROTOCOL_UDP; + else if (strcmp(proto_name, "tcp") == 0) + protocol = DNS_SERVER_PROTOCOL_TCP; + else + protocol = DNS_SERVER_PROTOCOL_UNSPECIFIED; + + vsl->status = kafs_lookup_good; + return kafs_parse_srv(vsl, domain_name, handle, ns_s_an, protocol, ctx); +} + +/* + * Look up a cell by name in the DNS. + */ +int kafs_dns_lookup_vlservers(struct kafs_server_list *vsl, + const char *cell_name, + struct kafs_lookup_context *ctx) +{ + int ret; + + vsl->status = kafs_lookup_not_done; + + if (!ctx->no_vls_srv) { + ret = dns_query_SRV(vsl, cell_name, "afs3-vlserver", "udp", ctx); + if (ret == 0 && vsl->nr_servers > 0) + return 0; + } else { + verbose("Use of DNS/SRV for VL server lookup is disabled."); + } + + if (!ctx->no_vls_afsdb) { + ret = dns_query_AFSDB(vsl, cell_name, 1, ctx); + if (ret == 0 && vsl->nr_servers > 0) + return 0; + } else { + verbose("Use of DNS/AFSDB for VL server lookup is disabled."); + } + + return 0; +} diff --git a/src/lib_object.c b/src/lib_object.c new file mode 100644 index 0000000..3fad06a --- /dev/null +++ b/src/lib_object.c @@ -0,0 +1,164 @@ +/* + * Object creation/destruction. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +/* + * Initialise state in a lookup context. + */ +int kafs_init_lookup_context(struct kafs_lookup_context *ctx) +{ + memset(&ctx->res, 0, sizeof(ctx->res)); + if (res_ninit(&ctx->res) < 0) { + ctx->report.bad_error = true; + ctx->report.error("%m"); + return -1; + } + return 0; +} + +/* + * Clear state in a lookup context. + */ +void kafs_clear_lookup_context(struct kafs_lookup_context *ctx) +{ + res_nclose(&ctx->res); +} + +/* + * Allocate a blank server list. + */ +struct kafs_server_list *kafs_alloc_server_list(struct kafs_report *report) +{ + struct kafs_server_list *sl; + + sl = calloc(1, sizeof(*sl)); + if (!sl) { + report->bad_error = true; + report->error("%m"); + return NULL; + } + + sl->ttl = UINT_MAX; + return sl; +} + +/* + * Free a server list. + */ +void kafs_free_server_list(struct kafs_server_list *sl) +{ + unsigned int i; + + if (sl->servers) { + for (i = 0; i < sl->nr_servers; i++) { + struct kafs_server *s = &sl->servers[i]; + if (!s->borrowed_name) + free(s->name); + if (!s->borrowed_addrs) + free(s->addrs); + } + free(sl->servers); + } + + free(sl); +} + +/* + * Free a cell. + */ +void kafs_free_cell(struct kafs_cell *cell) +{ + if (!cell->borrowed_name) free(cell->name); + if (!cell->borrowed_desc) free(cell->desc); + if (!cell->borrowed_realm) free(cell->realm); + + if (cell->vlservers) + kafs_free_server_list(cell->vlservers); + + free(cell); +} + +/* + * Transfer the addresses from one server to another. + */ +void kafs_transfer_addresses(struct kafs_server *to, + const struct kafs_server *from) +{ + to->max_addrs = 0; + to->nr_addrs = from->nr_addrs; + to->addrs = from->addrs; + to->borrowed_addrs = true; +} + +/* + * Transfer the list of servers from one server list to another. + */ +int kafs_transfer_server_list(struct kafs_server_list *to, + const struct kafs_server_list *from) +{ + unsigned int i, nr = from->nr_servers; + + to->nr_servers = nr; + to->max_servers = from->max_servers; + to->ttl = from->ttl; + + if (nr == 0) { + to->servers = NULL; + return 0; + } + + to->servers = malloc(nr * sizeof(struct kafs_server)); + if (!to->servers) + return -1; + + memcpy(to->servers, from->servers, nr * sizeof(struct kafs_server)); + for (i = 0; i < nr; i++) { + struct kafs_server *s = &to->servers[i]; + + s->borrowed_name = true; + s->max_addrs = 0; + s->nr_addrs = 0; + s->addrs = NULL; + } + + return 0; +} + +/* + * Transfer information from one cell record to another. + */ +void kafs_transfer_cell(struct kafs_cell *to, const struct kafs_cell *from) +{ + if (!to->name) { + to->name = from->name; + to->borrowed_name = true; + } + + if (from->desc) { + to->desc = from->desc; + to->borrowed_desc = true; + } + + if (from->realm) { + to->realm = from->realm; + to->borrowed_realm = true; + } + + to->use_dns = from->use_dns; + to->show_cell = from->show_cell; +} diff --git a/src/lib_profile.c b/src/lib_profile.c new file mode 100644 index 0000000..469ddc0 --- /dev/null +++ b/src/lib_profile.c @@ -0,0 +1,512 @@ +/* + * Kerberos-style profile file parser. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define report_error(r, fmt, ...) \ + ({ \ + r->error(fmt, ## __VA_ARGS__); \ + -1; \ + }) + +#define parse_error(r, fmt, ...) \ + ({ \ + r->bad_config = true; \ + r->error("%s:%u: " fmt, r->what, r->line, ## __VA_ARGS__); \ + -1; \ + }) + +/* + * Dump a profile to stdout in tree form. + */ +void kafs_profile_dump(const struct kafs_profile *p, unsigned int depth) +{ + unsigned int i; + + if (p->type == kafs_profile_value_is_list) { + printf("%*s [*] '%s'%s\n", + depth, "", + p->name, + p->final ? " [final]" : ""); + for (i = 0; i < p->nr_relations; i++) + kafs_profile_dump(p->relations[i], depth + 2); + } else { + printf("%*s [=] '%s' = '%s'\n", depth, "", p->name, p->value); + } +} + +/* + * Find/create relation in the list to which we're contributing. + * + * If a list relation is already closed then it is replaced unless it is final, + * in which case the new stuff is ignored. + */ +static struct kafs_profile *kafs_profile_get_relation(struct kafs_profile *parent, + char *name, + enum kafs_profile_value_type type, + struct kafs_report *report) +{ + struct kafs_profile *r, **list = parent->relations; + bool dummy = false; + int i, n = parent->nr_relations; + + if (parent->type != kafs_profile_value_is_list) { + report->error("%s:%u: Can't insert into a non-list", + report->what, report->line); + return NULL; + } + + if (type == kafs_profile_value_is_list) { + for (i = 0; i < n; i++) { + r = list[i]; + if (r->type != kafs_profile_value_is_list || + strcmp(r->name, name) != 0) + continue; + + if (r->final) { + dummy = true; + goto create; + } + + r->final |= parent->final; + return r; + } + } + +create: + r = malloc(sizeof(*r)); + if (!r) + return NULL; + + memset(r, 0, sizeof(*r)); + r->type = type; + r->name = name; + r->parent = parent; + r->dummy = dummy | parent->final | parent->dummy; + + if (!r->dummy) { + list = realloc(list, sizeof(*list) * (n + 1)); + if (!list) + return NULL; + + list[n] = r; + parent->relations = list; + parent->nr_relations = n + 1; + } + + return r; +} + +/* + * Parse the contents of a kafs_profile file. + */ +static int kafs_profile_parse_content(struct kafs_profile *prof, const char *file, + char *p, char *end, + struct kafs_report *report) +{ + struct kafs_profile *section = NULL, *list = NULL, *tmp; + unsigned int line = 0; + char *eol, *next_line = p, *key, *value; + bool at_left; + +next_line: + p = next_line; + line++; + report->line = line; + at_left = p < end && !isblank(*p); + while (p < end && isblank(*p)) p++; + if (p == end) + return 0; + eol = strpbrk(p, "\n\r"); + if (!eol) { + next_line = eol = end; + } else { + next_line = eol + 1; + if (next_line < end && *next_line != *eol && + (*next_line == '\n' || *next_line == '\r')) + next_line++; /* handle CRLF and LFCR */ + while (eol > p && isblank(eol[-1])) + eol--; + *eol = 0; + } + + if (!*p || p[0] == '#' || p[0] == ';') + goto next_line; + + /* Deal with section markers. */ + if (list == section && p[0] == '[') { + if (eol - p < 3 || eol[-1] != ']') + return parse_error(report, "Bad section label"); + p++; + eol--; + *eol = 0; + if (strchr(p, ']')) + return parse_error(report, "Bad section label"); + + section = kafs_profile_get_relation(prof, p, kafs_profile_value_is_list, + report); + if (!section) + return -1; + section->file = file; + section->line = line; + list = section; + goto next_line; + } + + /* Things before the first section are either comments or inclusion + * directives. + */ + if (!section) { + if (!at_left || strncmp(p, "include", 7) != 0) + goto next_line; + p += 7; + + if (isblank(*p)) { + /* It's an include directive */ + while (*p && isblank(*p)) p++; + if (!*p) + return parse_error(report, "No include path"); + + if (kafs_profile_parse_file(prof, p, report) < 0) + return -1; + } + + if (strncmp(p, "dir", 3) == 0 && (isblank(p[3]) || !p[3])) { + /* It's an includedir directive */ + p += 3; + while (*p && isblank(*p)) p++; + if (!*p) + return parse_error(report, "No includedir path"); + + if (kafs_profile_parse_dir(prof, p, report) < 0) + return -1; + } + + goto next_line; + } + + /* Deal with the closure of a list */ + if (p[0] == '}') { + if (list == section) + return parse_error(report, "Unmatched '}'"); + p++; + if (p[0] == '*') { + list->final = true; + p++; + } + if (*p) + return parse_error(report, "Unexpected stuff after '}'"); + + tmp = list; + list = list->parent; + if (tmp->dummy) + free(tmp); + goto next_line; + } + + /* Everything else should be a relation specifier of one of the + * following forms: + * + * x = y + * x = { .. } + */ + key = p; + p = strchr(p, '='); + if (!p) + return parse_error(report, "Missing '=' in relation"); + if (p == key) + return parse_error(report, "Anonymous key in relation"); + + value = p + 1; + while (value < eol && isblank(*value)) value++; + p--; + while (p > key && isblank(p[-1])) + p--; + *p = 0; + + /* Handle the opening of a new list-type relation */ + if (value[0] == '{') { + if (value[1]) + return parse_error(report, "Unexpected stuff after '{'"); + + list = kafs_profile_get_relation(list, key, kafs_profile_value_is_list, + report); + if (!list) + return -1; + list->file = file; + list->line = line; + goto next_line; + } + + /* Handle a relation with a quoted-string value */ + if (value[0] == '"') { + char *q; + + value++; + if (eol <= value || eol[-1] != '"') + return parse_error(report, "Unterminated string"); + eol--; + eol[0] = 0; + + /* Substitute for all the escape chars in place */ + for (p = q = value; p < eol;) { + char ch = *p++; + if (ch == '\\') { + if (p >= eol) + return parse_error(report, "Uncompleted '\\' escape"); + + ch = *p++; + switch (ch) { + case 'n': ch = '\n'; break; + case 't': ch = '\t'; break; + case 'b': ch = '\b'; break; + } + } + *q++ = ch; + } + + *q = 0; + } + + tmp = kafs_profile_get_relation(list, key, kafs_profile_value_is_string, report); + if (!tmp) + return -1; + tmp->file = file; + tmp->line = line; + tmp->value = value; + goto next_line; +} + +/* + * Parse a kafs_profile file. + */ +int kafs_profile_parse_file(struct kafs_profile *prof, const char *file, + struct kafs_report *report) +{ + const char *old_file = report->what; + struct stat st; + ssize_t n; + char *buffer; + int fd, ret; + + report->what = file; + fd = open(file, O_RDONLY); + if (fd == -1) + return -1; + + if (fstat(fd, &st) == -1) { + close(fd); + return -1; + } + + buffer = malloc(st.st_size + 1); + if (!buffer) { + close(fd); + return -1; + } + + n = read(fd, buffer, st.st_size); + close(fd); + if (n == -1) { + free(buffer); + return -1; + } + buffer[n] = 0; + + ret = kafs_profile_parse_content(prof, file, buffer, buffer + n, report); + if (ret == 0) + report->what = old_file; + return ret; +} + +/* + * Parse a kafs_profile directory. + */ +int kafs_profile_parse_dir(struct kafs_profile *prof, + const char *dirname, + struct kafs_report *report) +{ + const char *old_file = report->what; + struct dirent *de; + char *filename; + DIR *dir; + int ret, n; + + report->what = dirname; + report->line = 0; + dir = opendir(dirname); + if (!dir) + return report_error(report, "%s: %m", dirname); + + while (errno = 0, + (de = readdir(dir))) { + if (de->d_name[0] == '.') + continue; + n = strlen(de->d_name); + if (n < 1 || de->d_name[n - 1] == '~') + continue; + + if (asprintf(&filename, "%s/%s", dirname, de->d_name) == -1) { + closedir(dir); + return report_error(report, "%m"); + } + + ret = kafs_profile_parse_file(prof, filename, report); + if (ret < 0) { + closedir(dir); + return -1; + } + } + + report->what = dirname; + closedir(dir); + if (errno != 0) + return -1; + report->what = old_file; + return 0; +} + +/* + * Find the first child object of a type and name in the list attached to the + * given object. + */ +const struct kafs_profile *kafs_profile_find_first_child(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + struct kafs_report *report) +{ + unsigned int i; + + if (prof->type != kafs_profile_value_is_list) { + report_error(report, "Trying to find '%s' in relation '%s'", + name, prof->name); + return NULL; + } + + for (i = 0; i < prof->nr_relations; i++) { + const struct kafs_profile *r = prof->relations[i]; + + if (r->type == type && + strcmp(r->name, name) == 0) + return r; + } + + return NULL; +} + +/* + * Iterate over all the child objects of the given type and name in the list + * attached to the given object until the iterator function returns non-zero. + */ +int kafs_profile_iterate(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + kafs_profile_iterator iterator, + void *data, + struct kafs_report *report) +{ + unsigned int i; + int ret; + + if (prof->type != kafs_profile_value_is_list) { + report_error(report, "Trying to iterate over relation '%s'", + prof->name); + return -1; + } + + for (i = 0; i < prof->nr_relations; i++) { + const struct kafs_profile *r = prof->relations[i]; + + if (r->type != type) + continue; + if (name && strcmp(r->name, name) != 0) + continue; + ret = iterator(r, data, report); + if (ret) + return ret; + } + + return 0; +} + +static int kafs_count_objects(const struct kafs_profile *child, + void *data, + struct kafs_report *report) +{ + unsigned int *_nr = data; + + *_nr += 1; + return 0; +} + +/* + * Count the number of matching children of an object. + */ +int kafs_profile_count(const struct kafs_profile *prof, + enum kafs_profile_value_type type, + const char *name, + unsigned int *_nr) +{ + return kafs_profile_iterate(prof, type, NULL, kafs_count_objects, _nr, NULL); +} + +static int cmp_constant(const void *name, const void *entry) +{ + const struct kafs_constant_table *e = entry; + return strcasecmp(name, e->name); +} + +/* + * Turn a string into a constant. + */ +int kafs_lookup_constant2(const struct kafs_constant_table *tbl, size_t tbl_size, + const char *name, int not_found) +{ + const struct kafs_constant_table *e; + + e = bsearch(name, tbl, tbl_size, sizeof(tbl[0]), cmp_constant); + if (!e) + return not_found; + return e->value; +} + +static const struct kafs_constant_table bool_names[] = { + { "0", false }, + { "1", true }, + { "f", false }, + { "false", false }, + { "n", false }, + { "no", false }, + { "off", false }, + { "on", true }, + { "t", true }, + { "true", true }, + { "y", true }, + { "yes", true }, +}; + +/* + * Parse a string as a bool constant. + */ +int kafs_lookup_bool(const char *name, int not_found) +{ + return kafs_lookup_constant(bool_names, name, not_found); +} diff --git a/src/preload-cells.c b/src/preload-cells.c new file mode 100644 index 0000000..e0edc6a --- /dev/null +++ b/src/preload-cells.c @@ -0,0 +1,154 @@ +/* + * Cell preloader for kAFS filesystem. + * + * Copyright (C) David Howells (dhowells@redhat.com) 2018 + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void verbose(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + vprintf(fmt, va); + putchar('\n'); + va_end(va); +} + +/* + * Just print an error to stderr or the syslog + */ +static void _error(const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + if (isatty(2)) { + vfprintf(stderr, fmt, va); + fputc('\n', stderr); + } else { + vsyslog(LOG_ERR, fmt, va); + } + va_end(va); +} + +/* + * Parse the cell database file + */ +int do_preload(const struct kafs_cell_db *db, bool redirect_to_stdout) +{ + unsigned int i; + char buf[4096]; + int fd; + + if (!redirect_to_stdout) { + fd = open("/proc/fs/afs/cells", O_WRONLY); + if (fd == -1) { + _error("Can't open /proc/fs/afs/cells: %m"); + exit(1); + } + } else { + fd = 1; + } + + for (i = 0; i < db->nr_cells; i++) { + const struct kafs_cell *cell = db->cells[i]; + int n; + + n = snprintf(buf, sizeof(buf) - 1, "add %s", cell->name); + if (write(fd, buf, n) != n) { + if (errno != EEXIST) { + _error("Can't add cell '%s': %m", cell->name); + exit(1); + } + + verbose("%s: Already exists", cell->name); + } + if (redirect_to_stdout) + if (write(1, "\n", 1) == -1) + perror("stdout"); + } + + if (!redirect_to_stdout) { + if (close(fd) == -1) { + _error("Can't close /proc/fs/afs/cells: %m"); + exit(1); + } + } + + exit(0); +} + +static __attribute__((noreturn)) +void usage(const char *prog) +{ + fprintf(stderr, "Usage: %s [-Dv] [*]\n", prog); + exit(2); +} + +int main(int argc, char *argv[]) +{ + struct kafs_report report = {}; + const char *const *files; + bool redirect_to_stdout = false; + int opt; + + if (argc > 1 && strcmp(argv[1], "--help") == 0) + usage(argv[0]); + + while (opt = getopt(argc, argv, "Dv"), + opt != -1) { + switch (opt) { + case 'D': + redirect_to_stdout = true; + break; + case 'v': + if (!report.verbose) + report.verbose = verbose; + else + report.verbose2 = verbose; + break; + default: + usage(argv[0]); + break; + } + } + + argc -= optind; + argv += optind; + + files = NULL; + if (argc > 0) + files = (const char **)argv; + + if (kafs_init_celldb(files, &report) < 0) + exit(3); + + do_preload(kafs_cellserv_db, redirect_to_stdout); + return 0; +} diff --git a/src/version.lds b/src/version.lds new file mode 100644 index 0000000..49160db --- /dev/null +++ b/src/version.lds @@ -0,0 +1,28 @@ +KAFS_CLIENT_0.1 { + kafs_alloc_cell; + kafs_alloc_server_list; + kafs_cellserv_dump; + kafs_cellserv_parse_conf; + kafs_cellserv_profile; + kafs_clear_lookup_context; + kafs_dns_lookup_addresses; + kafs_dns_lookup_vlservers; + kafs_dump_cell; + kafs_dump_server_list; + kafs_free_cell; + kafs_free_server_list; + kafs_init_celldb; + kafs_init_lookup_context; + kafs_lookup_bool; + kafs_lookup_cell; + kafs_lookup_constant2; + kafs_profile_count; + kafs_profile_dump; + kafs_profile_find_first_child; + kafs_profile_iterate; + kafs_profile_parse_dir; + kafs_profile_parse_file; + kafs_transfer_addresses; + kafs_transfer_cell; + kafs_transfer_server_list; +}; -- 2.49.0