From 7e77aebf8e945918f4cfd50b938bb25c2ba3367c Mon Sep 17 00:00:00 2001 From: anton Date: Wed, 9 May 2018 19:34:54 +0000 Subject: Add rebound regress tests, including a simple DNS server used as the upstream resolver with support for stubbing responses. Input and ok bluhm@ --- regress/usr.sbin/rebound/Makefile | 53 +++++++ regress/usr.sbin/rebound/cache.sh | 32 ++++ regress/usr.sbin/rebound/localhost.sh | 30 ++++ regress/usr.sbin/rebound/rebound-ns.c | 270 ++++++++++++++++++++++++++++++++++ regress/usr.sbin/rebound/record.sh | 75 ++++++++++ regress/usr.sbin/rebound/reload.sh | 54 +++++++ regress/usr.sbin/rebound/run.sh | 90 ++++++++++++ 7 files changed, 604 insertions(+) create mode 100644 regress/usr.sbin/rebound/Makefile create mode 100644 regress/usr.sbin/rebound/cache.sh create mode 100644 regress/usr.sbin/rebound/localhost.sh create mode 100644 regress/usr.sbin/rebound/rebound-ns.c create mode 100644 regress/usr.sbin/rebound/record.sh create mode 100644 regress/usr.sbin/rebound/reload.sh create mode 100644 regress/usr.sbin/rebound/run.sh (limited to 'regress') diff --git a/regress/usr.sbin/rebound/Makefile b/regress/usr.sbin/rebound/Makefile new file mode 100644 index 00000000000..2c738c2c972 --- /dev/null +++ b/regress/usr.sbin/rebound/Makefile @@ -0,0 +1,53 @@ +# $OpenBSD: Makefile,v 1.1 2018/05/09 19:34:53 anton Exp $ + +REBOUND= /usr/sbin/rebound + +PROG= rebound-ns +CFLAGS+= -Wall -Wextra + +# IP address prefix. +PREFIX= 172.16.0 +# IP address suffixes. +IFS= 1 2 3 + +.if ! (make(clean) || make(cleandir) || make(obj) || make(rebound-ns)) +PREREQ1!= for v in ${IFS}; do \ + ifconfig lo$$v >/dev/null 2>&1 && echo lo$$v; \ + done; \ + exit 0 +PREREQ2!= pgrep rebound || exit 0 + +. if ! empty(PREREQ1) +regress: + @echo "${PREREQ1}: interface(s) already exists" + @echo "SKIPPED" +. elif ! empty(PREREQ2) +regress: + @echo "rebound: already running" + @echo "SKIPPED" +. else +.BEGIN: +. for i in ${IFS} + -${SUDO} ifconfig lo${i} create + -${SUDO} ifconfig lo${i} inet ${PREFIX}.${i} netmask 255.255.255.0 +. endfor + +.END: +. for i in ${IFS} + -${SUDO} ifconfig lo${i} destroy +. endfor +. endif +.endif + +REGRESS_TARGETS+= cache localhost record reload + +.SUFFIXES: .sh + +.sh: rebound-ns + @echo '\n======== ${@} ========' + ${SUDO} sh ${.CURDIR}/run.sh \ + -n ${.OBJDIR}/rebound-ns -r ${REBOUND} \ + ${IFS:C/^/-a ${PREFIX}./} \ + -- ${.CURDIR}/${@}.sh + +.include diff --git a/regress/usr.sbin/rebound/cache.sh b/regress/usr.sbin/rebound/cache.sh new file mode 100644 index 00000000000..9315abb45cf --- /dev/null +++ b/regress/usr.sbin/rebound/cache.sh @@ -0,0 +1,32 @@ +#!/bin/sh +# +# $OpenBSD: cache.sh,v 1.1 2018/05/09 19:34:53 anton Exp $ +# +# Copyright (c) 2018 Anton Lindqvist +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +echo nameserver $ADDR1 >$CONF + +$ENV $NS -l $ADDR1 A t1.example.com. 1.1.1.1 + +$ENV $REBOUND -c $CONF -l $ADDR2 + +R=$(resolve -t A t1.example.com $ADDR2) +asserteq "t1.example.com has address 1.1.1.1" "$R" \ + "must be resolved using rebound-ns" + +pkillw "^${NS}" + +R=$(resolve -t A t1.example.com $ADDR2) +asserteq "t1.example.com has address 1.1.1.1" "$R" "must be cached by rebound" diff --git a/regress/usr.sbin/rebound/localhost.sh b/regress/usr.sbin/rebound/localhost.sh new file mode 100644 index 00000000000..6bc2483c348 --- /dev/null +++ b/regress/usr.sbin/rebound/localhost.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# $OpenBSD: localhost.sh,v 1.1 2018/05/09 19:34:53 anton Exp $ +# +# Copyright (c) 2018 Anton Lindqvist +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +cat <$CONF +nameserver 127.0.0.1 +nameserver ${ADDR1} +! + +$ENV $NS -l $ADDR1 A t1.example.com. 1.1.1.1 + +$ENV $REBOUND -c $CONF -l $ADDR2 + +R=$(resolve -t A t1.example.com $ADDR2) +asserteq "t1.example.com has address 1.1.1.1" "$R" \ + "must be resolved using rebound-ns since localhost is skipped" diff --git a/regress/usr.sbin/rebound/rebound-ns.c b/regress/usr.sbin/rebound/rebound-ns.c new file mode 100644 index 00000000000..4cca4dbb970 --- /dev/null +++ b/regress/usr.sbin/rebound/rebound-ns.c @@ -0,0 +1,270 @@ +/* $OpenBSD: rebound-ns.c,v 1.1 2018/05/09 19:34:53 anton Exp $ */ +/* + * Copyright (c) 2018 Anton Lindqvist + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct dnsheader { + uint16_t id; + uint16_t flags; + uint16_t qdcount; + uint16_t ancount; + uint16_t nscount; + uint16_t arcount; +}; + +union dnsmsg { + struct dnsheader *hdr; + uint8_t *u8; + uint16_t *u16; + uint32_t *u32; +}; + +struct dnsrecord { + unsigned char req[256]; + size_t reqlen; + unsigned char resp[256]; + size_t resplen; +}; + +static const struct dnsrecord *lookup(struct dnsrecord *, size_t, union dnsmsg, + size_t); +static const unsigned char *lowercase(union dnsmsg, size_t *); +static void newdnsrecord(struct dnsrecord *, const char *, const char *, + const char *); +static __dead void usage(void); + +int +main(int argc, char *argv[]) +{ +#define NRECORDS 32 + static struct dnsrecord records[NRECORDS]; + unsigned char recvbuf[65536]; + union { + struct sockaddr a; + struct sockaddr_in i; + } bindaddr, fromaddr; + const char *bindname = "127.0.0.1"; + const struct dnsrecord *resp; + union dnsmsg req; + socklen_t fromlen; + ssize_t n; + size_t nrecords = 0; + int c, fd; + + while ((c = getopt(argc, argv, "l:")) != -1) + switch (c) { + case 'l': + bindname = optarg; + break; + default: + usage(); + } + argc -= optind; + argv += optind; + if (argc == 0 || argc % 3 > 0) + usage(); + + for (; argc > 0; argc -= 3, argv += 3) { + if (nrecords == NRECORDS) + errx(1, "too many arguments"); + newdnsrecord(records + nrecords, argv[0], argv[1], argv[2]); + nrecords++; + } + + memset(&bindaddr, 0, sizeof(bindaddr)); + bindaddr.i.sin_len = sizeof(bindaddr.i); + bindaddr.i.sin_family = AF_INET; + bindaddr.i.sin_port = htons(53); + inet_aton(bindname, &bindaddr.i.sin_addr); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd == -1) + err(1, "socket"); + if (bind(fd, &bindaddr.a, bindaddr.a.sa_len) == -1) + err(1, "bind"); + + if (daemon(0, 1) == -1) + err(1, "daemon"); + + req.u8 = recvbuf; + for (;;) { + fromlen = sizeof(fromaddr.a); + n = recvfrom(fd, req.u8, sizeof(recvbuf), 0, &fromaddr.a, + &fromlen); + if (n == -1) { + warn("recvfrom"); + break; + } + resp = lookup(records, nrecords, req, n); + sendto(fd, resp->resp, resp->resplen, 0, &fromaddr.a, fromlen); + } + close(fd); + + return 0; +} + +static __dead void +usage(void) +{ + fprintf(stderr, "usage: ns [-l addr] type name rddata ...\n"); + exit(1); +} + +static const struct dnsrecord * +lookup(struct dnsrecord *records, size_t nrecords, union dnsmsg req, + size_t reqlen) +{ + static struct dnsrecord resp; + union dnsmsg dns; + const unsigned char *qname; + size_t i, qnamelen; + uint16_t id; + + qname = lowercase(req, &qnamelen); + id = req.hdr->id; + req.hdr->id = 0; + + for (i = 0; i < nrecords; i++) { + if (records[i].reqlen != reqlen) + continue; + dns.u8 = records[i].req; + if (memcmp(dns.hdr, req.hdr, reqlen) == 0) { + memcpy(resp.resp, records[i].resp, records[i].resplen); + resp.resplen = records[i].resplen; + dns.u8 = resp.resp; + dns.hdr->id = id; + memcpy(dns.hdr + 1, qname, qnamelen); + return &resp; + } + } + + resp.resplen = sizeof(struct dnsheader); + memset(resp.resp, 0, sizeof(resp.resp)); + dns.u8 = resp.resp; + dns.hdr->id = id; + dns.hdr->flags = htons(0x8000 | 0x3); /* QR | NXDOMAIN */ + return &resp; +} + +static const unsigned char * +lowercase(union dnsmsg dns, size_t *retlen) +{ + static unsigned char buf[256]; + size_t i; + size_t len = 0; + + dns.hdr++; /* move to qname */ + for (;;) { + if (*dns.u8 == '\0') + break; + i = buf[len++] = *dns.u8++; /* label length */ + for (; i > 0; i--) { + buf[len++] = *dns.u8; + *dns.u8 = tolower(*dns.u8); + dns.u8++; + } + } + *retlen = len; + return buf; +} + +static void +newdnsrecord(struct dnsrecord *record, const char *type, const char *qname, + const char *rddata) +{ + union dnsmsg beg, dns; + const char *label; + int qtype; + + if (strcmp(type, "A") == 0) + qtype = 1; + else + errx(1, "%s: unknown type", type); + + /* question header */ + dns.u8 = beg.u8 = record->req; + dns.hdr->flags = htons(0x100); /* RD */ + dns.hdr->qdcount = htons(1); + + /* question message */ + dns.hdr++; + + /* question name */ + for (;;) { + label = strchr(qname, '.'); + if (label == NULL) + break; + *dns.u8++ = label - qname; + for (; qname < label; qname++) + *dns.u8++ = *qname; + qname = label + 1; + } + *dns.u8++ = '\0'; + + /* question type */ + *dns.u16++ = htons(qtype); + + /* question class */ + *dns.u16++ = htons(1); + + record->reqlen = dns.u8 - beg.u8; + + /* response header */ + memcpy(record->resp, record->req, record->reqlen); + dns.u8 = beg.u8 = record->resp; + dns.hdr->flags = htons(0x8000 | 0x100); /* QR | RD */ + dns.hdr->ancount = htons(1); + + /* response message */ + dns.u8 += record->reqlen; + + /* response name with compression */ + *dns.u16++ = htons(0xc000 | sizeof(struct dnsheader)); + + /* response type */ + *dns.u16++ = htons(qtype); + + /* response class */ + *dns.u16++ = htons(1); + + /* response ttl */ + *dns.u32++ = htonl(3600); + + /* response rdlength */ + *dns.u16++ = htons(4); + + /* response rddata */ + for (;;) { + for (; isdigit(*rddata); rddata++) + *dns.u8 = *dns.u8 * 10 + (*rddata - '0'); + dns.u8++; + if (*rddata++ == '\0') + break; + } + + record->resplen = dns.u8 - beg.u8; +} diff --git a/regress/usr.sbin/rebound/record.sh b/regress/usr.sbin/rebound/record.sh new file mode 100644 index 00000000000..8d2dc7a2b52 --- /dev/null +++ b/regress/usr.sbin/rebound/record.sh @@ -0,0 +1,75 @@ +#!/bin/sh +# +# $OpenBSD: record.sh,v 1.1 2018/05/09 19:34:53 anton Exp $ +# +# Copyright (c) 2018 Anton Lindqvist +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +cat <$CONF +nameserver ${ADDR1} +# Unknown type. +record MX t1.example.com. 1.1.1.1 +# Missing trailing dot. +record A t1.example.com 1.1.1.1 +# Name too short. +record A a 1.1.1.1 +! + +$ENV $REBOUND -c $CONF -l $ADDR2 + +R=$(resolve -W 1 -t MX t1.example.com $ADDR2) +asserteq ";; connection timed out; no servers could be reached" "$R" \ + "must not be resolved by rebound" + +R=$(resolve -W 1 -t A t1.example.com $ADDR2) +asserteq ";; connection timed out; no servers could be reached" "$R" \ + "must not be resolved by rebound" + +R=$(resolve -W 1 -t A a $ADDR2) +asserteq ";; connection timed out; no servers could be reached" "$R" \ + "must not be resolved by rebound" + +pkillw "^${REBOUND}" + +cat <$CONF +nameserver ${ADDR1} +record A t1.example.com. 1.1.1.1 +record A t2.example.com. 2.2.2.2 +! + +$ENV $NS -l $ADDR1 A t3.example.com. 3.3.3.3 + +$ENV $REBOUND -c $CONF -l $ADDR2 + +R=$(resolve -t A t1.example.com $ADDR2) +asserteq "t1.example.com has address 1.1.1.1" "$R" "must be resolved by rebound" + +R=$(resolve -t PTR 1.1.1.1 $ADDR2) +asserteq "1.1.1.1.in-addr.arpa domain name pointer t1.example.com." "$R" \ + "must be resolved by rebound" + +R=$(resolve -t A t2.example.com $ADDR2) +asserteq "t2.example.com has address 2.2.2.2" "$R" "must be resolved by rebound" + +R=$(resolve -t PTR 2.2.2.2 $ADDR2) +asserteq "2.2.2.2.in-addr.arpa domain name pointer t2.example.com." "$R" \ + "must be resolved by rebound" + +R=$(resolve -t A t3.example.com $ADDR2) +asserteq "t3.example.com has address 3.3.3.3" "$R" \ + "must be resolved by rebound-ns" + +R=$(resolve -t A t4.example.com $ADDR2) +asserteq "Host t4.example.com not found: 3(NXDOMAIN)" "$R" \ + "must not be resolved by rebound nor rebound-ns" diff --git a/regress/usr.sbin/rebound/reload.sh b/regress/usr.sbin/rebound/reload.sh new file mode 100644 index 00000000000..0073e5d7437 --- /dev/null +++ b/regress/usr.sbin/rebound/reload.sh @@ -0,0 +1,54 @@ +#!/bin/sh +# +# $OpenBSD: reload.sh,v 1.1 2018/05/09 19:34:53 anton Exp $ +# +# Copyright (c) 2018 Anton Lindqvist +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +echo nameserver $ADDR1 >$CONF + +$ENV $NS -l $ADDR1 A t1.example.com. 1.1.1.1 + +$ENV $REBOUND -c $CONF -l $ADDR2 + +R=$(resolve -t A t1.example.com $ADDR2) +asserteq "t1.example.com has address 1.1.1.1" "$R" \ + "must be resolved by rebound-ns" + +# Kill and restart rebound-ns in order to bind to a different address. +pkillw "^${NS}" +echo nameserver $ADDR3 >$CONF +$ENV $NS -l $ADDR3 A t2.example.com. 2.2.2.2 A t3.example.com. 3.3.3.3 + +R=$(resolve -t A t2.example.com $ADDR2) +asserteq "t2.example.com has address 2.2.2.2" "$R" \ + "must be resolved by rebound-ns" + +# It should survive a SIGHUP delivery. +pkill -HUP -f "^${REBOUND}" + +R=$(resolve -t A t3.example.com $ADDR2) +asserteq "t3.example.com has address 3.3.3.3" "$R" \ + "must be resolved by rebound-ns" + +# Clear resolv.conf, rebound should exit since all nameservers are gone. +echo >$CONF +NTRIES=0 +while pgrep -f "^${REBOUND}" >/dev/null 2>&1; do + sleep .1 + NTRIES=$((NTRIES + 1)) + [ $NTRIES -eq 100 ] && break +done +R=$(pgrep -fl "^${REBOUND}" | xargs) +asserteq "" "$R" "rebound not dead after $((NTRIES / 10)) seconds" diff --git a/regress/usr.sbin/rebound/run.sh b/regress/usr.sbin/rebound/run.sh new file mode 100644 index 00000000000..1ff6c7cd489 --- /dev/null +++ b/regress/usr.sbin/rebound/run.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# +# $OpenBSD: run.sh,v 1.1 2018/05/09 19:34:53 anton Exp $ +# +# Copyright (c) 2018 Anton Lindqvist +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +set -eu + +usage() { + echo "usage: sh run.sh -a addr -n rebound-ns -r rebound file" 1>&2 + exit 1 +} + +# asserteq WANT GOT [MESSAGE] +asserteq() { + [ "$1" = "$2" ] && return 0 + + printf 'FAIL:\n\tWANT:\t"%s"\n\tGOT:\t"%s"\n' "$1" "$2" + [ $# -eq 3 ] && printf '\tREASON:\t"%s"\n' "$3" + FAIL=1 +} + +atexit() { + local _err=$? + + # Kill daemons. + pkillw "^${REBOUND}" "^${NS}" + + # Cleanup temporary files. + rm -f $@ + + if [ $_err -ne 0 ] || [ $FAIL -ne 0 ]; then + exit 1 + else + exit 0 + fi +} + +pkillw() { + local _pat _sig + + for _pat; do + _sig=TERM + while pgrep -f "$_pat" >/dev/null 2>&1; do + pkill "-${_sig}" -f "$_pat" + sleep .5 + _sig=KILL + done + done +} + +resolve() { + host $@ | sed -n '$p' +} + +ENV='env MALLOC_OPTIONS=S' +FAIL=0 +NADDR=0 +NS= +REBOUND= + +while getopts "a:n:r:" opt; do + case "$opt" in + a) NADDR=$((NADDR + 1)) + eval "ADDR${NADDR}=${OPTARG}" + ;; + n) NS=$OPTARG;; + r) REBOUND=$OPTARG;; + *) usage;; + esac +done +shift $((OPTIND - 1)) +([ $# -ne 1 ] || [ $NADDR -eq 0 ] || [ -z "$NS" ] || [ -z "$REBOUND" ]) && usage + +CONF=$(mktemp -t rebound.XXXXXX) +trap 'atexit $CONF' EXIT + +. $1 -- cgit v1.2.3