1 /*	$NetBSD: record.c,v 1.1.1.2 2010/06/17 18:06:51 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	record 3
6 /* SUMMARY
7 /*	simple typed record I/O
8 /* SYNOPSIS
9 /*	#include <record.h>
10 /*
11 /*	int	rec_get(stream, buf, maxsize)
12 /*	VSTREAM	*stream;
13 /*	VSTRING	*buf;
14 /*	ssize_t	maxsize;
15 /*
16 /*	int	rec_get_raw(stream, buf, maxsize, flags)
17 /*	VSTREAM	*stream;
18 /*	VSTRING	*buf;
19 /*	ssize_t	maxsize;
20 /*	int	flags;
21 /*
22 /*	int	rec_put(stream, type, data, len)
23 /*	VSTREAM	*stream;
24 /*	int	type;
25 /*	const char *data;
26 /*	ssize_t	len;
27 /* AUXILIARY FUNCTIONS
28 /*	int	rec_put_type(stream, type, offset)
29 /*	VSTREAM	*stream;
30 /*	int	type;
31 /*	long	offset;
32 /*
33 /*	int	rec_fprintf(stream, type, format, ...)
34 /*	VSTREAM	*stream;
35 /*	int	type;
36 /*	const char *format;
37 /*
38 /*	int	rec_fputs(stream, type, str)
39 /*	VSTREAM	*stream;
40 /*	int	type;
41 /*	const char *str;
42 /*
43 /*	int	REC_PUT_BUF(stream, type, buf)
44 /*	VSTREAM	*stream;
45 /*	int	type;
46 /*	VSTRING	*buf;
47 /*
48 /*	int	rec_vfprintf(stream, type, format, ap)
49 /*	VSTREAM	*stream;
50 /*	int	type;
51 /*	const char *format;
52 /*	va_list	ap;
53 /*
54 /*	int	rec_goto(stream, where)
55 /*	VSTREAM	*stream;
56 /*	const char *where;
57 /*
58 /*	int	rec_pad(stream, type, len)
59 /*	VSTREAM *stream;
60 /*	int	type;
61 /*	int	len;
62 /*
63 /*	REC_SPACE_NEED(buflen, reclen)
64 /*	ssize_t	buflen;
65 /*	ssize_t	reclen;
66 /*
67 /*	REC_GET_HIDDEN_TYPE(type)
68 /*	int	type;
69 /* DESCRIPTION
70 /*	This module reads and writes typed variable-length records.
71 /*	Each record contains a 1-byte type code (0..255), a length
72 /*	(1 or more bytes) and as much data as the length specifies.
73 /*
74 /*	rec_get_raw() retrieves a record from the named record stream
75 /*	and returns the record type. The \fImaxsize\fR argument is
76 /*	zero, or specifies a maximal acceptable record length.
77 /*	The result is REC_TYPE_EOF when the end of the file was reached,
78 /*	and REC_TYPE_ERROR in case of a bad record. The result buffer is
79 /*	null-terminated for convenience. Records may contain embedded
80 /*	null characters. The \fIflags\fR argument specifies zero or
81 /*	more of the following:
82 /* .IP REC_FLAG_FOLLOW_PTR
83 /*	Follow PTR records, instead of exposing them to the application.
84 /* .IP REC_FLAG_SKIP_DTXT
85 /*	Skip "deleted text" records, instead of exposing them to
86 /*	the application.
87 /* .IP REC_FLAG_SEEK_END
88 /*	Seek to the end-of-file upon reading a REC_TYPE_END record.
89 /* .PP
90 /*	Specify REC_FLAG_NONE to request no special processing,
91 /*	and REC_FLAG_DEFAULT for normal use.
92 /*
93 /*	rec_get() is a wrapper around rec_get_raw() that always
94 /*	enables the REC_FLAG_FOLLOW_PTR, REC_FLAG_SKIP_DTXT
95 /*	and REC_FLAG_SEEK_END features.
96 /*
97 /*	REC_GET_HIDDEN_TYPE() is an unsafe macro that returns
98 /*	non-zero when the specified record type is "not exposed"
99 /*	by rec_get().
100 /*
101 /*	rec_put() stores the specified record and returns the record
102 /*	type, or REC_TYPE_ERROR in case of problems.
103 /*
104 /*	rec_put_type() updates the type field of the record at the
105 /*	specified file offset.  The result is the new record type,
106 /*	or REC_TYPE_ERROR in case of trouble.
107 /*
108 /*	rec_fprintf() and rec_vfprintf() format their arguments and
109 /*	write the result to the named stream. The result is the same
110 /*	as with rec_put().
111 /*
112 /*	rec_fputs() writes a record with as contents a copy of the
113 /*	specified string. The result is the same as with rec_put().
114 /*
115 /*	REC_PUT_BUF() is a wrapper for rec_put() that makes it
116 /*	easier to handle VSTRING buffers. It is an unsafe macro
117 /*	that evaluates some arguments more than once.
118 /*
119 /*	rec_goto() takes the argument of a pointer record and moves
120 /*	the file pointer to the specified location. A zero position
121 /*	means do nothing. The result is REC_TYPE_ERROR in case of
122 /*	failure.
123 /*
124 /*	rec_pad() writes a record that occupies the larger of (the
125 /*	specified amount) or (an implementation-defined minimum).
126 /*
127 /*	REC_SPACE_NEED(buflen, reclen) converts the specified buffer
128 /*	length into a record length. This macro modifies its second
129 /*	argument.
130 /* DIAGNOSTICS
131 /*	Panics: interface violations. Fatal errors: insufficient memory.
132 /*	Warnings: corrupted file.
133 /* LICENSE
134 /* .ad
135 /* .fi
136 /*	The Secure Mailer license must be distributed with this software.
137 /* AUTHOR(S)
138 /*	Wietse Venema
139 /*	IBM T.J. Watson Research
140 /*	P.O. Box 704
141 /*	Yorktown Heights, NY 10598, USA
142 /*--*/
143 
144 /* System library. */
145 
146 #include <sys_defs.h>
147 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
148 #include <stdarg.h>
149 #include <unistd.h>
150 #include <string.h>
151 
152 #ifndef NBBY
153 #define NBBY 8				/* XXX should be in sys_defs.h */
154 #endif
155 
156 /* Utility library. */
157 
158 #include <msg.h>
159 #include <mymalloc.h>
160 #include <vstream.h>
161 #include <vstring.h>
162 #include <stringops.h>
163 
164 /* Global library. */
165 
166 #include <off_cvt.h>
167 #include <rec_type.h>
168 #include <record.h>
169 
170 /* rec_put_type - update record type field */
171 
172 int     rec_put_type(VSTREAM *stream, int type, off_t offset)
173 {
174     if (type < 0 || type > 255)
175 	msg_panic("rec_put_type: bad record type %d", type);
176 
177     if (msg_verbose > 2)
178 	msg_info("rec_put_type: %d at %ld", type, (long) offset);
179 
180     if (vstream_fseek(stream, offset, SEEK_SET) < 0
181 	|| VSTREAM_PUTC(type, stream) != type) {
182 	return (REC_TYPE_ERROR);
183     } else {
184 	return (type);
185     }
186 }
187 
188 /* rec_put - store typed record */
189 
190 int     rec_put(VSTREAM *stream, int type, const char *data, ssize_t len)
191 {
192     ssize_t len_rest;
193     int     len_byte;
194 
195     if (type < 0 || type > 255)
196 	msg_panic("rec_put: bad record type %d", type);
197 
198     if (msg_verbose > 2)
199 	msg_info("rec_put: type %c len %ld data %.10s",
200 		 type, (long) len, data);
201 
202     /*
203      * Write the record type, one byte.
204      */
205     if (VSTREAM_PUTC(type, stream) == VSTREAM_EOF)
206 	return (REC_TYPE_ERROR);
207 
208     /*
209      * Write the record data length in 7-bit portions, using the 8th bit to
210      * indicate that there is more. Use as many length bytes as needed.
211      */
212     len_rest = len;
213     do {
214 	len_byte = len_rest & 0177;
215 	if (len_rest >>= 7U)
216 	    len_byte |= 0200;
217 	if (VSTREAM_PUTC(len_byte, stream) == VSTREAM_EOF) {
218 	    return (REC_TYPE_ERROR);
219 	}
220     } while (len_rest != 0);
221 
222     /*
223      * Write the record data portion. Use as many length bytes as needed.
224      */
225     if (len && vstream_fwrite(stream, data, len) != len)
226 	return (REC_TYPE_ERROR);
227     return (type);
228 }
229 
230 /* rec_get_raw - retrieve typed record */
231 
232 int     rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags)
233 {
234     const char *myname = "rec_get";
235     int     type;
236     ssize_t len;
237     int     len_byte;
238     unsigned shift;
239 
240     /*
241      * Sanity check.
242      */
243     if (maxsize < 0)
244 	msg_panic("%s: bad record size limit: %ld", myname, (long) maxsize);
245 
246     for (;;) {
247 
248 	/*
249 	 * Extract the record type.
250 	 */
251 	if ((type = VSTREAM_GETC(stream)) == VSTREAM_EOF)
252 	    return (REC_TYPE_EOF);
253 
254 	/*
255 	 * Find out the record data length. Return an error result when the
256 	 * record data length is malformed or when it exceeds the acceptable
257 	 * limit.
258 	 */
259 	for (len = 0, shift = 0; /* void */ ; shift += 7) {
260 	    if (shift >= (int) (NBBY * sizeof(int))) {
261 		msg_warn("%s: too many length bits, record type %d",
262 			 VSTREAM_PATH(stream), type);
263 		return (REC_TYPE_ERROR);
264 	    }
265 	    if ((len_byte = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
266 		msg_warn("%s: unexpected EOF reading length, record type %d",
267 			 VSTREAM_PATH(stream), type);
268 		return (REC_TYPE_ERROR);
269 	    }
270 	    len |= (len_byte & 0177) << shift;
271 	    if ((len_byte & 0200) == 0)
272 		break;
273 	}
274 	if (len < 0 || (maxsize > 0 && len > maxsize)) {
275 	    msg_warn("%s: illegal length %ld, record type %d",
276 		     VSTREAM_PATH(stream), (long) len, type);
277 	    while (len-- > 0 && VSTREAM_GETC(stream) != VSTREAM_EOF)
278 		 /* void */ ;
279 	    return (REC_TYPE_ERROR);
280 	}
281 
282 	/*
283 	 * Reserve buffer space for the result, and read the record data into
284 	 * the buffer.
285 	 */
286 	VSTRING_RESET(buf);
287 	VSTRING_SPACE(buf, len);
288 	if (vstream_fread(stream, vstring_str(buf), len) != len) {
289 	    msg_warn("%s: unexpected EOF in data, record type %d length %ld",
290 		     VSTREAM_PATH(stream), type, (long) len);
291 	    return (REC_TYPE_ERROR);
292 	}
293 	VSTRING_AT_OFFSET(buf, len);
294 	VSTRING_TERMINATE(buf);
295 	if (msg_verbose > 2)
296 	    msg_info("%s: type %c len %ld data %.10s", myname,
297 		     type, (long) len, vstring_str(buf));
298 
299 	/*
300 	 * Transparency options.
301 	 */
302 	if (flags == 0)
303 	    break;
304 	if (type == REC_TYPE_PTR && (flags & REC_FLAG_FOLLOW_PTR) != 0
305 	    && (type = rec_goto(stream, vstring_str(buf))) != REC_TYPE_ERROR)
306 	    continue;
307 	if (type == REC_TYPE_DTXT && (flags & REC_FLAG_SKIP_DTXT) != 0)
308 	    continue;
309 	if (type == REC_TYPE_END && (flags & REC_FLAG_SEEK_END) != 0)
310 	    (void) vstream_fseek(stream, (off_t) 0, SEEK_END);
311 	break;
312     }
313     return (type);
314 }
315 
316 /* rec_goto - follow PTR record */
317 
318 int     rec_goto(VSTREAM *stream, const char *buf)
319 {
320     off_t   offset;
321     static const char *saved_path;
322     static off_t saved_offset;
323     static int reverse_count;
324 
325     /*
326      * Crude workaround for queue file loops. VSTREAMs currently have no
327      * option to attach application-specific data, so we use global state and
328      * simple logic to detect if an application switches streams. We trigger
329      * on reverse jumps only. There's one reverse jump for every inserted
330      * header, but only one reverse jump for all appended recipients. No-one
331      * is likely to insert 10000 message headers, but someone might append
332      * 10000 recipients.
333      */
334 #define STREQ(x,y) ((x) == (y) && strcmp((x), (y)) == 0)
335 #define REVERSE_JUMP_LIMIT	10000
336 
337     if (!STREQ(saved_path, VSTREAM_PATH(stream))) {
338 	saved_path = VSTREAM_PATH(stream);
339 	reverse_count = 0;
340 	saved_offset = 0;
341     }
342     while (ISSPACE(*buf))
343 	buf++;
344     if ((offset = off_cvt_string(buf)) < 0) {
345 	msg_warn("%s: malformed pointer record value: %s",
346 		 VSTREAM_PATH(stream), buf);
347 	return (REC_TYPE_ERROR);
348     } else if (offset == 0) {
349 	/* Dummy record. */
350 	return (0);
351     } else if (offset <= saved_offset && ++reverse_count > REVERSE_JUMP_LIMIT) {
352 	msg_warn("%s: too many reverse jump records", VSTREAM_PATH(stream));
353 	return (REC_TYPE_ERROR);
354     } else if (vstream_fseek(stream, offset, SEEK_SET) < 0) {
355 	msg_warn("%s: seek error after pointer record: %m",
356 		 VSTREAM_PATH(stream));
357 	return (REC_TYPE_ERROR);
358     } else {
359 	saved_offset = offset;
360 	return (0);
361     }
362 }
363 
364 /* rec_vfprintf - write formatted string to record */
365 
366 int     rec_vfprintf(VSTREAM *stream, int type, const char *format, va_list ap)
367 {
368     static VSTRING *vp;
369 
370     if (vp == 0)
371 	vp = vstring_alloc(100);
372 
373     /*
374      * Writing a formatted string involves an extra copy, because we must
375      * know the record length before we can write it.
376      */
377     vstring_vsprintf(vp, format, ap);
378     return (REC_PUT_BUF(stream, type, vp));
379 }
380 
381 /* rec_fprintf - write formatted string to record */
382 
383 int     rec_fprintf(VSTREAM *stream, int type, const char *format,...)
384 {
385     int     result;
386     va_list ap;
387 
388     va_start(ap, format);
389     result = rec_vfprintf(stream, type, format, ap);
390     va_end(ap);
391     return (result);
392 }
393 
394 /* rec_fputs - write string to record */
395 
396 int     rec_fputs(VSTREAM *stream, int type, const char *str)
397 {
398     return (rec_put(stream, type, str, str ? strlen(str) : 0));
399 }
400 
401 /* rec_pad - write padding record */
402 
403 int     rec_pad(VSTREAM *stream, int type, int len)
404 {
405     int     width = len - 2;		/* type + length */
406 
407     return (rec_fprintf(stream, type, "%*s",
408 			width < 1 ? 1 : width, "0"));
409 }
410