1 /* $NetBSD: record.c,v 1.4 2022/10/08 16:12:45 christos 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 /* ssize_t 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 /* Wietse Venema
144 /* Google, Inc.
145 /* 111 8th Avenue
146 /* New York, NY 10011, USA
147 /*--*/
148
149 /* System library. */
150
151 #include <sys_defs.h>
152 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
153 #include <stdarg.h>
154 #include <unistd.h>
155 #include <string.h>
156
157 #ifndef NBBY
158 #define NBBY 8 /* XXX should be in sys_defs.h */
159 #endif
160
161 /* Utility library. */
162
163 #include <msg.h>
164 #include <mymalloc.h>
165 #include <vstream.h>
166 #include <vstring.h>
167 #include <stringops.h>
168
169 /* Global library. */
170
171 #include <off_cvt.h>
172 #include <rec_type.h>
173 #include <record.h>
174
175 /* rec_put_type - update record type field */
176
rec_put_type(VSTREAM * stream,int type,off_t offset)177 int rec_put_type(VSTREAM *stream, int type, off_t offset)
178 {
179 if (type < 0 || type > 255)
180 msg_panic("rec_put_type: bad record type %d", type);
181
182 if (msg_verbose > 2)
183 msg_info("rec_put_type: %d at %ld", type, (long) offset);
184
185 if (vstream_fseek(stream, offset, SEEK_SET) < 0
186 || VSTREAM_PUTC(type, stream) != type) {
187 msg_warn("%s: seek or write error", VSTREAM_PATH(stream));
188 return (REC_TYPE_ERROR);
189 } else {
190 return (type);
191 }
192 }
193
194 /* rec_put - store typed record */
195
rec_put(VSTREAM * stream,int type,const char * data,ssize_t len)196 int rec_put(VSTREAM *stream, int type, const char *data, ssize_t len)
197 {
198 ssize_t len_rest;
199 int len_byte;
200
201 if (type < 0 || type > 255)
202 msg_panic("rec_put: bad record type %d", type);
203
204 if (msg_verbose > 2)
205 msg_info("rec_put: type %c len %ld data %.10s",
206 type, (long) len, data);
207
208 /*
209 * Write the record type, one byte.
210 */
211 if (VSTREAM_PUTC(type, stream) == VSTREAM_EOF)
212 return (REC_TYPE_ERROR);
213
214 /*
215 * Write the record data length in 7-bit portions, using the 8th bit to
216 * indicate that there is more. Use as many length bytes as needed.
217 */
218 len_rest = len;
219 do {
220 len_byte = len_rest & 0177;
221 if (len_rest >>= 7U)
222 len_byte |= 0200;
223 if (VSTREAM_PUTC(len_byte, stream) == VSTREAM_EOF) {
224 return (REC_TYPE_ERROR);
225 }
226 } while (len_rest != 0);
227
228 /*
229 * Write the record data portion. Use as many length bytes as needed.
230 */
231 if (len && vstream_fwrite(stream, data, len) != len)
232 return (REC_TYPE_ERROR);
233 return (type);
234 }
235
236 /* rec_get_raw - retrieve typed record */
237
rec_get_raw(VSTREAM * stream,VSTRING * buf,ssize_t maxsize,int flags)238 int rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags)
239 {
240 const char *myname = "rec_get";
241 int type;
242 ssize_t len;
243 int len_byte;
244 unsigned shift;
245
246 /*
247 * Sanity check.
248 */
249 if (maxsize < 0)
250 msg_panic("%s: bad record size limit: %ld", myname, (long) maxsize);
251
252 for (;;) {
253
254 /*
255 * Extract the record type.
256 */
257 if ((type = VSTREAM_GETC(stream)) == VSTREAM_EOF)
258 return (REC_TYPE_EOF);
259
260 /*
261 * Find out the record data length. Return an error result when the
262 * record data length is malformed or when it exceeds the acceptable
263 * limit.
264 */
265 for (len = 0, shift = 0; /* void */ ; shift += 7) {
266 if (shift >= (int) (NBBY * sizeof(int))) {
267 msg_warn("%s: too many length bits, record type %d",
268 VSTREAM_PATH(stream), type);
269 return (REC_TYPE_ERROR);
270 }
271 if ((len_byte = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
272 msg_warn("%s: unexpected EOF reading length, record type %d",
273 VSTREAM_PATH(stream), type);
274 return (REC_TYPE_ERROR);
275 }
276 len |= (len_byte & 0177) << shift;
277 if ((len_byte & 0200) == 0)
278 break;
279 }
280 if (len < 0 || (maxsize > 0 && len > maxsize)) {
281 msg_warn("%s: illegal length %ld, record type %d",
282 VSTREAM_PATH(stream), (long) len, type);
283 while (len-- > 0 && VSTREAM_GETC(stream) != VSTREAM_EOF)
284 /* void */ ;
285 return (REC_TYPE_ERROR);
286 }
287
288 /*
289 * Reserve buffer space for the result, and read the record data into
290 * the buffer.
291 */
292 if (vstream_fread_buf(stream, buf, len) != len) {
293 msg_warn("%s: unexpected EOF in data, record type %d length %ld",
294 VSTREAM_PATH(stream), type, (long) len);
295 return (REC_TYPE_ERROR);
296 }
297 VSTRING_TERMINATE(buf);
298 if (msg_verbose > 2)
299 msg_info("%s: type %c len %ld data %.10s", myname,
300 type, (long) len, vstring_str(buf));
301
302 /*
303 * Transparency options.
304 */
305 if (flags == 0)
306 break;
307 if (type == REC_TYPE_PTR && (flags & REC_FLAG_FOLLOW_PTR) != 0
308 && (type = rec_goto(stream, vstring_str(buf))) != REC_TYPE_ERROR)
309 continue;
310 if (type == REC_TYPE_DTXT && (flags & REC_FLAG_SKIP_DTXT) != 0)
311 continue;
312 if (type == REC_TYPE_END && (flags & REC_FLAG_SEEK_END) != 0
313 && vstream_fseek(stream, (off_t) 0, SEEK_END) < 0) {
314 msg_warn("%s: seek error after reading END record: %m",
315 VSTREAM_PATH(stream));
316 return (REC_TYPE_ERROR);
317 }
318 break;
319 }
320 return (type);
321 }
322
323 /* rec_goto - follow PTR record */
324
rec_goto(VSTREAM * stream,const char * buf)325 int rec_goto(VSTREAM *stream, const char *buf)
326 {
327 off_t offset;
328 static char *saved_path;
329 static off_t saved_offset;
330 static int reverse_count;
331
332 /*
333 * Crude workaround for queue file loops. VSTREAMs currently have no
334 * option to attach application-specific data, so we use global state and
335 * simple logic to detect if an application switches streams. We trigger
336 * on reverse jumps only. There's one reverse jump for every inserted
337 * header, but only one reverse jump for all appended recipients. No-one
338 * is likely to insert 10000 message headers, but someone might append
339 * 10000 recipients.
340 */
341 #define REVERSE_JUMP_LIMIT 10000
342
343 if (saved_path == 0 || strcmp(saved_path, VSTREAM_PATH(stream)) != 0) {
344 if (saved_path)
345 myfree(saved_path);
346 saved_path = mystrdup(VSTREAM_PATH(stream));
347 reverse_count = 0;
348 saved_offset = 0;
349 }
350 while (ISSPACE(*buf))
351 buf++;
352 if ((offset = off_cvt_string(buf)) < 0) {
353 msg_warn("%s: malformed pointer record value: %s",
354 VSTREAM_PATH(stream), buf);
355 return (REC_TYPE_ERROR);
356 } else if (offset == 0) {
357 /* Dummy record. */
358 return (0);
359 } else if (offset <= saved_offset && ++reverse_count > REVERSE_JUMP_LIMIT) {
360 msg_warn("%s: too many reverse jump records", VSTREAM_PATH(stream));
361 return (REC_TYPE_ERROR);
362 } else if (vstream_fseek(stream, offset, SEEK_SET) < 0) {
363 msg_warn("%s: seek error after pointer record: %m",
364 VSTREAM_PATH(stream));
365 return (REC_TYPE_ERROR);
366 } else {
367 saved_offset = offset;
368 return (0);
369 }
370 }
371
372 /* rec_vfprintf - write formatted string to record */
373
rec_vfprintf(VSTREAM * stream,int type,const char * format,va_list ap)374 int rec_vfprintf(VSTREAM *stream, int type, const char *format, va_list ap)
375 {
376 static VSTRING *vp;
377
378 if (vp == 0)
379 vp = vstring_alloc(100);
380
381 /*
382 * Writing a formatted string involves an extra copy, because we must
383 * know the record length before we can write it.
384 */
385 vstring_vsprintf(vp, format, ap);
386 return (REC_PUT_BUF(stream, type, vp));
387 }
388
389 /* rec_fprintf - write formatted string to record */
390
rec_fprintf(VSTREAM * stream,int type,const char * format,...)391 int rec_fprintf(VSTREAM *stream, int type, const char *format,...)
392 {
393 int result;
394 va_list ap;
395
396 va_start(ap, format);
397 result = rec_vfprintf(stream, type, format, ap);
398 va_end(ap);
399 return (result);
400 }
401
402 /* rec_fputs - write string to record */
403
rec_fputs(VSTREAM * stream,int type,const char * str)404 int rec_fputs(VSTREAM *stream, int type, const char *str)
405 {
406 return (rec_put(stream, type, str, str ? strlen(str) : 0));
407 }
408
409 /* rec_pad - write padding record */
410
rec_pad(VSTREAM * stream,int type,ssize_t len)411 int rec_pad(VSTREAM *stream, int type, ssize_t len)
412 {
413 int width = len - 2; /* type + length */
414
415 return (rec_fprintf(stream, type, "%*s",
416 width < 1 ? 1 : width, "0"));
417 }
418