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 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 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 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 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 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 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 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 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