diff options
Diffstat (limited to '')
-rw-r--r-- | src/global/record.c | 415 |
1 files changed, 415 insertions, 0 deletions
diff --git a/src/global/record.c b/src/global/record.c new file mode 100644 index 0000000..80cb1ac --- /dev/null +++ b/src/global/record.c @@ -0,0 +1,415 @@ +/*++ +/* NAME +/* record 3 +/* SUMMARY +/* simple typed record I/O +/* SYNOPSIS +/* #include <record.h> +/* +/* int rec_get(stream, buf, maxsize) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t maxsize; +/* +/* int rec_get_raw(stream, buf, maxsize, flags) +/* VSTREAM *stream; +/* VSTRING *buf; +/* ssize_t maxsize; +/* int flags; +/* +/* int rec_put(stream, type, data, len) +/* VSTREAM *stream; +/* int type; +/* const char *data; +/* ssize_t len; +/* AUXILIARY FUNCTIONS +/* int rec_put_type(stream, type, offset) +/* VSTREAM *stream; +/* int type; +/* long offset; +/* +/* int rec_fprintf(stream, type, format, ...) +/* VSTREAM *stream; +/* int type; +/* const char *format; +/* +/* int rec_fputs(stream, type, str) +/* VSTREAM *stream; +/* int type; +/* const char *str; +/* +/* int REC_PUT_BUF(stream, type, buf) +/* VSTREAM *stream; +/* int type; +/* VSTRING *buf; +/* +/* int rec_vfprintf(stream, type, format, ap) +/* VSTREAM *stream; +/* int type; +/* const char *format; +/* va_list ap; +/* +/* int rec_goto(stream, where) +/* VSTREAM *stream; +/* const char *where; +/* +/* int rec_pad(stream, type, len) +/* VSTREAM *stream; +/* int type; +/* ssize_t len; +/* +/* REC_SPACE_NEED(buflen, reclen) +/* ssize_t buflen; +/* ssize_t reclen; +/* +/* REC_GET_HIDDEN_TYPE(type) +/* int type; +/* DESCRIPTION +/* This module reads and writes typed variable-length records. +/* Each record contains a 1-byte type code (0..255), a length +/* (1 or more bytes) and as much data as the length specifies. +/* +/* rec_get_raw() retrieves a record from the named record stream +/* and returns the record type. The \fImaxsize\fR argument is +/* zero, or specifies a maximal acceptable record length. +/* The result is REC_TYPE_EOF when the end of the file was reached, +/* and REC_TYPE_ERROR in case of a bad record. The result buffer is +/* null-terminated for convenience. Records may contain embedded +/* null characters. The \fIflags\fR argument specifies zero or +/* more of the following: +/* .IP REC_FLAG_FOLLOW_PTR +/* Follow PTR records, instead of exposing them to the application. +/* .IP REC_FLAG_SKIP_DTXT +/* Skip "deleted text" records, instead of exposing them to +/* the application. +/* .IP REC_FLAG_SEEK_END +/* Seek to the end-of-file upon reading a REC_TYPE_END record. +/* .PP +/* Specify REC_FLAG_NONE to request no special processing, +/* and REC_FLAG_DEFAULT for normal use. +/* +/* rec_get() is a wrapper around rec_get_raw() that always +/* enables the REC_FLAG_FOLLOW_PTR, REC_FLAG_SKIP_DTXT +/* and REC_FLAG_SEEK_END features. +/* +/* REC_GET_HIDDEN_TYPE() is an unsafe macro that returns +/* non-zero when the specified record type is "not exposed" +/* by rec_get(). +/* +/* rec_put() stores the specified record and returns the record +/* type, or REC_TYPE_ERROR in case of problems. +/* +/* rec_put_type() updates the type field of the record at the +/* specified file offset. The result is the new record type, +/* or REC_TYPE_ERROR in case of trouble. +/* +/* rec_fprintf() and rec_vfprintf() format their arguments and +/* write the result to the named stream. The result is the same +/* as with rec_put(). +/* +/* rec_fputs() writes a record with as contents a copy of the +/* specified string. The result is the same as with rec_put(). +/* +/* REC_PUT_BUF() is a wrapper for rec_put() that makes it +/* easier to handle VSTRING buffers. It is an unsafe macro +/* that evaluates some arguments more than once. +/* +/* rec_goto() takes the argument of a pointer record and moves +/* the file pointer to the specified location. A zero position +/* means do nothing. The result is REC_TYPE_ERROR in case of +/* failure. +/* +/* rec_pad() writes a record that occupies the larger of (the +/* specified amount) or (an implementation-defined minimum). +/* +/* REC_SPACE_NEED(buflen, reclen) converts the specified buffer +/* length into a record length. This macro modifies its second +/* argument. +/* DIAGNOSTICS +/* Panics: interface violations. Fatal errors: insufficient memory. +/* Warnings: corrupted file. +/* 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 +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <unistd.h> +#include <string.h> + +#ifndef NBBY +#define NBBY 8 /* XXX should be in sys_defs.h */ +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <stringops.h> + +/* Global library. */ + +#include <off_cvt.h> +#include <rec_type.h> +#include <record.h> + +/* rec_put_type - update record type field */ + +int rec_put_type(VSTREAM *stream, int type, off_t offset) +{ + if (type < 0 || type > 255) + msg_panic("rec_put_type: bad record type %d", type); + + if (msg_verbose > 2) + msg_info("rec_put_type: %d at %ld", type, (long) offset); + + if (vstream_fseek(stream, offset, SEEK_SET) < 0 + || VSTREAM_PUTC(type, stream) != type) { + msg_warn("%s: seek or write error", VSTREAM_PATH(stream)); + return (REC_TYPE_ERROR); + } else { + return (type); + } +} + +/* rec_put - store typed record */ + +int rec_put(VSTREAM *stream, int type, const char *data, ssize_t len) +{ + ssize_t len_rest; + int len_byte; + + if (type < 0 || type > 255) + msg_panic("rec_put: bad record type %d", type); + + if (msg_verbose > 2) + msg_info("rec_put: type %c len %ld data %.10s", + type, (long) len, data); + + /* + * Write the record type, one byte. + */ + if (VSTREAM_PUTC(type, stream) == VSTREAM_EOF) + return (REC_TYPE_ERROR); + + /* + * Write the record data length in 7-bit portions, using the 8th bit to + * indicate that there is more. Use as many length bytes as needed. + */ + len_rest = len; + do { + len_byte = len_rest & 0177; + if (len_rest >>= 7U) + len_byte |= 0200; + if (VSTREAM_PUTC(len_byte, stream) == VSTREAM_EOF) { + return (REC_TYPE_ERROR); + } + } while (len_rest != 0); + + /* + * Write the record data portion. Use as many length bytes as needed. + */ + if (len && vstream_fwrite(stream, data, len) != len) + return (REC_TYPE_ERROR); + return (type); +} + +/* rec_get_raw - retrieve typed record */ + +int rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags) +{ + const char *myname = "rec_get"; + int type; + ssize_t len; + int len_byte; + unsigned shift; + + /* + * Sanity check. + */ + if (maxsize < 0) + msg_panic("%s: bad record size limit: %ld", myname, (long) maxsize); + + for (;;) { + + /* + * Extract the record type. + */ + if ((type = VSTREAM_GETC(stream)) == VSTREAM_EOF) + return (REC_TYPE_EOF); + + /* + * Find out the record data length. Return an error result when the + * record data length is malformed or when it exceeds the acceptable + * limit. + */ + for (len = 0, shift = 0; /* void */ ; shift += 7) { + if (shift >= (int) (NBBY * sizeof(int))) { + msg_warn("%s: too many length bits, record type %d", + VSTREAM_PATH(stream), type); + return (REC_TYPE_ERROR); + } + if ((len_byte = VSTREAM_GETC(stream)) == VSTREAM_EOF) { + msg_warn("%s: unexpected EOF reading length, record type %d", + VSTREAM_PATH(stream), type); + return (REC_TYPE_ERROR); + } + len |= (len_byte & 0177) << shift; + if ((len_byte & 0200) == 0) + break; + } + if (len < 0 || (maxsize > 0 && len > maxsize)) { + msg_warn("%s: illegal length %ld, record type %d", + VSTREAM_PATH(stream), (long) len, type); + while (len-- > 0 && VSTREAM_GETC(stream) != VSTREAM_EOF) + /* void */ ; + return (REC_TYPE_ERROR); + } + + /* + * Reserve buffer space for the result, and read the record data into + * the buffer. + */ + if (vstream_fread_buf(stream, buf, len) != len) { + msg_warn("%s: unexpected EOF in data, record type %d length %ld", + VSTREAM_PATH(stream), type, (long) len); + return (REC_TYPE_ERROR); + } + VSTRING_TERMINATE(buf); + if (msg_verbose > 2) + msg_info("%s: type %c len %ld data %.10s", myname, + type, (long) len, vstring_str(buf)); + + /* + * Transparency options. + */ + if (flags == 0) + break; + if (type == REC_TYPE_PTR && (flags & REC_FLAG_FOLLOW_PTR) != 0 + && (type = rec_goto(stream, vstring_str(buf))) != REC_TYPE_ERROR) + continue; + if (type == REC_TYPE_DTXT && (flags & REC_FLAG_SKIP_DTXT) != 0) + continue; + if (type == REC_TYPE_END && (flags & REC_FLAG_SEEK_END) != 0 + && vstream_fseek(stream, (off_t) 0, SEEK_END) < 0) { + msg_warn("%s: seek error after reading END record: %m", + VSTREAM_PATH(stream)); + return (REC_TYPE_ERROR); + } + break; + } + return (type); +} + +/* rec_goto - follow PTR record */ + +int rec_goto(VSTREAM *stream, const char *buf) +{ + off_t offset; + static char *saved_path; + static off_t saved_offset; + static int reverse_count; + + /* + * Crude workaround for queue file loops. VSTREAMs currently have no + * option to attach application-specific data, so we use global state and + * simple logic to detect if an application switches streams. We trigger + * on reverse jumps only. There's one reverse jump for every inserted + * header, but only one reverse jump for all appended recipients. No-one + * is likely to insert 10000 message headers, but someone might append + * 10000 recipients. + */ +#define REVERSE_JUMP_LIMIT 10000 + + if (saved_path == 0 || strcmp(saved_path, VSTREAM_PATH(stream)) != 0) { + if (saved_path) + myfree(saved_path); + saved_path = mystrdup(VSTREAM_PATH(stream)); + reverse_count = 0; + saved_offset = 0; + } + while (ISSPACE(*buf)) + buf++; + if ((offset = off_cvt_string(buf)) < 0) { + msg_warn("%s: malformed pointer record value: %s", + VSTREAM_PATH(stream), buf); + return (REC_TYPE_ERROR); + } else if (offset == 0) { + /* Dummy record. */ + return (0); + } else if (offset <= saved_offset && ++reverse_count > REVERSE_JUMP_LIMIT) { + msg_warn("%s: too many reverse jump records", VSTREAM_PATH(stream)); + return (REC_TYPE_ERROR); + } else if (vstream_fseek(stream, offset, SEEK_SET) < 0) { + msg_warn("%s: seek error after pointer record: %m", + VSTREAM_PATH(stream)); + return (REC_TYPE_ERROR); + } else { + saved_offset = offset; + return (0); + } +} + +/* rec_vfprintf - write formatted string to record */ + +int rec_vfprintf(VSTREAM *stream, int type, const char *format, va_list ap) +{ + static VSTRING *vp; + + if (vp == 0) + vp = vstring_alloc(100); + + /* + * Writing a formatted string involves an extra copy, because we must + * know the record length before we can write it. + */ + vstring_vsprintf(vp, format, ap); + return (REC_PUT_BUF(stream, type, vp)); +} + +/* rec_fprintf - write formatted string to record */ + +int rec_fprintf(VSTREAM *stream, int type, const char *format,...) +{ + int result; + va_list ap; + + va_start(ap, format); + result = rec_vfprintf(stream, type, format, ap); + va_end(ap); + return (result); +} + +/* rec_fputs - write string to record */ + +int rec_fputs(VSTREAM *stream, int type, const char *str) +{ + return (rec_put(stream, type, str, str ? strlen(str) : 0)); +} + +/* rec_pad - write padding record */ + +int rec_pad(VSTREAM *stream, int type, ssize_t len) +{ + int width = len - 2; /* type + length */ + + return (rec_fprintf(stream, type, "%*s", + width < 1 ? 1 : width, "0")); +} |