summaryrefslogtreecommitdiffstats
path: root/src/global/record.c
blob: 80cb1ac3b3c7e3cde7c56ceb720bd09e11832d21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
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"));
}