20011010-14
Replaced the internal protocols by (name,value) attribute
- lists. This is more extensible.
+ lists. This gives better error detection when we start
+ making changes to internal protocols.
+
+20011015
+
+ Put base 64 encoding into place on the replced internal
+ protocols. Files: util/base64_code.[hc].
+
+ Feature: header/body REJECT rules can now end in text that
+ is sent to the originator. Files: cleanup/cleanup.c,
+ cleanup/cleanup_message.c, conf/sample-filter.cf.
Open problems:
#
# REJECT the entire message is rejected.
#
+# REJECT text.... The text is sent to the originator.
+#
# IGNORE the header line is silently discarded.
#
# OK Nothing happens. the message will still be rejected when some
/*
* Read the and validate the client request.
*/
- if (mail_command_server(client, "%d %s %s %s",
+ if (mail_command_server(client,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags,
- ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name,
+ ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id,
ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient,
- ATTR_TYPE_STR, MAIL_ATTR_WHY, why, 0) != 4) {
+ ATTR_TYPE_STR, MAIL_ATTR_WHY, why,
+ ATTR_TYPE_END) != 4) {
msg_warn("malformed request");
return (-1);
}
/*
* Read and validate the client request.
*/
- if (mail_command_server(client, ATTR_FLAG_MISSING,
+ if (mail_command_server(client,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id,
- ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, 0) != 4) {
+ ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
+ ATTR_TYPE_END) != 4) {
msg_warn("malformed request");
return (-1);
}
ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id,
ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender,
- ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp_delims, 0) != 5) {
+ ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp_delims,
+ ATTR_TYPE_END) != 5) {
msg_warn("malformed request");
return (-1);
}
*/
attr_print(src, ATTR_FLAG_NONE,
ATTR_TYPE_NUM, MAIL_ATTR_STATUS, cleanup_close(state),
- ATTR_TYPE_STR, MAIL_ATTR_WHY, "",
+ ATTR_TYPE_STR, MAIL_ATTR_WHY, state->why_rejected ?
+ vstring_str(state->why_rejected) : "",
ATTR_TYPE_END);
+ cleanup_free(state);
/*
* Cleanup.
off_t xtra_offset; /* start of extra segment */
int end_seen; /* REC_TYPE_END seen */
int rcpt_count; /* recipient count */
+ VSTRING *why_rejected; /* REJECT reason */
} CLEANUP_STATE;
/*
extern CLEANUP_STATE *cleanup_open(void);
extern void cleanup_control(CLEANUP_STATE *, int);
extern int cleanup_close(CLEANUP_STATE *);
+extern void cleanup_free(CLEANUP_STATE *);
extern void cleanup_all(void);
extern void cleanup_pre_jail(char *, char **);
extern void cleanup_post_jail(char *, char **);
/*
/* int cleanup_close(state)
/* CLEANUP_STATE *state;
+/*
+/* int cleanup_free(state)
+/* CLEANUP_STATE *state;
/* DESCRIPTION
/* This module implements a callable interface to the cleanup service
/* for processing one message and for writing it to queue file.
/*
/* cleanup_open() creates a new queue file and performs other
/* per-message initialization. The result is a handle that should be
-/* given to the cleanup_control(), cleanup_record() and cleanup_close()
+/* given to the cleanup_control(), cleanup_record(), cleanup_close()
+/* and cleanup_close()
/* routines. The name of the queue file is in the queue_id result
/* structure member.
/*
/* The result is false when further message processing is futile.
/* In that case, it is safe to call cleanup_close() immediately.
/*
-/* cleanup_close() finishes a queue file. In case of any errors,
+/* cleanup_close() closes a queue file. In case of any errors,
/* the file is removed. The result value is non-zero in case of
/* problems. Use cleanup_strerror() to translate the result into
/* human_readable text.
+/*
+/* cleanup_free() destroys its argument.
/* DIAGNOSTICS
/* Problems and transactions are logged to \fBsyslogd\fR(8).
/* SEE ALSO
{
char *junk;
int status;
+ const char *reason;
/*
* See if there are any errors. For example, the message is incomplete,
* copy of the message.
*/
if ((state->errs & CLEANUP_STAT_LETHAL) == 0)
- state->errs |= mail_stream_finish(state->handle);
+ state->errs |= mail_stream_finish(state->handle, (VSTRING *) 0);
else
mail_stream_cleanup(state->handle);
state->handle = 0;
if (state->errs & CLEANUP_STAT_LETHAL) {
if (CAN_BOUNCE()) {
+ reason = cleanup_strerror(state->errs);
+ if (reason == cleanup_strerror(CLEANUP_STAT_CONT))
+ reason = vstring_str(state->why_rejected);
if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id,
state->recip ? state->recip : "unknown",
"cleanup", state->time,
"Message processing aborted: %s",
- cleanup_strerror(state->errs)) == 0
+ reason) == 0
&& bounce_flush(BOUNCE_FLAG_CLEAN, MAIL_QUEUE_INCOMING,
state->queue_id, state->sender) == 0) {
state->errs = 0;
if (msg_verbose)
msg_info("cleanup_close: status %d", state->errs);
status = state->errs & CLEANUP_STAT_LETHAL;
- cleanup_state_free(state);
return (status);
}
+
+/* cleanup_close - pay the last respects */
+
+void cleanup_free(CLEANUP_STATE *state)
+{
+ cleanup_state_free(state);
+}
cleanup_fold_header(state);
}
+/* cleanup_parse_reject - parse REJECT liune and pick up the reason */
+
+static const char *cleanup_parse_reject(CLEANUP_STATE *state, const char *value)
+{
+ const char *reason;
+
+ /*
+ * See if they spelled REJECT right.
+ */
+ if (strcasecmp(value, "REJECT") == 0) {
+ reason = "Content rejected";
+ } else if (strncasecmp(value, "REJECT ", 7) == 0
+ || strncasecmp(value, "REJECT\t", 7) == 0) {
+ reason = value + 7;
+ while (*reason && ISSPACE(*reason))
+ reason++;
+ if (*reason == 0)
+ reason = "Content rejected";
+ } else {
+ return (0);
+ }
+
+ /*
+ * Update the remembered reason if none was stored.
+ */
+ if (state->why_rejected == 0) {
+ state->why_rejected = vstring_alloc(10);
+ vstring_strcpy(state->why_rejected, reason);
+ }
+ return (reason);
+}
+
/* cleanup_header - process one complete header line */
static void cleanup_header(CLEANUP_STATE *state)
if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_header_checks) {
char *header = vstring_str(state->header_buf);
const char *value;
+ const char *reason;
if ((value = maps_find(cleanup_header_checks, header, 0)) != 0) {
- if (strcasecmp(value, "REJECT") == 0) {
- msg_info("%s: reject: header %.200s; from=<%s> to=<%s>",
+ if ((reason = cleanup_parse_reject(state, value)) != 0) {
+ msg_info("%s: reject: header %.200s; from=<%s> to=<%s>: %s",
state->queue_id, header, state->sender,
- state->recip ? state->recip : "unknown");
+ state->recip ? state->recip : "unknown", reason);
state->errs |= CLEANUP_STAT_CONT;
} else if (strcasecmp(value, "IGNORE") == 0) {
return;
*/
if ((state->flags & CLEANUP_FLAG_FILTER) && cleanup_body_checks) {
const char *value;
+ const char *reason;
if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) {
- if (strcasecmp(value, "REJECT") == 0) {
- msg_info("%s: reject: body %.200s; from=<%s> to=<%s>",
+ if ((reason = cleanup_parse_reject(state, value)) != 0) {
+ msg_info("%s: reject: body %.200s; from=<%s> to=<%s>: %s",
state->queue_id, buf, state->sender,
- state->recip ? state->recip : "unknown");
+ state->recip ? state->recip : "unknown", reason);
state->errs |= CLEANUP_STAT_CONT;
} else if (strcasecmp(value, "IGNORE") == 0) {
return;
state->xtra_offset = -1;
state->end_seen = 0;
state->rcpt_count = 0;
+ state->why_rejected = 0;
return (state);
}
if (state->queue_id)
myfree(state->queue_id);
been_here_free(state->dups);
+ if (state->why_rejected)
+ vstring_free(state->why_rejected);
myfree((char *) state);
}
site = vstring_alloc(10);
queue_id = vstring_alloc(10);
if (attr_scan(client_stream, ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA,
- ATTR_TYPE_STR, MAIL_ATTR_SITE, site, ATTR_FLAG_MISSING,
- ATTR_TYPE_STR, MAIL_ATTR_SITE, queue_id,
+ ATTR_TYPE_STR, MAIL_ATTR_SITE, site,
+ ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id,
ATTR_TYPE_END) == 2
&& mail_queue_id_ok(STR(queue_id)))
status = flush_add_service(lowercase(STR(site)), STR(queue_id));
vstring_vsprintf(why, fmt, ap);
if (mail_command_client(MAIL_CLASS_PRIVATE, var_soft_bounce ?
MAIL_SERVICE_DEFER : MAIL_SERVICE_BOUNCE,
- ATTR_TYPE_NUM, MAIL_ATTR_REQ, BOUNCE_CMD_APPEND,
+ ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient,
if (var_soft_bounce)
return (-1);
if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE,
- ATTR_TYPE_NUM, MAIL_ATTR_REQ, BOUNCE_CMD_FLUSH,
+ ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
vstring_vsprintf(why, fmt, ap);
if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER,
- ATTR_TYPE_NUM, MAIL_ATTR_REQ, BOUNCE_CMD_APPEND,
+ ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient,
const char *sender)
{
if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER,
- ATTR_TYPE_NUM, MAIL_ATTR_REQ, BOUNCE_CMD_FLUSH,
+ ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
const char *sender)
{
if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER,
- ATTR_TYPE_NUM, MAIL_ATTR_REQ, BOUNCE_CMD_WARN,
+ ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_WARN,
ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags,
ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue,
ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id,
--- /dev/null
+#ifndef _MAIL_ATTR_H_INCLUDED_
+#define _MAIL_ATTR_H_INCLUDED_
+
+/*++
+/* NAME
+/* mail_attr 3h
+/* SUMMARY
+/* mail internal IPC support
+/* SYNOPSIS
+/* #include <mail_attr.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Request attribute. Values are defined by individual applications.
+ */
+#define MAIL_REQUEST "request"
+
+ /*
+ * Request completion status.
+ */
+#define MAIL_STATUS "status"
+#define MAIL_STAT_OK "success"
+#define MAIL_STAT_FAIL "failed"
+#define MAIL_STAT_RETRY "retry"
+#define MAIL_STAT_REJECT "reject"
+
+/* 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
+/* mail_command_read 3
+/* SUMMARY
+/* single-command server
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* int mail_command_read(stream, format, ...)
+/* VSTREAM *stream;
+/* char *format;
+/* DESCRIPTION
+/* This module implements the server interface for single-command
+/* requests: a clients sends a single command and expects a single
+/* completion status code.
+/*
+/* Arguments:
+/* .IP stream
+/* Server endpoint.
+/* .IP format
+/* Format string understood by mail_print(3) and mail_scan(3).
+/* DIAGNOSTICS
+/* Fatal: out of memory.
+/* SEE ALSO
+/* mail_scan(3)
+/* mail_command_write(3) client interface
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+#include <string.h>
+
+/* Utility library. */
+
+#include <vstring.h>
+#include <vstream.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+
+/* mail_command_read - read single-command request */
+
+int mail_command_read(VSTREAM *stream, char *fmt,...)
+{
+ VSTRING *eof = vstring_alloc(10);
+ va_list ap;
+ int count;
+
+ va_start(ap, fmt);
+ count = mail_vscan(stream, fmt, ap);
+ va_end(ap);
+ if (mail_scan(stream, "%s", eof) != 1 || strcmp(vstring_str(eof), MAIL_EOF))
+ count = -1;
+ vstring_free(eof);
+ return (count);
+}
--- /dev/null
+/*++
+/* NAME
+/* mail_command_write 3
+/* SUMMARY
+/* single-command client
+/* SYNOPSIS
+/* #include <mail_proto.h>
+/*
+/* int mail_command_write(class, name, format, ...)
+/* const char *class;
+/* const char *name;
+/* const char *format;
+/* DESCRIPTION
+/* This module implements a client interface for single-command
+/* clients: a client that sends a single command and expects
+/* a single completion status code.
+/*
+/* Arguments:
+/* .IP class
+/* Service type: MAIL_CLASS_PUBLIC or MAIL_CLASS_PRIVATE
+/* .IP name
+/* Service name (master.cf).
+/* .IP format
+/* Format string understood by mail_print(3).
+/* DIAGNOSTICS
+/* The result is -1 if the request could not be sent, otherwise
+/* the result is the status reported by the server.
+/* Warnings: problems connecting to the requested service.
+/* Fatal: out of memory.
+/* SEE ALSO
+/* mail_command_read(3), server interface
+/* mail_proto(5h), client-server protocol
+/* 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 <stdlib.h> /* 44BSD stdarg.h uses abort() */
+#include <stdarg.h>
+
+/* Utility library. */
+
+#include <vstream.h>
+
+/* Global library. */
+
+#include "mail_proto.h"
+
+/* mail_command_write - single-command transaction with completion status */
+
+int mail_command_write(const char *class, const char *name,
+ const char *fmt,...)
+{
+ va_list ap;
+ VSTREAM *stream;
+ int status;
+
+ /*
+ * Talk a little protocol with the specified service.
+ */
+ if ((stream = mail_connect(class, name, BLOCKING)) == 0)
+ return (-1);
+ va_start(ap, fmt);
+ status = mail_vprint(stream, fmt, ap);
+ va_end(ap);
+ if (status != 0
+ || mail_print(stream, "%s", MAIL_EOF) != 0
+ || vstream_fflush(stream) != 0
+ || mail_scan(stream, "%d", &status) != 1)
+ status = -1;
+ (void) vstream_fclose(stream);
+ return (status);
+}
#include <connect.h>
#include <mymalloc.h>
#include <iostuff.h>
+#include <stringops.h>
/* Global library. */
char *path;
VSTREAM *stream;
int fd;
+ char *sock_name;
path = mail_pathname(class, name);
if ((fd = LOCAL_CONNECT(path, block_mode, 0)) < 0) {
msg_info("connect to subsystem %s", path);
stream = vstream_fdopen(fd, O_RDWR);
timed_ipc_setup(stream);
+ sock_name = concatenate("socket ", path, (char *) 0);
vstream_control(stream,
- VSTREAM_CTL_PATH, path,
+ VSTREAM_CTL_PATH, sock_name,
VSTREAM_CTL_END);
+ myfree(sock_name);
}
myfree(path);
return (stream);
/* mail_stream_finish_file - finish file mail stream */
-static int mail_stream_finish_file(MAIL_STREAM * info)
+static int mail_stream_finish_file(MAIL_STREAM * info, VSTRING *unused_why)
{
int status = 0;
static char wakeup[] = {TRIGGER_REQ_WAKEUP};
/* mail_stream_finish_ipc - finish IPC mail stream */
-static int mail_stream_finish_ipc(MAIL_STREAM * info)
+static int mail_stream_finish_ipc(MAIL_STREAM * info, VSTRING *why)
{
int status = CLEANUP_STAT_WRITE;
* Receive the peer's completion status.
*/
if (attr_scan(info->stream, ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA,
- ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, 0) != 1)
+ ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status,
+ ATTR_TYPE_STR, MAIL_ATTR_WHY, why,
+ ATTR_TYPE_END) != 2)
status = CLEANUP_STAT_WRITE;
/*
/* mail_stream_finish - finish action */
-int mail_stream_finish(MAIL_STREAM * info)
+int mail_stream_finish(MAIL_STREAM * info, VSTRING *why)
{
- return (info->finish(info));
+ return (info->finish(info, why));
}
/* mail_stream_file - destination is file */
*/
typedef struct MAIL_STREAM MAIL_STREAM;
-typedef int (*MAIL_STREAM_FINISH_FN) (MAIL_STREAM *);
+typedef int (*MAIL_STREAM_FINISH_FN) (MAIL_STREAM *, VSTRING *);
typedef int (*MAIL_STREAM_CLOSE_FN) (VSTREAM *);
struct MAIL_STREAM {
extern MAIL_STREAM *mail_stream_service(const char *, const char *);
extern MAIL_STREAM *mail_stream_command(const char *);
extern void mail_stream_cleanup(MAIL_STREAM *);
-extern int mail_stream_finish(MAIL_STREAM *);
+extern int mail_stream_finish(MAIL_STREAM *, VSTRING *);
/* LICENSE
* Version of this program.
*/
#define VAR_MAIL_VERSION "mail_version"
-#define DEF_MAIL_VERSION "Snapshot-20011014"
+#define DEF_MAIL_VERSION "Snapshot-20011015"
extern char *var_mail_version;
/* LICENSE
/*
* Finish the file.
*/
- if ((status = mail_stream_finish(dst)) != 0)
+ if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0)
msg_fatal("uid=%ld: %s", (long) uid, cleanup_strerror(status));
/*
* Finish the queue file or finish the cleanup conversation.
*/
if (state->err == 0)
- state->err = mail_stream_finish(state->dest);
+ state->err = mail_stream_finish(state->dest, state->why_rejected);
else
mail_stream_cleanup(state->dest);
state->dest = 0;
"Error: too many hops");
} else if ((state->err & CLEANUP_STAT_CONT) != 0) {
qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD,
- "Error: content rejected");
+ "Error: %s", STR(state->why_rejected));
} else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY,
"Error: queue file write error");
char *recipient; /* recipient address */
char *protocol; /* protocol name */
char *where; /* protocol state */
+ VSTRING *why_rejected; /* REJECT reason */
} QMQPD_STATE;
/*
state->recipient = 0;
state->protocol = "QMQP";
state->where = "initializing client connection";
+ state->why_rejected = vstring_alloc(10);
return (state);
}
myfree(state->sender);
if (state->recipient)
myfree(state->recipient);
+ vstring_free(state->why_rejected);
myfree((char *) state);
}
if (vstream_ferror(VSTREAM_IN))
msg_fatal("%s(%ld): error reading input: %m",
saved_sender, (long) uid);
- if ((status = mail_stream_finish(handle)) != 0)
+ if ((status = mail_stream_finish(handle, buf)) != 0)
msg_fatal("%s(%ld): %s", saved_sender,
(long) uid, cleanup_strerror(status));
if (sendmail_path) {
int curr_rec_type;
int prev_rec_type;
int first = 1;
+ VSTRING *why = 0;
/*
* Sanity checks. With ESMTP command pipelining the client can send DATA
* Finish the queue file or finish the cleanup conversation.
*/
if (state->err == 0)
- state->err |= mail_stream_finish(state->dest);
+ state->err |= mail_stream_finish(state->dest, why = vstring_alloc(10));
else
mail_stream_cleanup(state->dest);
state->dest = 0;
smtpd_chat_reply(state, "554 Error: too many hops");
} else if ((state->err & CLEANUP_STAT_CONT) != 0) {
state->error_mask |= MAIL_ERROR_POLICY;
- smtpd_chat_reply(state, "552 Error: content rejected");
+ smtpd_chat_reply(state, "552 Error: %s", STR(why));
} else if ((state->err & CLEANUP_STAT_WRITE) != 0) {
state->error_mask |= MAIL_ERROR_RESOURCE;
smtpd_chat_reply(state, "451 Error: queue file write error");
*/
mail_reset(state);
rcpt_reset(state);
+ if (why)
+ vstring_free(why);
return (state->err);
}
sane_link.c unescape.c timed_read.c timed_write.c dict_tcp.c \
hex_quote.c dict_alloc.c rand_sleep.c sane_time.c dict_debug.c \
sane_socketpair.c myrand.c netstring.c ctable.c attr_print.c intv.c \
- attr_scan.c attr_table.c
+ attr_scan.c attr_table.c base64_code.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 \
sane_link.o unescape.o timed_read.o timed_write.o dict_tcp.o \
hex_quote.o dict_alloc.o rand_sleep.o sane_time.o dict_debug.o \
sane_socketpair.o myrand.o netstring.o ctable.o attr_print.o intv.o \
- attr_scan.o attr_table.o
+ attr_scan.o attr_table.o base64_code.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 \
dict_unix.h dict_pcre.h dict_regexp.h mac_expand.h clean_env.h \
watchdog.h spawn_command.h sane_fsops.h dict_tcp.h hex_quote.h \
sane_time.h sane_socketpair.h myrand.h netstring.h ctable.h \
- intv.h
+ intv.h base64_code.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 \
mystrtok sigdelay translit valid_hostname vstream_popen \
vstring vstring_vstream doze select_bug stream_test mac_expand \
watchdog unescape hex_quote name_mask rand_sleep sane_time ctable \
- inet_addr_list attr_print attr_scan attr_table
+ inet_addr_list attr_print attr_scan attr_table base64_code
LIB_DIR = ../../lib
INC_DIR = ../../include
$(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIB) $(SYSLIBS)
mv junk $@.o
+base64_code: $(LIB) $@.o
+ 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 \
$(CC) $(CFLAGS) -o $@ $@.c $(LIB) $(SYSLIBS)
tests: valid_hostname_test mac_expand_test dict_test unescape_test \
- hex_quote_test ctable_test inet_addr_list_test
+ hex_quote_test ctable_test inet_addr_list_test base64_code_test
valid_hostname_test: valid_hostname valid_hostname.in valid_hostname.ref
./valid_hostname <valid_hostname.in 2>valid_hostname.tmp
diff inet_addr_list.ref inet_addr_list.tmp
rm -f inet_addr_list.tmp
+base64_code_test: base64_code
+ ./base64_code
+
DB_TYPE = `../postconf/postconf -h default_database_type`
dict_test: dict_open testdb dict_test.in dict_test.ref
attr_print.o: vstream.h
attr_print.o: vbuf.h
attr_print.o: htable.h
+attr_print.o: base64_code.h
+attr_print.o: vstring.h
attr_print.o: attr.h
attr_scan.o: attr_scan.c
attr_scan.o: sys_defs.h
attr_scan.o: vstring.h
attr_scan.o: argv.h
attr_scan.o: intv.h
+attr_scan.o: base64_code.h
attr_scan.o: attr.h
attr_scan.o: htable.h
attr_table.o: attr_table.c
attr_table.o: argv.h
attr_table.o: intv.h
attr_table.o: attr.h
+base64_code.o: base64_code.c
+base64_code.o: sys_defs.h
+base64_code.o: msg.h
+base64_code.o: mymalloc.h
+base64_code.o: vstring.h
+base64_code.o: vbuf.h
+base64_code.o: base64_code.h
basename.o: basename.c
basename.o: sys_defs.h
basename.o: stringops.h
#include <mymalloc.h>
#include <vstream.h>
#include <htable.h>
+#include <base64_code.h>
#include <attr.h>
-/* attr_fprintf - encode attribute information on the fly */
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
-static void PRINTFLIKE(2, 3) attr_fprintf(VSTREAM *fp, const char *format,...)
+/* attr_print_str - encode and send attribute information */
+
+static void attr_print_str(VSTREAM *fp, const char *str, int len)
{
- va_list ap;
+ static VSTRING *base64_buf;
- va_start(ap, format);
- vstream_vfprintf(fp, format, ap);
- va_end(ap);
+ if (base64_buf == 0)
+ base64_buf = vstring_alloc(10);
+
+ base64_encode(base64_buf, str, len);
+ vstream_fputs(STR(base64_buf), fp);
+}
+
+static void attr_print_num(VSTREAM *fp, unsigned num)
+{
+ static VSTRING *plain;
+
+ if (plain == 0)
+ plain = vstring_alloc(10);
+
+ vstring_sprintf(plain, "%u", num);
+ attr_print_str(fp, STR(plain), LEN(plain));
}
/* attr_vprint - send attribute list to stream */
switch (attr_type) {
case ATTR_TYPE_NUM:
attr_name = va_arg(ap, char *);
- attr_fprintf(fp, "%s", attr_name);
+ attr_print_str(fp, attr_name, strlen(attr_name));
int_val = va_arg(ap, int);
- attr_fprintf(fp, ":%u", (unsigned) int_val);
+ VSTREAM_PUTC(':', fp);
+ attr_print_num(fp, (unsigned) int_val);
if (msg_verbose)
- msg_info("send attr name %s value %u", attr_name, int_val);
+ msg_info("send attr %s = %u", attr_name, int_val);
break;
case ATTR_TYPE_STR:
attr_name = va_arg(ap, char *);
- attr_fprintf(fp, "%s", attr_name);
+ attr_print_str(fp, attr_name, strlen(attr_name));
str_val = va_arg(ap, char *);
- attr_fprintf(fp, ":%s", str_val);
+ VSTREAM_PUTC(':', fp);
+ attr_print_str(fp, str_val, strlen(str_val));
if (msg_verbose)
- msg_info("send attr name %s value %s", attr_name, str_val);
+ msg_info("send attr %s = %s", attr_name, str_val);
break;
case ATTR_TYPE_NUM_ARRAY:
attr_name = va_arg(ap, char *);
- attr_fprintf(fp, "%s", attr_name);
+ attr_print_str(fp, attr_name, strlen(attr_name));
ip_val = va_arg(ap, int *);
count_val = va_arg(ap, int);
- for (i = 0; i < count_val; i++)
- attr_fprintf(fp, ":%u", (unsigned) *ip_val++);
+ for (i = 0; i < count_val; i++) {
+ VSTREAM_PUTC(':', fp);
+ attr_print_num(fp, (unsigned) *ip_val++);}
if (msg_verbose)
- msg_info("send attr name %s values %d", attr_name, count_val);
+ msg_info("send attr %s values %d", attr_name, count_val);
break;
case ATTR_TYPE_STR_ARRAY:
attr_name = va_arg(ap, char *);
- attr_fprintf(fp, "%s", attr_name);
+ attr_print_str(fp, attr_name, strlen(attr_name));
cpp_val = va_arg(ap, char **);
count_val = va_arg(ap, int);
for (i = 0; i < count_val; i++) {
str_val = *cpp_val++;
- attr_fprintf(fp, ":%s", str_val);
+ VSTREAM_PUTC(':', fp);
+ attr_print_str(fp, str_val, strlen(str_val));
}
if (msg_verbose)
- msg_info("send attr name %s values %d", attr_name, count_val);
+ msg_info("send attr %s values %d", attr_name, count_val);
break;
case ATTR_TYPE_HASH:
ht_info_list = htable_list(va_arg(ap, HTABLE *));
for (ht = ht_info_list; *ht; ht++) {
- attr_fprintf(fp, "%s:%s", ht[0]->key, ht[0]->value);
+ attr_print_str(fp, ht[0]->key, strlen(ht[0]->key));
+ VSTREAM_PUTC(':', fp);
+ attr_print_str(fp, ht[0]->value, strlen(ht[0]->value));
if (msg_verbose)
msg_info("send attr name %s value %s",
ht[0]->key, ht[0]->value);
/* For convenience, this value requests none of the above.
/* .RE
/* .IP type
-/* The type determines the arguments that follow.
+/* The type argument determines the arguments that follow.
/* .RS
/* .IP "ATTR_TYPE_NUM (char *, int *)"
/* This argument is followed by an attribute name and an integer pointer.
/* This is used for recovering a string array attribute value.
/* Values from the input stream are appended to the array.
/* .IP "ATTR_TYPE_HASH (HTABLE *)"
-/* All further attributes are stored into the given hash table as simple
-/* string-valued attributes, under keys equal to the attribute name.
+/* All further input attributes are required to be simple string or
+/* integer attributes.
+/* Their string values are stored in the specified hash table under
+/* keys equal to the attribute name (obtained from the input stream).
/* Values from the input stream are added to the hash table, but existing
/* hash table entries are not replaced.
/* .sp
-/* N.B. This must be followed by an ATTR_TYPE_END argument.
+/* N.B. This construct must be followed by an ATTR_TYPE_END argument.
/* .IP ATTR_TYPE_END
-/* This terminates the requested attribute list.
+/* This argument terminates the requested attribute list.
/* .RE
+/* BUGS
+/* ATTR_TYPE_HASH accepts attributes with arbitrary names from an
+/* untrusted source. This is safe only if the resulting table is
+/* queried for specific names.
/* DIAGNOSTICS
/* The result value is the number of attributes that were successfully
/* recovered from the input stream (an array-valued attribute counts
#include <vstring.h>
#include <argv.h>
#include <intv.h>
+#include <base64_code.h>
#include <attr.h>
/* Application specific. */
static int attr_scan_string(VSTREAM *fp, VSTRING *plain_buf, const char *context)
{
static VSTRING *base64_buf = 0;
+
+#if 0
extern int var_line_limit; /* XXX */
int limit = var_line_limit * 5 / 4;
+
+#endif
int ch;
if (base64_buf == 0)
return (-1);
}
VSTRING_ADDCH(base64_buf, ch);
+#if 0
if (LEN(base64_buf) > limit) {
msg_warn("string length > %d characters from %s while reading %s",
limit, VSTREAM_PATH(fp), context);
return (-1);
}
+#endif
}
VSTRING_TERMINATE(base64_buf);
- if (BASE64_DECODE(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
+ if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) {
msg_warn("malformed base64 data from %s: %.100s",
VSTREAM_PATH(fp), STR(base64_buf));
return (-1);
"attribute value")) < 0)
return (conversions);
if (ch != '\n') {
- msg_warn("too many values for number attribute %s from %s",
+ msg_warn("multiple values for attribute %s from %s",
STR(name_buf), VSTREAM_PATH(fp));
return (conversions);
}
if ((ch = attr_scan_string(fp, string, "attribute value")) < 0)
return (conversions);
if (ch != '\n') {
- msg_warn("too many values for string attribute %s from %s",
+ msg_warn("multiple values for attribute %s from %s",
STR(name_buf), VSTREAM_PATH(fp));
return (conversions);
}
if ((ch = attr_scan_string(fp, str_buf, "attribute value")) < 0)
return (conversions);
if (ch != '\n') {
- msg_warn("too many values for string attribute %s from %s",
+ msg_warn("multiple values for attribute %s from %s",
STR(name_buf), VSTREAM_PATH(fp));
return (conversions);
}
--- /dev/null
+/*++
+/* NAME
+/* base64_code 3
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/*
+/* VSTRING *base64_encode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* int len;
+/*
+/* VSTRING *base64_decode(result, in, len)
+/* VSTRING *result;
+/* const char *in;
+/* int len;
+/* DESCRIPTION
+/* base64_encode() takes a block of len bytes and encodes it as one
+/* null-terminated string. The result value is the result argument.
+/*
+/* base64_decode() performs the opposite transformation. The result
+/* value is the result argument. The result is null terminated, whether
+/* or not that makes sense.
+/* DIAGNOSTICS
+/* base64_decode () returns a null pointer when the input contains
+/* characters not in the base 64 alphabet.
+/* 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 <mymalloc.h>
+#include <vstring.h>
+#include <base64_code.h>
+
+/* Application-specific. */
+
+static unsigned char to_b64[] =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x))
+
+/* base64_encode - raw data to encoded */
+
+VSTRING *base64_encode(VSTRING *result, const char *in, int len)
+{
+ const unsigned char *cp;
+ int count;
+
+ /*
+ * Encode 3 -> 4.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) {
+ VSTRING_ADDCH(result, to_b64[cp[0] >> 2]);
+ if (count > 1) {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]);
+ if (count > 2) {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]);
+ VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]);
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]);
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ } else {
+ VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]);
+ VSTRING_ADDCH(result, '=');
+ VSTRING_ADDCH(result, '=');
+ break;
+ }
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+/* base64_decode - encoded data to raw */
+
+VSTRING *base64_decode(VSTRING *result, const char *in, int len)
+{
+ static char *un_b64 = 0;
+ const unsigned char *cp;
+ int count;
+ int ch0;
+ int ch1;
+ int ch2;
+ int ch3;
+
+#define CHARS_PER_BYTE 256
+#define INVALID 0xff
+
+ /*
+ * Sanity check.
+ */
+ if (len % 4)
+ return (0);
+
+ /*
+ * Once: initialize the decoding lookup table on the fly.
+ */
+ if (un_b64 == 0) {
+ un_b64 = mymalloc(CHARS_PER_BYTE);
+ memset(un_b64, INVALID, CHARS_PER_BYTE);
+ for (cp = to_b64; cp < to_b64 + sizeof(to_b64); cp++)
+ un_b64[*cp] = cp - to_b64;
+ }
+
+ /*
+ * Decode 4 -> 3.
+ */
+ VSTRING_RESET(result);
+ for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) {
+ if ((ch0 = un_b64[*cp++]) == INVALID
+ || (ch1 = un_b64[*cp++]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4);
+ if ((ch2 = *cp++) == '=')
+ break;
+ if ((ch2 = un_b64[ch2]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2);
+ if ((ch3 = *cp++) == '=')
+ break;
+ if ((ch3 = un_b64[ch3]) == INVALID)
+ return (0);
+ VSTRING_ADDCH(result, ch2 << 6 | ch3);
+ }
+ VSTRING_TERMINATE(result);
+ return (result);
+}
+
+#ifdef TEST
+
+ /*
+ * Proof-of-concept test program: convert to base 64 and back.
+ */
+
+#define STR(x) vstring_str(x)
+#define LEN(x) VSTRING_LEN(x)
+
+int main(int unused_argc, char **unused_argv)
+{
+ VSTRING *b1 = vstring_alloc(1);
+ VSTRING *b2 = vstring_alloc(1);
+ char *test = "this is a test";
+
+#define DECODE(b,s,l) { \
+ if (base64_decode((b),(s),(l)) == 0) \
+ msg_panic("bad base64: %s", (s)); \
+ }
+#define VERIFY(b,t) { \
+ if (strcmp((b), (t)) != 0) \
+ msg_panic("bad test: %s", (b)); \
+ }
+
+ base64_encode(b1, test, strlen(test));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ base64_encode(b1, test, strlen(test));
+ base64_encode(b2, STR(b1), LEN(b1));
+ base64_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ base64_encode(b1, test, strlen(test));
+ base64_encode(b2, STR(b1), LEN(b1));
+ base64_encode(b1, STR(b2), LEN(b2));
+ base64_encode(b2, STR(b1), LEN(b1));
+ base64_encode(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ DECODE(b1, STR(b2), LEN(b2));
+ DECODE(b2, STR(b1), LEN(b1));
+ VERIFY(STR(b2), test);
+
+ vstring_free(b1);
+ vstring_free(b2);
+ return (0);
+}
+
+#endif
--- /dev/null
+#ifndef _BASE64_CODE_H_INCLUDED_
+#define _BASE64_CODE_H_INCLUDED_
+
+/*++
+/* NAME
+/* base64_code 3h
+/* SUMMARY
+/* encode/decode data, base 64 style
+/* SYNOPSIS
+/* #include <base64_code.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern VSTRING *base64_encode(VSTRING *, const char *, int);
+extern VSTRING *base64_decode(VSTRING *, const char *, 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