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