/* * Copyright 2021 The OpenSSL Project Authors. All Rights Reserved. * * Licensed under the Apache License 2.0 (the "License"). You may not use * this file except in compliance with the License. You can obtain a copy * in the file LICENSE in the source distribution or at * https://www.openssl.org/source/license.html */ /* * This is a read only BIO filter that can be used to add BIO_tell() and * BIO_seek() support to source/sink BIO's (such as a file BIO that uses stdin). * It does this by caching ALL data read from the BIO source/sink into a * resizable memory buffer. */ #include #include #include "bio_local.h" #include "internal/cryptlib.h" #define DEFAULT_BUFFER_SIZE 4096 static int readbuffer_write(BIO *h, const char *buf, int num); static int readbuffer_read(BIO *h, char *buf, int size); static int readbuffer_puts(BIO *h, const char *str); static int readbuffer_gets(BIO *h, char *str, int size); static long readbuffer_ctrl(BIO *h, int cmd, long arg1, void *arg2); static int readbuffer_new(BIO *h); static int readbuffer_free(BIO *data); static long readbuffer_callback_ctrl(BIO *h, int cmd, BIO_info_cb *fp); static const BIO_METHOD methods_readbuffer = { BIO_TYPE_BUFFER, "readbuffer", bwrite_conv, readbuffer_write, bread_conv, readbuffer_read, readbuffer_puts, readbuffer_gets, readbuffer_ctrl, readbuffer_new, readbuffer_free, readbuffer_callback_ctrl, }; const BIO_METHOD *BIO_f_readbuffer(void) { return &methods_readbuffer; } static int readbuffer_new(BIO *bi) { BIO_F_BUFFER_CTX *ctx = OPENSSL_zalloc(sizeof(*ctx)); if (ctx == NULL) return 0; ctx->ibuf_size = DEFAULT_BUFFER_SIZE; ctx->ibuf = OPENSSL_malloc(DEFAULT_BUFFER_SIZE); if (ctx->ibuf == NULL) { OPENSSL_free(ctx); return 0; } bi->init = 1; bi->ptr = (char *)ctx; bi->flags = 0; return 1; } static int readbuffer_free(BIO *a) { BIO_F_BUFFER_CTX *b; if (a == NULL) return 0; b = (BIO_F_BUFFER_CTX *)a->ptr; OPENSSL_free(b->ibuf); OPENSSL_free(a->ptr); a->ptr = NULL; a->init = 0; a->flags = 0; return 1; } static int readbuffer_resize(BIO_F_BUFFER_CTX *ctx, int sz) { char *tmp; /* Figure out how many blocks are required */ sz += (ctx->ibuf_off + DEFAULT_BUFFER_SIZE - 1); sz = DEFAULT_BUFFER_SIZE * (sz / DEFAULT_BUFFER_SIZE); /* Resize if the buffer is not big enough */ if (sz > ctx->ibuf_size) { tmp = OPENSSL_realloc(ctx->ibuf, sz); if (tmp == NULL) return 0; ctx->ibuf = tmp; ctx->ibuf_size = sz; } return 1; } static int readbuffer_read(BIO *b, char *out, int outl) { int i, num = 0; BIO_F_BUFFER_CTX *ctx; if (out == NULL || outl == 0) return 0; ctx = (BIO_F_BUFFER_CTX *)b->ptr; if ((ctx == NULL) || (b->next_bio == NULL)) return 0; BIO_clear_retry_flags(b); for (;;) { i = ctx->ibuf_len; /* If there is something in the buffer just read it. */ if (i != 0) { if (i > outl) i = outl; memcpy(out, &(ctx->ibuf[ctx->ibuf_off]), i); ctx->ibuf_off += i; ctx->ibuf_len -= i; num += i; /* Exit if we have read the bytes required out of the buffer */ if (outl == i) return num; outl -= i; out += i; } /* Only gets here if the buffer has been consumed */ if (!readbuffer_resize(ctx, outl)) return 0; /* Do some buffering by reading from the next bio */ i = BIO_read(b->next_bio, ctx->ibuf + ctx->ibuf_off, outl); if (i <= 0) { BIO_copy_next_retry(b); if (i < 0) return ((num > 0) ? num : i); else return num; /* i == 0 */ } ctx->ibuf_len = i; } } static int readbuffer_write(BIO *b, const char *in, int inl) { return 0; } static int readbuffer_puts(BIO *b, const char *str) { return 0; } static long readbuffer_ctrl(BIO *b, int cmd, long num, void *ptr) { BIO_F_BUFFER_CTX *ctx; long ret = 1, sz; ctx = (BIO_F_BUFFER_CTX *)b->ptr; switch (cmd) { case BIO_CTRL_EOF: if (ctx->ibuf_len > 0) return 0; if (b->next_bio == NULL) return 1; ret = BIO_ctrl(b->next_bio, cmd, num, ptr); break; case BIO_C_FILE_SEEK: case BIO_CTRL_RESET: sz = ctx->ibuf_off + ctx->ibuf_len; /* Assume it can only seek backwards */ if (num < 0 || num > sz) return 0; ctx->ibuf_off = num; ctx->ibuf_len = sz - num; break; case BIO_C_FILE_TELL: case BIO_CTRL_INFO: ret = (long)ctx->ibuf_off; break; case BIO_CTRL_PENDING: ret = (long)ctx->ibuf_len; if (ret == 0) { if (b->next_bio == NULL) return 0; ret = BIO_ctrl(b->next_bio, cmd, num, ptr); } break; case BIO_CTRL_DUP: case BIO_CTRL_FLUSH: ret = 1; break; default: ret = 0; break; } return ret; } static long readbuffer_callback_ctrl(BIO *b, int cmd, BIO_info_cb *fp) { if (b->next_bio == NULL) return 0; return BIO_callback_ctrl(b->next_bio, cmd, fp); } static int readbuffer_gets(BIO *b, char *buf, int size) { BIO_F_BUFFER_CTX *ctx; int num = 0, num_chars, found_newline; char *p; if (size == 0) return 0; --size; /* the passed in size includes the terminator - so remove it here */ ctx = (BIO_F_BUFFER_CTX *)b->ptr; BIO_clear_retry_flags(b); for (;;) { if (ctx->ibuf_len > 0) { p = &(ctx->ibuf[ctx->ibuf_off]); found_newline = 0; for (num_chars = 0; (num_chars < ctx->ibuf_len) && (num_chars < size); num_chars++) { *(buf++) = p[num_chars]; if (p[num_chars] == '\n') { found_newline = 1; num_chars++; break; } } num += num_chars; size -= num_chars; ctx->ibuf_len -= num_chars; ctx->ibuf_off += num_chars; if (found_newline || size == 0) { *buf = '\0'; return num; } } else { /* read another line and resize if we have to */ if (!readbuffer_resize(ctx, size)) return 0; /* Read another line from the next bio using BIO_gets */ num_chars = BIO_gets(b->next_bio, ctx->ibuf + ctx->ibuf_off, 1 + size); if (num_chars <= 0) { BIO_copy_next_retry(b); *buf = '\0'; return num > 0 ? num : num_chars; } ctx->ibuf_len = num_chars; } } }