Update Red Hat Copyright Notices
[nbdkit.git] / plugins / info / info.c
blobd9d42e88ec1b011f3bebc43ce4d69e3533c28fea
1 /* nbdkit
2 * Copyright Red Hat
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>
38 #include <sys/time.h>
40 #ifdef HAVE_SYS_SOCKET_H
41 #include <sys/socket.h>
42 #endif
44 #ifdef HAVE_NETINET_IN_H
45 #include <netinet/in.h>
46 #endif
48 #ifdef HAVE_ARPA_INET_H
49 #include <arpa/inet.h>
50 #endif
52 #if defined (HAVE_GNUTLS) && defined (HAVE_GNUTLS_BASE64_DECODE2)
53 #include <gnutls/gnutls.h>
54 #define HAVE_BASE64 1
55 #endif
57 #define NBDKIT_API_VERSION 2
59 #include <nbdkit-plugin.h>
61 #include "ascii-string.h"
62 #include "byte-swapping.h"
63 #include "tvdiff.h"
65 /* The mode. */
66 enum mode {
67 MODE_EXPORTNAME,
68 MODE_BASE64EXPORTNAME,
69 MODE_ADDRESS,
70 MODE_TIME,
71 MODE_UPTIME,
72 MODE_CONNTIME,
74 static enum mode mode = MODE_EXPORTNAME;
76 /* Plugin load time. */
77 static struct timeval load_t;
79 static void
80 info_load (void)
82 gettimeofday (&load_t, NULL);
85 static int
86 info_config (const char *key, const char *value)
88 if (strcmp (key, "mode") == 0) {
89 if (ascii_strcasecmp (value, "exportname") == 0 ||
90 ascii_strcasecmp (value, "export-name") == 0) {
91 mode = MODE_EXPORTNAME;
93 else if (ascii_strcasecmp (value, "base64exportname") == 0 ||
94 ascii_strcasecmp (value, "base64-export-name") == 0) {
95 #ifdef HAVE_BASE64
96 mode = MODE_BASE64EXPORTNAME;
97 #else
98 nbdkit_error ("the plugin was compiled without base64 support");
99 return -1;
100 #endif
102 else if (ascii_strcasecmp (value, "address") == 0) {
103 #ifdef HAVE_INET_NTOP
104 mode = MODE_ADDRESS;
105 #else
106 nbdkit_error ("the plugin was compiled without inet_ntop");
107 return -1;
108 #endif
110 else if (ascii_strcasecmp (value, "time") == 0)
111 mode = MODE_TIME;
112 else if (ascii_strcasecmp (value, "uptime") == 0)
113 mode = MODE_UPTIME;
114 else if (ascii_strcasecmp (value, "conntime") == 0)
115 mode = MODE_CONNTIME;
116 else {
117 nbdkit_error ("unknown mode: '%s'", value);
118 return -1;
121 else {
122 nbdkit_error ("unknown parameter '%s'", key);
123 return -1;
126 return 0;
129 #define info_config_help \
130 "mode=exportname|base64exportname|address|time|uptime|conntime\n" \
131 " Plugin mode (default exportname)."
133 /* Provide a way to detect if the base64 feature is supported. */
134 static void
135 info_dump_plugin (void)
137 #ifdef HAVE_INET_NTOP
138 printf ("info_address=yes\n");
139 #endif
140 #ifdef HAVE_BASE64
141 printf ("info_base64=yes\n");
142 #endif
145 /* Per-connection handle. */
146 struct handle {
147 void *data; /* Block device data. */
148 size_t len; /* Length of data in bytes. */
149 struct timeval conn_t; /* Time since connection was opened. */
152 static int
153 decode_base64 (const char *data, size_t len, struct handle *ret)
155 #ifdef HAVE_BASE64
156 gnutls_datum_t in, out;
157 int err;
159 /* For unclear reasons gnutls_base64_decode2 won't handle an empty
160 * string, even though base64("") == "".
161 * https://tools.ietf.org/html/rfc4648#section-10
162 * https://gitlab.com/gnutls/gnutls/issues/834
163 * So we have to special-case it.
165 if (len == 0) {
166 ret->data = NULL;
167 ret->len = 0;
168 return 0;
171 in.data = (unsigned char *) data;
172 in.size = len;
173 err = gnutls_base64_decode2 (&in, &out);
174 if (err != GNUTLS_E_SUCCESS) {
175 nbdkit_error ("base64: %s", gnutls_strerror (err));
176 /* We don't have to free out.data. I verified that it is freed on
177 * the error path of gnutls_base64_decode2.
179 return -1;
182 ret->data = out.data; /* caller frees, eventually */
183 ret->len = out.size;
184 return 0;
185 #else
186 nbdkit_error ("the plugin was compiled without base64 support");
187 return -1;
188 #endif
191 static int
192 handle_address (struct sockaddr *sa, socklen_t addrlen,
193 struct handle *ret)
195 #ifdef HAVE_INET_NTOP
196 struct sockaddr_in *addr = (struct sockaddr_in *) sa;
197 struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) sa;
198 union {
199 char straddr[INET_ADDRSTRLEN];
200 char straddr6[INET6_ADDRSTRLEN];
201 } u;
202 int r;
203 char *str;
205 switch (addr->sin_family) {
206 case AF_INET:
207 if (inet_ntop (AF_INET, &addr->sin_addr,
208 u.straddr, sizeof u.straddr) == NULL) {
209 nbdkit_error ("inet_ntop: %m");
210 return -1;
212 r = asprintf (&str, "%s:%d", u.straddr, ntohs (addr->sin_port));
213 if (r == -1) {
214 nbdkit_error ("asprintf: %m");
215 return -1;
217 ret->len = r;
218 ret->data = str;
219 return 0;
221 case AF_INET6:
222 if (inet_ntop (AF_INET6, &addr6->sin6_addr,
223 u.straddr6, sizeof u.straddr6) == NULL) {
224 nbdkit_error ("inet_ntop: %m");
225 return -1;
227 r = asprintf (&str, "[%s]:%d", u.straddr6, ntohs (addr6->sin6_port));
228 if (r == -1) {
229 nbdkit_error ("asprintf: %m");
230 return -1;
232 ret->len = r;
233 ret->data = str;
234 return 0;
236 case AF_UNIX:
237 /* We don't want to expose the socket path because it's a host
238 * filesystem name. The client might not really be running on the
239 * same machine (eg. it is using a proxy). However it doesn't
240 * even matter because getpeername(2) on Linux returns a zero
241 * length sun_path in this case!
243 str = strdup ("unix");
244 if (str == NULL) {
245 nbdkit_error ("strdup: %m");
246 return -1;
248 ret->len = strlen (str);
249 ret->data = str;
250 return 0;
252 default:
253 nbdkit_debug ("unsupported socket family %d", addr->sin_family);
254 ret->data = NULL;
255 ret->len = 0;
256 return 0;
258 #else
259 nbdkit_error ("the plugin was compiled without inet_ntop");
260 return -1;
261 #endif
264 /* Create the per-connection handle.
266 * This is a rather unusual plugin because it has to parse data sent
267 * by the client. For security reasons, be careful about:
269 * - Returning more data than is sent by the client.
271 * - Inputs that result in unbounded output.
273 * - Inputs that could hang, crash or exploit the server.
275 * - Leaking host information (eg. paths).
277 static void *
278 info_open (int readonly)
280 const char *export_name;
281 size_t export_name_len;
282 struct sockaddr_storage addr;
283 socklen_t addrlen;
284 struct handle *h;
286 h = malloc (sizeof *h);
287 if (h == NULL) {
288 nbdkit_error ("malloc: %m");
289 return NULL;
292 switch (mode) {
293 case MODE_EXPORTNAME:
294 case MODE_BASE64EXPORTNAME:
295 export_name = nbdkit_export_name ();
296 if (export_name == NULL) {
297 free (h);
298 return NULL;
300 export_name_len = strlen (export_name);
302 if (mode == MODE_EXPORTNAME) {
303 h->len = export_name_len;
304 h->data = strdup (export_name);
305 if (h->data == NULL) {
306 nbdkit_error ("strdup: %m");
307 free (h);
308 return NULL;
310 return h;
312 else /* mode == MODE_BASE64EXPORTNAME */ {
313 if (decode_base64 (export_name, export_name_len, h) == -1) {
314 free (h);
315 return NULL;
317 return h;
320 case MODE_ADDRESS:
321 addrlen = sizeof addr;
322 if (nbdkit_peer_name ((struct sockaddr *) &addr, &addrlen) == -1 ||
323 handle_address ((struct sockaddr *) &addr, addrlen, h) == -1) {
324 free (h);
325 return NULL;
327 return h;
329 case MODE_TIME:
330 case MODE_UPTIME:
331 case MODE_CONNTIME:
332 gettimeofday (&h->conn_t, NULL);
333 h->len = 12;
334 h->data = malloc (h->len);
335 if (h->data == NULL) {
336 nbdkit_error ("malloc: %m");
337 free (h);
338 return NULL;
340 return h;
342 default:
343 abort ();
347 /* Close the per-connection handle. */
348 static void
349 info_close (void *handle)
351 struct handle *h = handle;
353 free (h->data);
354 free (h);
357 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
359 /* Get the disk size. */
360 static int64_t
361 info_get_size (void *handle)
363 struct handle *h = handle;
365 return (int64_t) h->len;
368 static int
369 info_can_multi_conn (void *handle)
371 switch (mode) {
372 /* Safe for exportname modes since clients should only request
373 * multi-conn with the same export name.
375 case MODE_EXPORTNAME:
376 case MODE_BASE64EXPORTNAME:
377 return 1;
378 /* Unsafe for mode=address because all multi-conn connections
379 * won't necessarily originate from the same client address.
381 case MODE_ADDRESS:
382 return 0;
383 /* All time modes will read different values at different times,
384 * so all of them are unsafe for multi-conn.
386 case MODE_TIME:
387 case MODE_UPTIME:
388 case MODE_CONNTIME:
389 return 0;
391 /* Keep GCC happy. */
392 default:
393 abort ();
397 /* Cache. */
398 static int
399 info_can_cache (void *handle)
401 /* Everything is already in memory, returning this without
402 * implementing .cache lets nbdkit do the correct no-op.
404 return NBDKIT_CACHE_NATIVE;
407 static void
408 update_time (struct handle *h)
410 struct timeval tv;
411 int64_t secs;
412 int32_t usecs;
413 char *p;
415 gettimeofday (&tv, NULL);
417 switch (mode) {
418 case MODE_TIME:
419 break;
421 case MODE_UPTIME:
422 subtract_timeval (&load_t, &tv, &tv);
423 break;
425 case MODE_CONNTIME:
426 subtract_timeval (&h->conn_t, &tv, &tv);
427 break;
429 default:
430 abort ();
433 /* Pack the result into the output buffer. */
434 secs = tv.tv_sec;
435 usecs = tv.tv_usec;
436 secs = htobe64 (secs);
437 usecs = htobe32 (usecs);
438 p = h->data;
439 memcpy (&p[0], &secs, 8);
440 memcpy (&p[8], &usecs, 4);
443 /* Read data. */
444 static int
445 info_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
446 uint32_t flags)
448 struct handle *h = handle;
450 /* For the time modes we update the data on every read. */
451 if (mode == MODE_TIME || mode == MODE_UPTIME || mode == MODE_CONNTIME)
452 update_time (h);
454 memcpy (buf, h->data + offset, count);
455 return 0;
458 static struct nbdkit_plugin plugin = {
459 .name = "info",
460 .version = PACKAGE_VERSION,
461 .load = info_load,
462 .config = info_config,
463 .config_help = info_config_help,
464 .dump_plugin = info_dump_plugin,
465 .magic_config_key = "mode",
466 .open = info_open,
467 .close = info_close,
468 .get_size = info_get_size,
469 .can_multi_conn = info_can_multi_conn,
470 .can_cache = info_can_cache,
471 .pread = info_pread,
472 /* In this plugin, errno is preserved properly along error return
473 * paths from failed system calls.
475 .errno_is_preserved = 1,
478 NBDKIT_REGISTER_PLUGIN (plugin)