that cannot be represented with the off_t type that is used
by standard functions such as lseek(2). Problem reported
by Blaz Zupan @ amis.net.
+
+20000823
+
+ Feature: all this discussion about when to reject mail and
+ when not made me decide to implement a TCP-based map type
+ so that it becomes relatively simple to implement dynamic
+ access controls, for example, hold off mail from an unknown
+ client or sender until we have completed some investigation,
+ after which we will either reject or accept.
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20000822"
+#define DEF_MAIL_VERSION "Snapshot-20000823"
extern char *var_mail_version;
/* LICENSE
-TDICT_PCRE
-TDICT_REGEXP
-TDICT_REGEXP_RULE
+-TDICT_TCP
-TDICT_UNIX
-TDNS_FIXED
-TDNS_REPLY
write_buf.c write_wait.c dict_unix.c dict_pcre.c stream_listen.c \
stream_connect.c stream_trigger.c dict_regexp.c mac_expand.c \
clean_env.c watchdog.c spawn_command.c duplex_pipe.c sane_rename.c \
- sane_link.c unescape.c timed_read.c timed_write.c
+ sane_link.c unescape.c timed_read.c timed_write.c dict_tcp.c \
+ hex_quote.c
OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.o \
close_on_exec.o concatenate.o dict.o dict_db.o dict_dbm.o \
dict_env.o dict_ht.o dict_ldap.o dict_mysql.o dict_ni.o dict_nis.o \
write_buf.o write_wait.o dict_unix.o dict_pcre.o stream_listen.o \
stream_connect.o stream_trigger.o dict_regexp.o mac_expand.o \
clean_env.o watchdog.o spawn_command.o duplex_pipe.o sane_rename.o \
- sane_link.o unescape.o timed_read.o timed_write.o
+ sane_link.o unescape.o timed_read.o timed_write.o dict_tcp.o \
+ hex_quote.o
HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.h \
dict_dbm.h dict_env.h dict_ht.h dict_ldap.h dict_mysql.h \
dict_ni.h dict_nis.h dict_nisplus.h dir_forest.h events.h \
timed_connect.h timed_wait.h trigger.h username.h valid_hostname.h \
vbuf.h vbuf_print.h vstream.h vstring.h vstring_vstream.h \
dict_unix.h dict_pcre.h dict_regexp.h mac_expand.h clean_env.h \
- watchdog.h spawn_command.h sane_fsops.h
+ watchdog.h spawn_command.h sane_fsops.h dict_tcp.h hex_quote.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \
inet_addr_host inet_addr_local mac_parse make_dirs msg_syslog \
mystrtok sigdelay translit valid_hostname vstream_popen \
vstring vstring_vstream doze select_bug stream_test mac_expand \
- watchdog unescape
+ watchdog unescape hex_quote
LIB_DIR = ../lib
INC_DIR = ../include
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+hex_quote: $(LIB)
+ mv $@.o junk
+ $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
+ mv junk $@.o
+
depend: $(MAKES)
(sed '1,/^# do not edit/!d' Makefile.in; \
set -e; for i in [a-z][a-z0-9]*.c; do \
stream_test: stream_test.c $(LIB)
$(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
-tests: valid_hostname_test mac_expand_test dict_test unescape_test
+tests: valid_hostname_test mac_expand_test dict_test unescape_test \
+ hex_quote_test
valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref
./valid_hostname <valid_hostname.in 2>valid_hostname.tmp
diff -b unescape.ref unescape.tmp
rm -f unescape.tmp
+hex_quote_test: hex_quote
+ ./hex_quote <hex_quote.c | od -cb >hex_quote.tmp
+ od -cb <hex_quote.c >hex_quote.ref
+ cmp hex_quote.ref hex_quote.tmp
+ rm -f hex_quote.ref hex_quote.tmp
+
DB_TYPE = `../postconf/postconf -h default_database_type`
dict_test: dict_open testdb dict_test.in dict_test.ref
dict_open.o: vbuf.h
dict_open.o: dict_env.h
dict_open.o: dict_unix.h
+dict_open.o: dict_tcp.h
dict_open.o: dict_dbm.h
dict_open.o: dict_db.h
dict_open.o: dict_nis.h
dict_regexp.o: argv.h
dict_regexp.o: dict_regexp.h
dict_regexp.o: mac_parse.h
+dict_tcp.o: dict_tcp.c
+dict_tcp.o: sys_defs.h
+dict_tcp.o: msg.h
+dict_tcp.o: mymalloc.h
+dict_tcp.o: vstring.h
+dict_tcp.o: vbuf.h
+dict_tcp.o: vstream.h
+dict_tcp.o: vstring_vstream.h
+dict_tcp.o: connect.h
+dict_tcp.o: iostuff.h
+dict_tcp.o: hex_quote.h
+dict_tcp.o: dict.h
+dict_tcp.o: argv.h
+dict_tcp.o: dict_tcp.h
dict_unix.o: dict_unix.c
dict_unix.o: sys_defs.h
dict_unix.o: msg.h
doze.o: sys_defs.h
doze.o: msg.h
doze.o: iostuff.h
+dup2_pass_on_exec.o: dup2_pass_on_exec.c
duplex_pipe.o: duplex_pipe.c
duplex_pipe.o: sys_defs.h
duplex_pipe.o: iostuff.h
get_hostname.o: msg.h
get_hostname.o: valid_hostname.h
get_hostname.o: get_hostname.h
+hex_quote.o: hex_quote.c
+hex_quote.o: sys_defs.h
+hex_quote.o: msg.h
+hex_quote.o: vstring.h
+hex_quote.o: vbuf.h
+hex_quote.o: hex_quote.h
htable.o: htable.c
htable.o: sys_defs.h
htable.o: mymalloc.h
vstring_vstream.o: vstring_vstream.h
watchdog.o: watchdog.c
watchdog.o: sys_defs.h
+watchdog.o: posix_signals.h
watchdog.o: msg.h
watchdog.o: mymalloc.h
watchdog.o: watchdog.h
#include <dict.h>
#include <dict_env.h>
#include <dict_unix.h>
+#include <dict_tcp.h>
#include <dict_dbm.h>
#include <dict_db.h>
#include <dict_nis.h>
static DICT_OPEN_INFO dict_open_info[] = {
"environ", dict_env_open,
"unix", dict_unix_open,
+ "tcp", dict_tcp_open,
#ifdef HAS_DBM
"dbm", dict_dbm_open,
#endif
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
+#include <signal.h>
/* Utility library. */
const char *value;
int ch;
+ signal(SIGPIPE, SIG_IGN);
+
msg_vstream_init(argv[0], VSTREAM_ERR);
while ((ch = GETOPT(argc, argv, "v")) > 0) {
switch (ch) {
vstream_printf("%s: deleted\n", key);
} else if (strcmp(cmd, "get") == 0 && key && !value) {
if ((value = dict_get(dict, key)) == 0) {
- vstream_printf("%s: not found\n", key);
+ vstream_printf("%s: %s\n", key,
+ dict_errno == DICT_ERR_RETRY ?
+ "soft error" : "not found");
} else {
vstream_printf("%s=%s\n", key, value);
}
dict_put(dict, key, value);
vstream_printf("%s=%s\n", key, value);
} else {
- vstream_printf("usage: del key|get key|put key=value");
+ vstream_printf("usage: del key|get key|put key=value\n");
}
vstream_fflush(VSTREAM_OUT);
}
--- /dev/null
+/*++
+/* NAME
+/* dict_tcp 3
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/*
+/* DICT *dict_tcp_open(map, dummy, dict_flags)
+/* const char *map;
+/* int dummy;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_tcp_open() makes a TCP server accessible via the generic
+/* dictionary operations described in dict_open(3).
+/* The \fIdummy\fR argument is not used. Server access is read-only
+/* (i.e., only lookups are implemented).
+/*
+/* Map names have the form host:port.
+/*
+/* The map implements a very simple protocol: the query is sent as
+/* one line of text, and the reply is sent back in the same format.
+/* Data is sent as a newline-terminated string. % and non-printable
+/* characters are replaced by %xx, xx being the corresponding
+/* hexadecimal value.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* DIAGNOSTICS
+/* Fatal errors: out of memory, unknown host or service name,
+/* attempt to update map.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "mymalloc.h"
+#include "vstring.h"
+#include "vstream.h"
+#include "vstring_vstream.h"
+#include "connect.h"
+#include "hex_quote.h"
+#include "dict.h"
+#include "dict_tcp.h"
+
+/* Application-specific. */
+
+typedef struct {
+ DICT dict; /* generic members */
+ char *map; /* server host:port */
+ VSTRING *raw_buf; /* raw buffer */
+ VSTRING *hex_buf; /* hexified buffer */
+ VSTREAM *fp; /* I/O stream */
+} DICT_TCP;
+
+#define DICT_TCP_MAXTRY 10
+#define DICT_TCP_TMOUT 100
+
+#define STR(x) vstring_str(x)
+
+/* dict_tcp_lookup - query TCP server */
+
+static const char *dict_tcp_lookup(DICT *dict, const char *key)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+ int fd;
+ int i;
+
+ dict_errno = 0;
+
+ for (i = 0; /* see below */ ; i++) {
+
+ /*
+ * Try to connect a few times before giving up.
+ */
+ if (i >= DICT_TCP_MAXTRY) {
+ dict_errno = DICT_ERR_RETRY;
+ return (0);
+ }
+
+ /*
+ * Sleep between connection attempts.
+ */
+ if (i > 0)
+ sleep(1);
+
+ /*
+ * Try to connect to the server.
+ */
+ if (dict_tcp->fp == 0) {
+ if ((fd = inet_connect(dict_tcp->map, BLOCKING, 0)) < 0) {
+ msg_warn("connect to TCP map %s: %m", dict_tcp->map);
+ continue;
+ }
+ dict_tcp->fp = vstream_fdopen(fd, O_RDWR);
+ vstream_control(dict_tcp->fp,
+ VSTREAM_CTL_TIMEOUT, DICT_TCP_TMOUT,
+ VSTREAM_CTL_END);
+ }
+
+ /*
+ * Allocate per-map buffers on the fly.
+ */
+ if (dict_tcp->raw_buf == 0) {
+ dict_tcp->raw_buf = vstring_alloc(10);
+ dict_tcp->hex_buf = vstring_alloc(10);
+ }
+
+ /*
+ * Send query and receive response. Both are %XX quoted and both are
+ * terminated by newline.
+ */
+ hex_quote(dict_tcp->hex_buf, key);
+ vstream_fprintf(dict_tcp->fp, "%s\n", STR(dict_tcp->hex_buf));
+ errno = 0;
+ if (vstring_get_nonl(dict_tcp->hex_buf, dict_tcp->fp) == VSTREAM_EOF) {
+ msg_warn("read TCP map reply from %s: %m", dict_tcp->map);
+ } else if (!hex_unquote(dict_tcp->raw_buf, STR(dict_tcp->hex_buf))) {
+ msg_warn("read TCP map reply from %s: malformed reply %.100s",
+ dict_tcp->map, STR(dict_tcp->hex_buf));
+ } else {
+ return (STR(dict_tcp->raw_buf));
+ }
+
+ /*
+ * That did not work. Clean up and try again.
+ */
+ (void) vstream_fclose(dict_tcp->fp);
+ dict_tcp->fp = 0;
+ }
+}
+
+/* dict_tcp_update - add or update table entry */
+
+static void dict_tcp_update(DICT *dict, const char *unused_name, const char *unused_value)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+
+ msg_fatal("dict_tcp_update: attempt to update map %s", dict_tcp->map);
+}
+
+/* dict_tcp_close - close TCP map */
+
+static void dict_tcp_close(DICT *dict)
+{
+ DICT_TCP *dict_tcp = (DICT_TCP *) dict;
+
+ if (dict_tcp->fp)
+ (void) vstream_fclose(dict_tcp->fp);
+ if (dict_tcp->raw_buf)
+ vstring_free(dict_tcp->raw_buf);
+ if (dict_tcp->hex_buf)
+ vstring_free(dict_tcp->hex_buf);
+ myfree(dict_tcp->map);
+ myfree((char *) dict_tcp);
+}
+
+/* dict_tcp_open - open TCP map */
+
+DICT *dict_tcp_open(const char *map, int unused_flags, int dict_flags)
+{
+ DICT_TCP *dict_tcp;
+
+ dict_errno = 0;
+
+ dict_tcp = (DICT_TCP *) mymalloc(sizeof(*dict_tcp));
+ dict_tcp->fp = 0;
+ dict_tcp->raw_buf = dict_tcp->hex_buf = 0;
+ dict_tcp->dict.lookup = dict_tcp_lookup;
+ dict_tcp->dict.update = dict_tcp_update;
+ dict_tcp->dict.close = dict_tcp_close;
+ dict_tcp->dict.fd = -1;
+ dict_tcp->map = mystrdup(map);
+ dict_tcp->dict.flags = dict_flags | DICT_FLAG_FIXED;
+ return (&dict_tcp->dict);
+}
--- /dev/null
+#ifndef _DICT_TCP_H_INCLUDED_
+#define _DICT_TCP_H_INCLUDED_
+
+/*++
+/* NAME
+/* dict_tcp 3h
+/* SUMMARY
+/* dictionary manager interface to tcp-based lookup tables
+/* SYNOPSIS
+/* #include <dict_tcp.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <dict.h>
+
+ /*
+ * External interface.
+ */
+extern DICT *dict_tcp_open(const char *, int, int);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
--- /dev/null
+/*++
+/* NAME
+/* hex_quote 3
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/*
+/* VSTRING *hex_quote(hex, raw)
+/* VSTRING *hex;
+/* const char *raw;
+/*
+/* VSTRING *hex_unquote(raw, hex)
+/* VSTRING *raw;
+/* const char *hex;
+/* DESCRIPTION
+/* hex_quote() takes a null-terminated string and replaces non-printable
+/* characters and % by %XX, XX being the two-digit hexadecimal equivalent.
+/* The hexadecimal codes are produced as upper-case characters. The result
+/* value is the hex argument.
+/*
+/* hex_unquote() performs the opposite transformation. This function
+/* understands lowercase and uppercase %XX sequences. The result
+/* value is the raw argument in case of success, a null pointer otherwise.
+/* BUGS
+/* Cannot process null characters.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+/* System library. */
+
+#include "sys_defs.h"
+#include <ctype.h>
+
+/* Utility library. */
+
+#include "msg.h"
+#include "vstring.h"
+#include "hex_quote.h"
+
+/* Application-specific. */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+/* hex_quote - raw data to quoted */
+
+VSTRING *hex_quote(VSTRING *hex, const char *raw)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(hex);
+ for (cp = raw; (ch = *(unsigned const char *) cp) != 0; cp++) {
+ if (ch != '%' && ISPRINT(ch)) {
+ VSTRING_ADDCH(hex, ch);
+ } else {
+ vstring_sprintf_append(hex, "%%%02X", ch);
+ }
+ }
+ VSTRING_TERMINATE(hex);
+ return (hex);
+}
+
+/* hex_unquote - quoted data to raw */
+
+VSTRING *hex_unquote(VSTRING *raw, const char *hex)
+{
+ const char *cp;
+ int ch;
+
+ VSTRING_RESET(raw);
+ for (cp = hex; (ch = *cp) != 0; cp++) {
+ if (ch == '%') {
+ if (ISDIGIT(cp[1]))
+ ch = (cp[1] - '0') << 4;
+ else if (cp[1] >= 'a' && cp[1] <= 'f')
+ ch = (cp[1] - 'a' + 10) << 4;
+ else if (cp[1] >= 'A' && cp[1] <= 'F')
+ ch = (cp[1] - 'A' + 10) << 4;
+ else
+ return (0);
+ if (ISDIGIT(cp[2]))
+ ch |= (cp[2] - '0');
+ else if (cp[2] >= 'a' && cp[2] <= 'f')
+ ch |= (cp[2] - 'a' + 10);
+ else if (cp[2] >= 'A' && cp[2] <= 'F')
+ ch |= (cp[2] - 'A' + 10);
+ else
+ return (0);
+ cp += 2;
+ }
+ VSTRING_ADDCH(raw, ch);
+ }
+ VSTRING_TERMINATE(raw);
+ return (raw);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to hex and back.
+ */
+#include <vstream.h>
+
+#define BUFLEN 1024
+
+static int read_buf(VSTREAM *fp, VSTRING *buf)
+{
+ int len;
+
+ VSTRING_RESET(buf);
+ len = vstream_fread(fp, STR(buf), vstring_avail(buf));
+ VSTRING_AT_OFFSET(buf, len); /* XXX */
+ VSTRING_TERMINATE(buf);
+ return (len);
+}
+
+main(int unused_argc, char **unused_argv)
+{
+ VSTRING *raw = vstring_alloc(BUFLEN);
+ VSTRING *hex = vstring_alloc(100);
+ int len;
+
+ while ((len = read_buf(VSTREAM_IN, raw)) > 0) {
+ hex_quote(hex, STR(raw));
+ if (hex_unquote(raw, STR(hex)) == 0)
+ msg_fatal("bad input: %.100s", STR(hex));
+ if (LEN(raw) != len)
+ msg_fatal("len %d != raw len %d", len, LEN(raw));
+ if (vstream_fwrite(VSTREAM_OUT, STR(raw), LEN(raw)) != LEN(raw))
+ msg_fatal("write error: %m");
+ }
+ vstream_fflush(VSTREAM_OUT);
+ vstring_free(raw);
+ vstring_free(hex);
+ return (0);
+}
+
+#endif
--- /dev/null
+#ifndef _HEX_QUOTE_H_INCLUDED_
+#define _HEX_QUOTE_H_INCLUDED_
+
+/*++
+/* NAME
+/* hex_quote 3h
+/* SUMMARY
+/* quote/unquote text, HTTP style.
+/* SYNOPSIS
+/* #include <hex_quote.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *hex_quote(VSTRING *, const char *);
+extern VSTRING *hex_unquote(VSTRING *, const char *);
+
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*--*/
+
+#endif
#if __GNUC__ == 2 && __GNUC_MINOR__ >= 7
#define PRINTFLIKE(x,y) __attribute__ ((format (printf, (x), (y))))
#else
-#define PRINTFLIKE
+#define PRINTFLIKE(x,y)
#endif
#endif