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