diff --git a/postfix/HISTORY b/postfix/HISTORY index 827a7fba1..a9b3636bf 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -5490,7 +5490,17 @@ Apologies for any names omitted. 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: diff --git a/postfix/conf/sample-filter.cf b/postfix/conf/sample-filter.cf index 9093de574..af68d4b3d 100644 --- a/postfix/conf/sample-filter.cf +++ b/postfix/conf/sample-filter.cf @@ -13,6 +13,8 @@ # # 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 diff --git a/postfix/src/bounce/bounce.c b/postfix/src/bounce/bounce.c index 8cfcf7380..6e85b0dfe 100644 --- a/postfix/src/bounce/bounce.c +++ b/postfix/src/bounce/bounce.c @@ -142,11 +142,12 @@ static int bounce_append_proto(char *service_name, VSTREAM *client) /* * 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); } @@ -181,11 +182,12 @@ static int bounce_notify_proto(char *service_name, VSTREAM *client, int flush) /* * 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); } @@ -230,7 +232,8 @@ static int bounce_verp_proto(char *service_name, VSTREAM *client, int flush) 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); } diff --git a/postfix/src/cleanup/cleanup.c b/postfix/src/cleanup/cleanup.c index 1802c1a86..cd8091898 100644 --- a/postfix/src/cleanup/cleanup.c +++ b/postfix/src/cleanup/cleanup.c @@ -235,8 +235,10 @@ static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) */ 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. diff --git a/postfix/src/cleanup/cleanup.h b/postfix/src/cleanup/cleanup.h index d5ae9c3cd..5e2f38548 100644 --- a/postfix/src/cleanup/cleanup.h +++ b/postfix/src/cleanup/cleanup.h @@ -59,6 +59,7 @@ typedef struct CLEANUP_STATE { 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; /* @@ -104,6 +105,7 @@ extern void cleanup_state_free(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 **); diff --git a/postfix/src/cleanup/cleanup_api.c b/postfix/src/cleanup/cleanup_api.c index 3d1e4d9cc..37239fbbf 100644 --- a/postfix/src/cleanup/cleanup_api.c +++ b/postfix/src/cleanup/cleanup_api.c @@ -20,6 +20,9 @@ /* /* 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. @@ -27,7 +30,8 @@ /* /* 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. /* @@ -43,10 +47,12 @@ /* 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 @@ -161,6 +167,7 @@ int cleanup_close(CLEANUP_STATE *state) { char *junk; int status; + const char *reason; /* * See if there are any errors. For example, the message is incomplete, @@ -181,7 +188,7 @@ int cleanup_close(CLEANUP_STATE *state) * 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; @@ -216,11 +223,14 @@ int cleanup_close(CLEANUP_STATE *state) 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; @@ -249,6 +259,12 @@ int cleanup_close(CLEANUP_STATE *state) 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); +} diff --git a/postfix/src/cleanup/cleanup_message.c b/postfix/src/cleanup/cleanup_message.c index 2944e75da..6843d6c35 100644 --- a/postfix/src/cleanup/cleanup_message.c +++ b/postfix/src/cleanup/cleanup_message.c @@ -254,6 +254,38 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts) 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) @@ -267,12 +299,13 @@ 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; @@ -547,12 +580,13 @@ static void cleanup_message_body(CLEANUP_STATE *state, int type, char *buf, int */ 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; diff --git a/postfix/src/cleanup/cleanup_state.c b/postfix/src/cleanup/cleanup_state.c index c7e5b74b6..615238e48 100644 --- a/postfix/src/cleanup/cleanup_state.c +++ b/postfix/src/cleanup/cleanup_state.c @@ -84,6 +84,7 @@ CLEANUP_STATE *cleanup_state_alloc(void) state->xtra_offset = -1; state->end_seen = 0; state->rcpt_count = 0; + state->why_rejected = 0; return (state); } @@ -113,5 +114,7 @@ void cleanup_state_free(CLEANUP_STATE *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); } diff --git a/postfix/src/flush/flush.c b/postfix/src/flush/flush.c index ffabf3329..8cfb68a5e 100644 --- a/postfix/src/flush/flush.c +++ b/postfix/src/flush/flush.c @@ -547,8 +547,8 @@ static void flush_service(VSTREAM *client_stream, char *unused_service, 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)); diff --git a/postfix/src/global/bounce.c b/postfix/src/global/bounce.c index 044c7b8e4..805d4c787 100644 --- a/postfix/src/global/bounce.c +++ b/postfix/src/global/bounce.c @@ -144,7 +144,7 @@ int vbounce_append(int flags, const char *id, const char *recipient, 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, @@ -177,7 +177,7 @@ int bounce_flush(int flags, const char *queue, const char *id, 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, diff --git a/postfix/src/global/defer.c b/postfix/src/global/defer.c index 6eaa2bd8e..40130cfe3 100644 --- a/postfix/src/global/defer.c +++ b/postfix/src/global/defer.c @@ -146,7 +146,7 @@ int vdefer_append(int flags, const char *id, const char *recipient, 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, @@ -180,7 +180,7 @@ int defer_flush(int flags, const char *queue, const char *id, 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, @@ -199,7 +199,7 @@ int defer_warn(int flags, const char *queue, const char *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, diff --git a/postfix/src/global/mail_attr.h b/postfix/src/global/mail_attr.h new file mode 100644 index 000000000..8e1d706ab --- /dev/null +++ b/postfix/src/global/mail_attr.h @@ -0,0 +1,39 @@ +#ifndef _MAIL_ATTR_H_INCLUDED_ +#define _MAIL_ATTR_H_INCLUDED_ + +/*++ +/* NAME +/* mail_attr 3h +/* SUMMARY +/* mail internal IPC support +/* SYNOPSIS +/* #include +/* 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 diff --git a/postfix/src/global/mail_command_read.c b/postfix/src/global/mail_command_read.c new file mode 100644 index 000000000..74c7def61 --- /dev/null +++ b/postfix/src/global/mail_command_read.c @@ -0,0 +1,69 @@ +/*++ +/* NAME +/* mail_command_read 3 +/* SUMMARY +/* single-command server +/* SYNOPSIS +/* #include +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include +#include + +/* Utility library. */ + +#include +#include + +/* 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); +} diff --git a/postfix/src/global/mail_command_write.c b/postfix/src/global/mail_command_write.c new file mode 100644 index 000000000..e6e855788 --- /dev/null +++ b/postfix/src/global/mail_command_write.c @@ -0,0 +1,82 @@ +/*++ +/* NAME +/* mail_command_write 3 +/* SUMMARY +/* single-command client +/* SYNOPSIS +/* #include +/* +/* 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 +#include /* 44BSD stdarg.h uses abort() */ +#include + +/* Utility library. */ + +#include + +/* 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); +} diff --git a/postfix/src/global/mail_connect.c b/postfix/src/global/mail_connect.c index cd486f9bc..3e00d9838 100644 --- a/postfix/src/global/mail_connect.c +++ b/postfix/src/global/mail_connect.c @@ -66,6 +66,7 @@ #include #include #include +#include /* Global library. */ @@ -79,6 +80,7 @@ VSTREAM *mail_connect(const char *class, const char *name, int block_mode) char *path; VSTREAM *stream; int fd; + char *sock_name; path = mail_pathname(class, name); if ((fd = LOCAL_CONNECT(path, block_mode, 0)) < 0) { @@ -90,9 +92,11 @@ VSTREAM *mail_connect(const char *class, const char *name, int block_mode) 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); diff --git a/postfix/src/global/mail_stream.c b/postfix/src/global/mail_stream.c index b624e7ff3..02d50707b 100644 --- a/postfix/src/global/mail_stream.c +++ b/postfix/src/global/mail_stream.c @@ -116,7 +116,7 @@ void mail_stream_cleanup(MAIL_STREAM * info) /* 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}; @@ -161,7 +161,7 @@ static int mail_stream_finish_file(MAIL_STREAM * info) /* 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; @@ -169,7 +169,9 @@ static int mail_stream_finish_ipc(MAIL_STREAM * info) * 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; /* @@ -181,9 +183,9 @@ static int mail_stream_finish_ipc(MAIL_STREAM * info) /* 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 */ diff --git a/postfix/src/global/mail_stream.h b/postfix/src/global/mail_stream.h index 3ae6c624a..8a3286661 100644 --- a/postfix/src/global/mail_stream.h +++ b/postfix/src/global/mail_stream.h @@ -22,7 +22,7 @@ */ 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 { @@ -38,7 +38,7 @@ extern MAIL_STREAM *mail_stream_file(const char *, const char *, const char *); 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 diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 0a2b83bd4..6508da3b3 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -15,7 +15,7 @@ * 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 diff --git a/postfix/src/postdrop/postdrop.c b/postfix/src/postdrop/postdrop.c index 8a8e974ea..07475df6b 100644 --- a/postfix/src/postdrop/postdrop.c +++ b/postfix/src/postdrop/postdrop.c @@ -289,7 +289,7 @@ int main(int argc, char **argv) /* * 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)); /* diff --git a/postfix/src/qmqpd/qmqpd.c b/postfix/src/qmqpd/qmqpd.c index 85a122847..f1d599578 100644 --- a/postfix/src/qmqpd/qmqpd.c +++ b/postfix/src/qmqpd/qmqpd.c @@ -394,7 +394,7 @@ static void qmqpd_close_file(QMQPD_STATE *state) * 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; @@ -453,7 +453,7 @@ static int qmqpd_send_status(QMQPD_STATE *state) "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"); diff --git a/postfix/src/qmqpd/qmqpd.h b/postfix/src/qmqpd/qmqpd.h index 9bccc7496..aaacb4242 100644 --- a/postfix/src/qmqpd/qmqpd.h +++ b/postfix/src/qmqpd/qmqpd.h @@ -45,6 +45,7 @@ typedef struct { char *recipient; /* recipient address */ char *protocol; /* protocol name */ char *where; /* protocol state */ + VSTRING *why_rejected; /* REJECT reason */ } QMQPD_STATE; /* diff --git a/postfix/src/qmqpd/qmqpd_state.c b/postfix/src/qmqpd/qmqpd_state.c index f4eaf27f6..8e5b46c9c 100644 --- a/postfix/src/qmqpd/qmqpd_state.c +++ b/postfix/src/qmqpd/qmqpd_state.c @@ -74,6 +74,7 @@ QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream) state->recipient = 0; state->protocol = "QMQP"; state->where = "initializing client connection"; + state->why_rejected = vstring_alloc(10); return (state); } @@ -92,5 +93,6 @@ void qmqpd_state_free(QMQPD_STATE *state) myfree(state->sender); if (state->recipient) myfree(state->recipient); + vstring_free(state->why_rejected); myfree((char *) state); } diff --git a/postfix/src/sendmail/sendmail.c b/postfix/src/sendmail/sendmail.c index 1c7984f40..694e99a55 100644 --- a/postfix/src/sendmail/sendmail.c +++ b/postfix/src/sendmail/sendmail.c @@ -516,7 +516,7 @@ static void enqueue(const int flags, const char *sender, const char *full_name, 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) { diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index 67f61bead..0ecd36fcd 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -891,6 +891,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) 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 @@ -999,7 +1000,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) * 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; @@ -1040,7 +1041,7 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) 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"); @@ -1062,6 +1063,8 @@ static int data_cmd(SMTPD_STATE *state, int argc, SMTPD_TOKEN *unused_argv) */ mail_reset(state); rcpt_reset(state); + if (why) + vstring_free(why); return (state->err); } diff --git a/postfix/src/util/Makefile.in b/postfix/src/util/Makefile.in index f3fab48e7..0d51b9b21 100644 --- a/postfix/src/util/Makefile.in +++ b/postfix/src/util/Makefile.in @@ -24,7 +24,7 @@ SRCS = argv.c argv_split.c attr.c basename.c binhash.c chroot_uid.c \ 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 \ @@ -50,7 +50,7 @@ OBJS = argv.o argv_split.o attr.o basename.o binhash.o chroot_uid.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 \ @@ -67,7 +67,7 @@ HDRS = argv.h attr.h binhash.h chroot_uid.h connect.h dict.h dict_db.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 \ @@ -84,7 +84,7 @@ TESTPROG= dict_open dup2_pass_on_exec events exec_command fifo_open \ 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 @@ -303,6 +303,11 @@ attr_table: $(LIB) $@.o $(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 \ @@ -315,7 +320,7 @@ stream_test: stream_test.c $(LIB) $(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.tmp @@ -348,6 +353,9 @@ inet_addr_list_test: inet_addr_list 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 @@ -387,6 +395,8 @@ attr_print.o: mymalloc.h 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 @@ -397,6 +407,7 @@ attr_scan.o: vbuf.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 @@ -411,6 +422,13 @@ attr_table.o: vstring_vstream.h 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 diff --git a/postfix/src/util/attr_print.c b/postfix/src/util/attr_print.c index 15fecbcac..4680747e0 100644 --- a/postfix/src/util/attr_print.c +++ b/postfix/src/util/attr_print.c @@ -92,17 +92,34 @@ #include #include #include +#include #include -/* 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 */ @@ -136,46 +153,52 @@ int attr_vprint(VSTREAM *fp, int flags, va_list ap) 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); diff --git a/postfix/src/util/attr_scan.c b/postfix/src/util/attr_scan.c index 392fee766..3039f1a94 100644 --- a/postfix/src/util/attr_scan.c +++ b/postfix/src/util/attr_scan.c @@ -83,7 +83,7 @@ /* 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. @@ -100,15 +100,21 @@ /* 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 @@ -143,6 +149,7 @@ #include #include #include +#include #include /* Application specific. */ @@ -155,8 +162,12 @@ 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) @@ -170,14 +181,16 @@ static int attr_scan_string(VSTREAM *fp, VSTRING *plain_buf, const char *context 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); @@ -335,7 +348,7 @@ int attr_vscan(VSTREAM *fp, int flags, va_list ap) "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); } @@ -350,7 +363,7 @@ int attr_vscan(VSTREAM *fp, int flags, va_list ap) 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); } @@ -364,7 +377,7 @@ int attr_vscan(VSTREAM *fp, int flags, va_list ap) 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); } diff --git a/postfix/src/util/base64_code.c b/postfix/src/util/base64_code.c new file mode 100644 index 000000000..443bf43c6 --- /dev/null +++ b/postfix/src/util/base64_code.c @@ -0,0 +1,200 @@ +/*++ +/* NAME +/* base64_code 3 +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include +/* +/* 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 + +/* Utility library. */ + +#include +#include +#include +#include + +/* 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 diff --git a/postfix/src/util/base64_code.h b/postfix/src/util/base64_code.h new file mode 100644 index 000000000..d018947e1 --- /dev/null +++ b/postfix/src/util/base64_code.h @@ -0,0 +1,36 @@ +#ifndef _BASE64_CODE_H_INCLUDED_ +#define _BASE64_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base64_code 3h +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include + + /* + * 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