Add reflection plugin.
[nbdkit/ericb.git] / plugins / reflection / reflection.c
blob686ca06ec6a2b70b459774a331707b18a119053b
1 /* nbdkit
2 * Copyright (C) 2017-2019 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
39 #if defined(HAVE_GNUTLS) && defined(HAVE_GNUTLS_BASE64_DECODE2)
40 #include <gnutls/gnutls.h>
41 #define HAVE_BASE64 1
42 #endif
44 #define NBDKIT_API_VERSION 2
46 #include <nbdkit-plugin.h>
48 /* The mode. */
49 enum mode {
50 MODE_EXPORTNAME,
51 MODE_BASE64EXPORTNAME,
53 static enum mode mode = MODE_EXPORTNAME;
55 static int
56 reflection_config (const char *key, const char *value)
58 if (strcmp (key, "mode") == 0) {
59 if (strcasecmp (value, "exportname") == 0 ||
60 strcasecmp (value, "export-name") == 0) {
61 mode = MODE_EXPORTNAME;
63 else if (strcasecmp (value, "base64exportname") == 0 ||
64 strcasecmp (value, "base64-export-name") == 0) {
65 #ifdef HAVE_BASE64
66 mode = MODE_BASE64EXPORTNAME;
67 #else
68 nbdkit_error ("the plugin was compiled without base64 support");
69 return -1;
70 #endif
72 else {
73 nbdkit_error ("unknown mode: '%s'", value);
74 return -1;
77 else {
78 nbdkit_error ("unknown parameter '%s'", key);
79 return -1;
82 return 0;
85 #define reflection_config_help \
86 "mode=exportname|base64exportname Plugin mode."
88 /* Provide a way to detect if the base64 feature is supported. */
89 static void
90 reflection_dump_plugin (void)
92 #ifdef HAVE_BASE64
93 printf ("reflection_base64=yes\n");
94 #endif
97 /* Per-connection handle. */
98 struct handle {
99 void *data; /* Block device data. */
100 size_t len; /* Length of data in bytes. */
103 static int
104 decode_base64 (const char *data, size_t len, struct handle *ret)
106 #ifdef HAVE_BASE64
107 gnutls_datum_t in, out;
108 int err;
110 /* For unclear reasons gnutls_base64_decode2 won't handle an empty
111 * string, even though base64("") == "".
112 * https://tools.ietf.org/html/rfc4648#section-10
113 * https://gitlab.com/gnutls/gnutls/issues/834
114 * So we have to special-case it.
116 if (len == 0) {
117 ret->data = NULL;
118 ret->len = 0;
119 return 0;
122 in.data = (unsigned char *) data;
123 in.size = len;
124 err = gnutls_base64_decode2 (&in, &out);
125 if (err != GNUTLS_E_SUCCESS) {
126 nbdkit_error ("base64: %s", gnutls_strerror (err));
127 /* We don't have to free out.data. I verified that it is freed on
128 * the error path of gnutls_base64_decode2.
130 return -1;
133 ret->data = out.data; /* caller frees, eventually */
134 ret->len = out.size;
135 return 0;
136 #else
137 nbdkit_error ("the plugin was compiled without base64 support");
138 return -1;
139 #endif
142 /* Create the per-connection handle.
144 * This is a rather unusual plugin because it has to parse data sent
145 * by the client. For security reasons, be careful about:
147 * - Returning more data than is sent by the client.
149 * - Inputs that result in unbounded output.
151 * - Inputs that could hang, crash or exploit the server.
153 static void *
154 reflection_open (int readonly)
156 const char *export_name;
157 size_t export_name_len;
158 struct handle *h;
160 h = malloc (sizeof *h);
161 if (h == NULL) {
162 nbdkit_error ("malloc: %m");
163 return NULL;
166 switch (mode) {
167 case MODE_EXPORTNAME:
168 case MODE_BASE64EXPORTNAME:
169 export_name = nbdkit_export_name ();
170 if (export_name == NULL) {
171 free (h);
172 return NULL;
174 export_name_len = strlen (export_name);
176 if (mode == MODE_EXPORTNAME) {
177 h->len = export_name_len;
178 h->data = strdup (export_name);
179 if (h->data == NULL) {
180 nbdkit_error ("strdup: %m");
181 free (h);
182 return NULL;
184 return h;
186 else /* mode == MODE_BASE64EXPORTNAME */ {
187 if (decode_base64 (export_name, export_name_len, h) == -1) {
188 free (h);
189 return NULL;
191 return h;
194 default:
195 abort ();
199 /* Close the per-connection handle. */
200 static void
201 reflection_close (void *handle)
203 struct handle *h = handle;
205 free (h->data);
206 free (h);
209 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
211 /* Get the disk size. */
212 static int64_t
213 reflection_get_size (void *handle)
215 struct handle *h = handle;
217 return (int64_t) h->len;
220 /* Read-only plugin so multi-conn is safe. */
221 static int
222 reflection_can_multi_conn (void *handle)
224 return 1;
227 /* Cache. */
228 static int
229 reflection_can_cache (void *handle)
231 /* Everything is already in memory, returning this without
232 * implementing .cache lets nbdkit do the correct no-op.
234 return NBDKIT_CACHE_NATIVE;
237 /* Read data. */
238 static int
239 reflection_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
240 uint32_t flags)
242 struct handle *h = handle;
244 memcpy (buf, h->data + offset, count);
245 return 0;
248 static struct nbdkit_plugin plugin = {
249 .name = "reflection",
250 .version = PACKAGE_VERSION,
251 .config = reflection_config,
252 .config_help = reflection_config_help,
253 .dump_plugin = reflection_dump_plugin,
254 .magic_config_key = "mode",
255 .open = reflection_open,
256 .close = reflection_close,
257 .get_size = reflection_get_size,
258 .can_multi_conn = reflection_can_multi_conn,
259 .can_cache = reflection_can_cache,
260 .pread = reflection_pread,
261 /* In this plugin, errno is preserved properly along error return
262 * paths from failed system calls.
264 .errno_is_preserved = 1,
267 NBDKIT_REGISTER_PLUGIN(plugin)