Mercurial > dovecot > original-hg > dovecot-1.2
view src/lib-index/mail-cache-lookup.c @ 6560:9036aa956d14 HEAD
Minor read access buffer overflow fix.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Sat, 20 Oct 2007 19:15:59 +0300 |
parents | 65c69a53a7be |
children | 3a41003bb1ae |
line wrap: on
line source
/* Copyright (c) 2003-2007 Dovecot authors, see the included COPYING file */ #include "lib.h" #include "array.h" #include "buffer.h" #include "str.h" #include "mail-cache-private.h" #include <stdlib.h> #define CACHE_PREFETCH 1024 int mail_cache_get_record(struct mail_cache *cache, uint32_t offset, const struct mail_cache_record **rec_r) { const struct mail_cache_record *rec; i_assert(offset != 0); /* we don't know yet how large the record is, so just guess */ if (mail_cache_map(cache, offset, sizeof(*rec) + CACHE_PREFETCH) < 0) return -1; if (offset + sizeof(*rec) > cache->mmap_length) { mail_cache_set_corrupted(cache, "record points outside file"); return -1; } rec = CACHE_RECORD(cache, offset); if (rec->size < sizeof(*rec)) { mail_cache_set_corrupted(cache, "invalid record size"); return -1; } if (rec->size > CACHE_PREFETCH) { /* larger than we guessed. map the rest of the record. */ if (mail_cache_map(cache, offset, rec->size) < 0) return -1; rec = CACHE_RECORD(cache, offset); } if (rec->size > cache->mmap_length || offset + rec->size > cache->mmap_length) { mail_cache_set_corrupted(cache, "record points outside file"); return -1; } *rec_r = rec; return 0; } static int mail_cache_lookup_offset(struct mail_cache *cache, struct mail_index_view *view, uint32_t seq, uint32_t *offset_r) { struct mail_index_map *map; const void *data; uint32_t reset_id; int i, ret; mail_index_lookup_ext_full(view, seq, cache->ext_id, &map, &data, NULL); if (data == NULL || *((const uint32_t *)data) == 0) { /* nothing in cache (for this record) */ return 0; } if (!mail_index_ext_get_reset_id(view, map, cache->ext_id, &reset_id)) i_unreached(); /* reset_id must match file_seq or the offset is for a different cache file. if this happens, try if reopening the cache helps. if not, it was probably for an old cache file that's already lost by now. */ i = 0; while (cache->hdr->file_seq != reset_id) { if (++i == 2) return 0; if (cache->locked) { /* we're probably compressing */ return 0; } if ((ret = mail_cache_reopen(cache)) <= 0) { /* error / we already have the latest file open */ return ret; } } *offset_r = *((const uint32_t *)data); return 1; } bool mail_cache_track_loops(ARRAY_TYPE(uint32_t) *array, uint32_t offset) { const uint32_t *offsets; unsigned int i, count; offsets = array_get(array, &count); for (i = 0; i < count; i++) { if (offsets[i] == offset) return TRUE; } array_append(array, &offset, 1); return FALSE; } void mail_cache_lookup_iter_init(struct mail_cache_view *view, uint32_t seq, struct mail_cache_lookup_iterate_ctx *ctx_r) { struct mail_cache_lookup_iterate_ctx *ctx = ctx_r; int ret; if (!view->cache->opened) (void)mail_cache_open_and_verify(view->cache); memset(ctx, 0, sizeof(*ctx)); ctx->view = view; ctx->seq = seq; if (!MAIL_CACHE_IS_UNUSABLE(view->cache)) { /* look up the first offset */ ret = mail_cache_lookup_offset(view->cache, view->view, seq, &ctx->offset); if (ret <= 0) { ctx->stop = TRUE; ctx->failed = ret < 0; } } ctx->remap_counter = view->cache->remap_counter; array_clear(&view->looping_offsets); } static int mail_cache_lookup_iter_next_record(struct mail_cache_lookup_iterate_ctx *ctx) { struct mail_cache_view *view = ctx->view; if (ctx->stop) return ctx->failed ? -1 : 0; if (ctx->rec != NULL) ctx->offset = ctx->rec->prev_offset; if (ctx->offset == 0) { /* end of this record list. check newly appended data. */ if (ctx->appends_checked || view->trans_seq1 > ctx->seq || view->trans_seq2 < ctx->seq || MAIL_CACHE_IS_UNUSABLE(view->cache) || mail_cache_lookup_offset(view->cache, view->trans_view, ctx->seq, &ctx->offset) <= 0) return 0; ctx->appends_checked = TRUE; ctx->remap_counter = view->cache->remap_counter; array_clear(&view->looping_offsets); } /* look up the next record */ if (mail_cache_track_loops(&view->looping_offsets, ctx->offset)) { mail_cache_set_corrupted(view->cache, "record list is circular"); return -1; } if (mail_cache_get_record(view->cache, ctx->offset, &ctx->rec) < 0) return -1; ctx->remap_counter = view->cache->remap_counter; ctx->pos = sizeof(*ctx->rec); ctx->rec_size = ctx->rec->size; return 1; } int mail_cache_lookup_iter_next(struct mail_cache_lookup_iterate_ctx *ctx, struct mail_cache_iterate_field *field_r) { struct mail_cache *cache = ctx->view->cache; unsigned int field_idx; unsigned int data_size; uint32_t file_field; int ret; i_assert(ctx->remap_counter == cache->remap_counter); if (ctx->pos + sizeof(uint32_t) > ctx->rec_size) { if (ctx->pos != ctx->rec_size) { mail_cache_set_corrupted(cache, "record has invalid size"); return -1; } if ((ret = mail_cache_lookup_iter_next_record(ctx)) <= 0) return ret; } /* return the next field */ file_field = *((const uint32_t *)CONST_PTR_OFFSET(ctx->rec, ctx->pos)); ctx->pos += sizeof(uint32_t); if (file_field >= cache->file_fields_count) { /* new field, have to re-read fields header to figure out its size. don't do this if we're compressing. */ if (!cache->locked) { if (mail_cache_header_fields_read(cache) < 0) return -1; } if (file_field >= cache->file_fields_count) { mail_cache_set_corrupted(cache, "field index too large (%u >= %u)", file_field, cache->file_fields_count); return -1; } /* field reading might have re-mmaped the file and caused rec pointer to break. need to get it again. */ if (mail_cache_get_record(cache, ctx->offset, &ctx->rec) < 0) return -1; ctx->remap_counter = cache->remap_counter; } field_idx = cache->file_field_map[file_field]; data_size = cache->fields[field_idx].field.field_size; if (data_size == (unsigned int)-1 && ctx->pos + sizeof(uint32_t) <= ctx->rec->size) { /* variable size field. get its size from the file. */ data_size = *((const uint32_t *) CONST_PTR_OFFSET(ctx->rec, ctx->pos)); ctx->pos += sizeof(uint32_t); } if (ctx->rec->size - ctx->pos < data_size) { mail_cache_set_corrupted(cache, "record continues outside its allocated size"); return -1; } field_r->field_idx = field_idx; field_r->data = CONST_PTR_OFFSET(ctx->rec, ctx->pos); field_r->size = data_size; /* each record begins from 32bit aligned position */ ctx->pos += (data_size + sizeof(uint32_t)-1) & ~(sizeof(uint32_t)-1); return 1; } static int mail_cache_seq(struct mail_cache_view *view, uint32_t seq) { struct mail_cache_lookup_iterate_ctx iter; struct mail_cache_iterate_field field; int ret; if (++view->cached_exists_value == 0) { /* wrapped, we'll have to clear the buffer */ buffer_reset(view->cached_exists_buf); view->cached_exists_value++; } view->cached_exists_seq = seq; mail_cache_lookup_iter_init(view, seq, &iter); while ((ret = mail_cache_lookup_iter_next(&iter, &field)) > 0) { buffer_write(view->cached_exists_buf, field.field_idx, &view->cached_exists_value, 1); } return ret; } static bool mail_cache_file_has_field(struct mail_cache *cache, unsigned int field) { i_assert(field < cache->fields_count); return cache->field_file_map[field] != (uint32_t)-1; } int mail_cache_field_exists(struct mail_cache_view *view, uint32_t seq, unsigned int field) { const uint8_t *data; i_assert(seq > 0); if (!view->cache->opened) (void)mail_cache_open_and_verify(view->cache); if (!mail_cache_file_has_field(view->cache, field)) return 0; /* FIXME: we should discard the cache if view has been synced */ if (view->cached_exists_seq != seq) { if (mail_cache_seq(view, seq) < 0) return -1; } data = view->cached_exists_buf->data; return (field < view->cached_exists_buf->used && data[field] == view->cached_exists_value) ? 1 : 0; } enum mail_cache_decision_type mail_cache_field_get_decision(struct mail_cache *cache, unsigned int field_idx) { i_assert(field_idx < cache->fields_count); return cache->fields[field_idx].field.decision; } static int mail_cache_lookup_bitmask(struct mail_cache_lookup_iterate_ctx *iter, unsigned int field_idx, unsigned int field_size, buffer_t *dest_buf) { struct mail_cache_iterate_field field; const unsigned char *src; unsigned char *dest; unsigned int i; bool found = FALSE; int ret; /* make sure all bits are cleared first */ buffer_write_zero(dest_buf, 0, field_size); while ((ret = mail_cache_lookup_iter_next(iter, &field)) > 0) { if (field.field_idx != field_idx) continue; /* merge all bits */ src = field.data; dest = buffer_get_space_unsafe(dest_buf, 0, field.size); for (i = 0; i < field.size; i++) dest[i] |= src[i]; found = TRUE; } return ret < 0 ? -1 : (found ? 1 : 0); } int mail_cache_lookup_field(struct mail_cache_view *view, buffer_t *dest_buf, uint32_t seq, unsigned int field_idx) { const struct mail_cache_field *field_def; struct mail_cache_lookup_iterate_ctx iter; struct mail_cache_iterate_field field; int ret; if ((ret = mail_cache_field_exists(view, seq, field_idx)) <= 0) return ret; /* the field should exist */ mail_cache_decision_state_update(view, seq, field_idx); mail_cache_lookup_iter_init(view, seq, &iter); field_def = &view->cache->fields[field_idx].field; if (field_def->type == MAIL_CACHE_FIELD_BITMASK) { return mail_cache_lookup_bitmask(&iter, field_idx, field_def->field_size, dest_buf); } /* return the first one that's found. if there are multiple they're all identical. */ while ((ret = mail_cache_lookup_iter_next(&iter, &field)) > 0) { if (field.field_idx == field_idx) { buffer_append(dest_buf, field.data, field.size); break; } } return ret; } struct header_lookup_data { uint32_t offset; uint32_t data_size; }; struct header_lookup_line { uint32_t line_num; struct header_lookup_data *data; }; struct header_lookup_context { struct mail_cache_view *view; ARRAY_DEFINE(lines, struct header_lookup_line); }; enum { HDR_FIELD_STATE_DONTWANT = 0, HDR_FIELD_STATE_WANT, HDR_FIELD_STATE_SEEN }; static void header_lines_save(struct header_lookup_context *ctx, const struct mail_cache_iterate_field *field) { const uint32_t *lines = field->data; uint32_t data_size = field->size; struct header_lookup_line hdr_line; struct header_lookup_data *hdr_data; unsigned int i, lines_count; /* data = { line_nums[], 0, "headers" } */ for (i = 0; data_size >= sizeof(uint32_t); i++) { data_size -= sizeof(uint32_t); if (lines[i] == 0) break; } lines_count = i; hdr_data = t_new(struct header_lookup_data, 1); hdr_data->offset = (const char *)&lines[lines_count+1] - (const char *)ctx->view->cache->data; hdr_data->data_size = data_size; for (i = 0; i < lines_count; i++) { hdr_line.line_num = lines[i]; hdr_line.data = hdr_data; array_append(&ctx->lines, &hdr_line, 1); } } static int header_lookup_line_cmp(const void *p1, const void *p2) { const struct header_lookup_line *l1 = p1, *l2 = p2; return (int)l1->line_num - (int)l2->line_num; } int mail_cache_lookup_headers(struct mail_cache_view *view, string_t *dest, uint32_t seq, unsigned int field_idxs[], unsigned int fields_count) { struct mail_cache *cache = view->cache; struct mail_cache_lookup_iterate_ctx iter; struct mail_cache_iterate_field field; struct header_lookup_context ctx; struct header_lookup_line *lines; const unsigned char *p, *start, *end; uint8_t *field_state; unsigned int i, count, max_field = 0; size_t hdr_size; uint8_t want = HDR_FIELD_STATE_WANT; buffer_t *buf; int ret; if (fields_count == 0) return 1; if (!view->cache->opened) (void)mail_cache_open_and_verify(view->cache); t_push(); /* mark all the fields we want to find. */ buf = buffer_create_dynamic(pool_datastack_create(), 32); for (i = 0; i < fields_count; i++) { if (!mail_cache_file_has_field(cache, field_idxs[i])) { t_pop(); return 0; } if (field_idxs[i] > max_field) max_field = field_idxs[i]; buffer_write(buf, field_idxs[i], &want, 1); } field_state = buffer_get_modifiable_data(buf, NULL); /* lookup the fields */ memset(&ctx, 0, sizeof(ctx)); ctx.view = view; t_array_init(&ctx.lines, 32); mail_cache_lookup_iter_init(view, seq, &iter); while ((ret = mail_cache_lookup_iter_next(&iter, &field)) > 0) { if (field.field_idx > max_field || field_state[field.field_idx] != HDR_FIELD_STATE_WANT) { /* a) don't want it, b) duplicate */ } else { field_state[field.field_idx] = HDR_FIELD_STATE_SEEN; header_lines_save(&ctx, &field); } } if (ret < 0) { t_pop(); return -1; } /* check that all fields were found */ for (i = 0; i <= max_field; i++) { if (field_state[i] == HDR_FIELD_STATE_WANT) { t_pop(); return 0; } } for (i = 0; i < fields_count; i++) mail_cache_decision_state_update(view, seq, field_idxs[i]); /* we need to return headers in the order they existed originally. we can do this by sorting the messages by their line numbers. */ lines = array_get_modifiable(&ctx.lines, &count); qsort(lines, count, sizeof(*lines), header_lookup_line_cmp); /* then start filling dest buffer from the headers */ for (i = 0; i < count; i++) { start = CONST_PTR_OFFSET(cache->data, lines[i].data->offset); end = start + lines[i].data->data_size; /* find the end of the (multiline) header */ for (p = start; p != end; p++) { if (*p == '\n' && (p+1 == end || (p[1] != ' ' && p[1] != '\t'))) { p++; break; } } hdr_size = (size_t)(p - start); buffer_append(dest, start, hdr_size); /* if there are more lines for this header, the following lines continue after this one. so skip this line. */ lines[i].data->offset += hdr_size; lines[i].data->data_size -= hdr_size; } t_pop(); return 1; }