/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* util/support/k5buf.c - string buffer functions */ /* * Copyright 2008 Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. */ /* * Can't include krb5.h here, or k5-int.h which includes it, because krb5.h * needs to be generated with error tables, after util/et, which builds after * this directory. */ #include "k5-platform.h" #include "k5-buf.h" #include /* * Structure invariants: * * buftype is K5BUF_FIXED, K5BUF_DYNAMIC, K5BUF_DYNAMIC_ZAP, or K5BUF_ERROR * if buftype is K5BUF_ERROR, the other fields are NULL or 0 * if buftype is not K5BUF_ERROR: * space > 0 * len < space * data[len] = '\0' */ /* Return a character pointer to the current end of buf. */ static inline char * endptr(struct k5buf *buf) { return (char *)buf->data + buf->len; } static inline void set_error(struct k5buf *buf) { buf->buftype = K5BUF_ERROR; buf->data = NULL; buf->space = buf->len = 0; } /* * Make sure there is room for LEN more characters in BUF, in addition to the * null terminator and what's already in there. Return true on success. On * failure, set the error flag and return false. */ static int ensure_space(struct k5buf *buf, size_t len) { size_t new_space; char *new_data; if (buf->buftype == K5BUF_ERROR) return 0; if (buf->space - 1 - buf->len >= len) /* Enough room already. */ return 1; if (buf->buftype == K5BUF_FIXED) /* Can't resize a fixed buffer. */ goto error_exit; assert(buf->buftype == K5BUF_DYNAMIC || buf->buftype == K5BUF_DYNAMIC_ZAP); new_space = buf->space * 2; while (new_space - buf->len - 1 < len) { if (new_space > SIZE_MAX / 2) goto error_exit; new_space *= 2; } if (buf->buftype == K5BUF_DYNAMIC_ZAP) { /* realloc() could leave behind a partial copy of sensitive data. */ new_data = malloc(new_space); if (new_data == NULL) goto error_exit; memcpy(new_data, buf->data, buf->len); new_data[buf->len] = '\0'; zap(buf->data, buf->len); free(buf->data); } else { new_data = realloc(buf->data, new_space); if (new_data == NULL) goto error_exit; } buf->data = new_data; buf->space = new_space; return 1; error_exit: if (buf->buftype == K5BUF_DYNAMIC_ZAP) zap(buf->data, buf->len); if (buf->buftype == K5BUF_DYNAMIC_ZAP || buf->buftype == K5BUF_DYNAMIC) free(buf->data); set_error(buf); return 0; } void k5_buf_init_fixed(struct k5buf *buf, char *data, size_t space) { assert(space > 0); buf->buftype = K5BUF_FIXED; buf->data = data; buf->space = space; buf->len = 0; *endptr(buf) = '\0'; } void k5_buf_init_dynamic(struct k5buf *buf) { buf->buftype = K5BUF_DYNAMIC; buf->space = 128; buf->data = malloc(buf->space); if (buf->data == NULL) { set_error(buf); return; } buf->len = 0; *endptr(buf) = '\0'; } void k5_buf_init_dynamic_zap(struct k5buf *buf) { k5_buf_init_dynamic(buf); if (buf->buftype == K5BUF_DYNAMIC) buf->buftype = K5BUF_DYNAMIC_ZAP; } void k5_buf_add(struct k5buf *buf, const char *data) { k5_buf_add_len(buf, data, strlen(data)); } void k5_buf_add_len(struct k5buf *buf, const void *data, size_t len) { if (!ensure_space(buf, len)) return; if (len > 0) memcpy(endptr(buf), data, len); buf->len += len; *endptr(buf) = '\0'; } void k5_buf_add_vfmt(struct k5buf *buf, const char *fmt, va_list ap) { va_list apcopy; int r; size_t remaining; char *tmp; if (buf->buftype == K5BUF_ERROR) return; remaining = buf->space - buf->len; if (buf->buftype == K5BUF_FIXED) { /* Format the data directly into the fixed buffer. */ r = vsnprintf(endptr(buf), remaining, fmt, ap); if (SNPRINTF_OVERFLOW(r, remaining)) set_error(buf); else buf->len += (unsigned int) r; return; } /* Optimistically format the data directly into the dynamic buffer. */ assert(buf->buftype == K5BUF_DYNAMIC || buf->buftype == K5BUF_DYNAMIC_ZAP); va_copy(apcopy, ap); r = vsnprintf(endptr(buf), remaining, fmt, apcopy); va_end(apcopy); if (!SNPRINTF_OVERFLOW(r, remaining)) { buf->len += (unsigned int) r; return; } if (r >= 0) { /* snprintf correctly told us how much space is required. */ if (!ensure_space(buf, r)) return; remaining = buf->space - buf->len; r = vsnprintf(endptr(buf), remaining, fmt, ap); if (SNPRINTF_OVERFLOW(r, remaining)) /* Shouldn't ever happen. */ k5_buf_free(buf); else buf->len += (unsigned int) r; return; } /* It's a pre-C99 snprintf implementation, or something else went wrong. * Fall back to asprintf. */ r = vasprintf(&tmp, fmt, ap); if (r < 0) { k5_buf_free(buf); return; } if (ensure_space(buf, r)) { /* Copy the temporary string into buf, including terminator. */ memcpy(endptr(buf), tmp, r + 1); buf->len += r; } if (buf->buftype == K5BUF_DYNAMIC_ZAP) zap(tmp, strlen(tmp)); free(tmp); } void k5_buf_add_fmt(struct k5buf *buf, const char *fmt, ...) { va_list ap; va_start(ap, fmt); k5_buf_add_vfmt(buf, fmt, ap); va_end(ap); } void * k5_buf_get_space(struct k5buf *buf, size_t len) { if (!ensure_space(buf, len)) return NULL; buf->len += len; *endptr(buf) = '\0'; return endptr(buf) - len; } void k5_buf_truncate(struct k5buf *buf, size_t len) { if (buf->buftype == K5BUF_ERROR) return; assert(len <= buf->len); buf->len = len; *endptr(buf) = '\0'; } int k5_buf_status(struct k5buf *buf) { return (buf->buftype == K5BUF_ERROR) ? ENOMEM : 0; } void k5_buf_free(struct k5buf *buf) { if (buf->buftype == K5BUF_ERROR) return; assert(buf->buftype == K5BUF_DYNAMIC || buf->buftype == K5BUF_DYNAMIC_ZAP); if (buf->buftype == K5BUF_DYNAMIC_ZAP) zap(buf->data, buf->len); free(buf->data); set_error(buf); }