]> www.infradead.org Git - users/dhowells/kafs-client.git/commitdiff
Add a utility library and number of utility programs
authorDavid Howells <dhowells@redhat.com>
Thu, 20 Sep 2018 11:33:51 +0000 (12:33 +0100)
committerDavid Howells <dhowells@redhat.com>
Fri, 12 Oct 2018 15:50:43 +0000 (16:50 +0100)
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 <dhowells@redhat.com>
25 files changed:
.gitignore
Makefile
conf/afs.mount
conf/kafs-config.service [new file with mode: 0644]
conf/kafs_dns.conf [new file with mode: 0644]
redhat/kafs-client.spec
src/Makefile
src/Makefile.config [new file with mode: 0644]
src/aklog-kafs.c
src/dns_afsdb.h [new file with mode: 0644]
src/dns_afsdb_text.c [new file with mode: 0644]
src/dns_afsdb_v1.c [new file with mode: 0644]
src/dns_main.c [new file with mode: 0644]
src/dns_resolver.h [new file with mode: 0644]
src/include/kafs/cellserv.h [new file with mode: 0644]
src/include/kafs/profile.h [new file with mode: 0644]
src/include/kafs/reporting.h [new file with mode: 0644]
src/kafs-check-config.c [new file with mode: 0644]
src/lib_cell_lookup.c [new file with mode: 0644]
src/lib_cellserv.c [new file with mode: 0644]
src/lib_dns_lookup.c [new file with mode: 0644]
src/lib_object.c [new file with mode: 0644]
src/lib_profile.c [new file with mode: 0644]
src/preload-cells.c [new file with mode: 0644]
src/version.lds [new file with mode: 0644]

index 34b39932775f59c72c704e651d10ef493be3c973..47d6dd958597a03c0ae5ce22305309569c0de866 100644 (file)
@@ -1 +1,7 @@
 aklog-kafs
+kafs-check-config
+kafs-preload
+kafs-dns
+*.o
+*.os
+libkafs_client.so*
index 56f70f02c2804d8e6f428edece6cf0ae9a5b147b..3f9b55cdf97db8dc4ce07137d48e553b2339e305 100644 (file)
--- 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
 
 ###############################################################################
 #
index 8e72726997520f0f1e314729d99c1b96af16932b..082efd7dd19ac848795651f004e6e0c68f713a8c 100644 (file)
@@ -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 (file)
index 0000000..f837e33
--- /dev/null
@@ -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 (file)
index 0000000..7e16d79
--- /dev/null
@@ -0,0 +1 @@
+create  dns_resolver afsdb:*    *               /usr/libexec/kafs-dns %k
index 66faa2c647e2a5dd25461fac96b9e86563fc43fc..db77f6d4720b3d48e6e34e17efce0836d59125a5 100644 (file)
@@ -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
index 1edabfaa36c06c2706de577ec4f0e6b9d45798fb..16365b7871480718769e664a6f7b83d1ebe5299d 100644 (file)
@@ -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 (file)
index 0000000..7f4004a
--- /dev/null
@@ -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
index 8e4cd47c67757d00797cc8e340a382a763d49a3c..27e4dc2867cbb6db4a4965250e9db125d1161b3b 100644 (file)
@@ -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 (file)
index 0000000..83f3a71
--- /dev/null
@@ -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 (file)
index 0000000..b1911ed
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <kafs/cellserv.h>
+#include <arpa/inet.h>
+#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 (file)
index 0000000..93c6077
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <kafs/cellserv.h>
+#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 (file)
index 0000000..0580d3a
--- /dev/null
@@ -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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <keyutils.h>
+#include <sys/mman.h>
+#include <kafs/cellserv.h>
+#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]... <key_serial>\n",
+                       prog);
+               fprintf(stderr, "       %s -D [OPTION]... <desc> <calloutinfo>\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 <conffile>\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 <dumpfile>\n");
+               fprintf(stderr, "\t-v\n");
+       } else {
+               verbose("Usage: %s [-vv] <key_serial>", 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;<query_type>:<name>") */
+               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 (file)
index 0000000..80af53a
--- /dev/null
@@ -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 <linux/types.h>
+
+/*
+ * 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 (file)
index 0000000..c48acf0
--- /dev/null
@@ -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 <stdbool.h>
+#include <resolv.h>
+#include <netinet/in.h>
+#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 (file)
index 0000000..8e7bb81
--- /dev/null
@@ -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 <stdbool.h>
+#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 (file)
index 0000000..f3c1482
--- /dev/null
@@ -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 (file)
index 0000000..ddb5a70
--- /dev/null
@@ -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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <kafs/cellserv.h>
+#include <kafs/profile.h>
+
+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 <conffile>]* [-N <restriction>] [<cellname>]*\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 (file)
index 0000000..6240bb6
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <errno.h>
+#include <kafs/cellserv.h>
+#include <kafs/profile.h>
+
+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 = "<cellservdb>" };
+
+#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 (file)
index 0000000..1419532
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <kafs/cellserv.h>
+#include <kafs/profile.h>
+#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 (file)
index 0000000..d1b7852
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <resolv.h>
+#include <netdb.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <kafs/cellserv.h>
+#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 (file)
index 0000000..3fad06a
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <arpa/inet.h>
+#include <kafs/cellserv.h>
+#include <kafs/profile.h>
+
+/*
+ * 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 (file)
index 0000000..469ddc0
--- /dev/null
@@ -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 <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <kafs/profile.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;                                                     \
+       })
+
+/*
+ * 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 (file)
index 0000000..e0edc6a
--- /dev/null
@@ -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 <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <arpa/inet.h>
+#include <kafs/profile.h>
+#include <kafs/cellserv.h>
+
+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] [<dbfile>*]\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 (file)
index 0000000..49160db
--- /dev/null
@@ -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;
+};