/**
* RRDTool - src/rrd_daemon.c
- * Copyright (C) 2008 Florian octo Forster
- * Copyright (C) 2008 Kevin Brintnall
+ * Copyright (C) 2008,2009 Florian octo Forster
+ * Copyright (C) 2008,2009 Kevin Brintnall
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
#include <stdlib.h>
#ifndef WIN32
-#include <stdint.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
#include <unistd.h>
#include <strings.h>
#include <inttypes.h>
-# include <sys/socket.h>
+#include <sys/socket.h>
#else
#include <assert.h>
#include <sys/time.h>
#include <time.h>
+#include <libgen.h>
#include <glib-2.0/glib.h>
/* }}} */
/*
* Types
*/
-typedef enum
-{
- PRIV_LOW,
- PRIV_HIGH
-} socket_privilege;
-
typedef enum { RESP_ERR = -1, RESP_OK = 0 } response_code;
struct listen_socket_s
int fd;
char addr[PATH_MAX + 1];
int family;
- socket_privilege privilege;
/* state for BATCH processing */
time_t batch_start;
char *wbuf;
ssize_t wbuf_len;
+
+ uint32_t permissions;
};
typedef struct listen_socket_s listen_socket_t;
-struct command;
+struct command_s;
+typedef struct command_s command_t;
/* note: guard against "unused" warnings in the handlers */
#define DISPATCH_PROTO listen_socket_t *sock __attribute__((unused)),\
time_t now __attribute__((unused)),\
char *buffer __attribute__((unused)),\
size_t buffer_size __attribute__((unused))
-#define HANDLER_PROTO struct command *cmd __attribute__((unused)),\
+#define HANDLER_PROTO command_t *cmd __attribute__((unused)),\
DISPATCH_PROTO
-struct command {
+struct command_s {
char *cmd;
int (*handler)(HANDLER_PROTO);
- socket_privilege min_priv;
char context; /* where we expect to see it */
#define CMD_CONTEXT_CLIENT (1<<0)
static pthread_mutex_t stats_lock = PTHREAD_MUTEX_INITIALIZER;
/* Journaled updates */
+#define JOURNAL_REPLAY(s) ((s) == NULL)
#define JOURNAL_BASE "rrd.journal"
static journal_set *journal_cur = NULL;
static journal_set *journal_old = NULL;
static int open_pidfile(char *action, int oflag) /* {{{ */
{
int fd;
- char *file;
+ const char *file;
+ char *file_copy, *dir;
file = (config_pid_file != NULL)
? config_pid_file
: LOCALSTATEDIR "/run/rrdcached.pid";
+ /* dirname may modify its argument */
+ file_copy = strdup(file);
+ if (file_copy == NULL)
+ {
+ fprintf(stderr, "rrdcached: strdup(): %s\n",
+ rrd_strerror(errno));
+ return -1;
+ }
+
+ dir = dirname(file_copy);
+ if (rrd_mkdir_p(dir, 0777) != 0)
+ {
+ fprintf(stderr, "Failed to create pidfile directory '%s': %s\n",
+ dir, rrd_strerror(errno));
+ return -1;
+ }
+
+ free(file_copy);
+
fd = open(file, oflag, S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
if (fd < 0)
fprintf(stderr, "rrdcached: can't %s pid file '%s' (%s)\n",
/* NOTREACHED */
assert(1==0);
-}
+} /* }}} char *next_cmd */
/* add the characters directly to the write buffer */
static int add_to_wbuf(listen_socket_t *sock, char *str, size_t len) /* {{{ */
char buffer[CMD_MAX];
int len;
- if (sock == NULL) return 0; /* journal replay mode */
+ if (JOURNAL_REPLAY(sock)) return 0;
if (sock->batch_start) return 0; /* no extra info returned when in BATCH */
va_start(argp, fmt);
ssize_t wrote;
int rclen, len;
- if (sock == NULL) return rc; /* journal replay mode */
+ if (JOURNAL_REPLAY(sock)) return rc;
if (sock->batch_start)
{
assert(file != NULL);
if (!config_write_base_only
- || sock == NULL /* journal replay */
+ || JOURNAL_REPLAY(sock)
|| config_base_dir == NULL)
return 1;
*filename = tmp;
} /* }}} static int get_abs_path */
-/* returns 1 if we have the required privilege level,
- * otherwise issue an error to the user on sock */
-static int has_privilege (listen_socket_t *sock, /* {{{ */
- socket_privilege priv)
-{
- if (sock == NULL) /* journal replay */
- return 1;
-
- if (sock->privilege >= priv)
- return 1;
-
- return send_response(sock, RESP_ERR, "%s\n", rrd_strerror(EACCES));
-} /* }}} static int has_privilege */
-
static int flush_file (const char *filename) /* {{{ */
{
cache_item_t *ci;
return (0);
} /* }}} int flush_file */
-static int syntax_error(listen_socket_t *sock, struct command *cmd) /* {{{ */
+static int syntax_error(listen_socket_t *sock, command_t *cmd) /* {{{ */
{
char *err = "Syntax error.\n";
if (found == TRUE)
{
- if (sock != NULL)
+ if (!JOURNAL_REPLAY(sock))
journal_write("forget", file);
return send_response(sock, RESP_OK, "Gone!\n");
cache_item_t *ci;
/* save it for the journal later */
- strncpy(orig_buf, buffer, sizeof(orig_buf)-1);
+ if (!JOURNAL_REPLAY(sock))
+ strncpy(orig_buf, buffer, buffer_size);
status = buffer_get_field (&buffer, &buffer_size, &file);
if (status != 0)
assert (ci != NULL);
/* don't re-write updates in replay mode */
- if (sock != NULL)
+ if (!JOURNAL_REPLAY(sock))
journal_write("update", orig_buf);
while (buffer_size > 0)
return -1;
} /* }}} static int handle_request_quit */
-struct command COMMANDS[] = {
+static command_t list_of_commands[] = { /* {{{ */
{
"UPDATE",
handle_request_update,
- PRIV_HIGH,
CMD_CONTEXT_ANY,
"UPDATE <filename> <values> [<values> ...]\n"
,
{
"WROTE",
handle_request_wrote,
- PRIV_HIGH,
CMD_CONTEXT_JOURNAL,
NULL,
NULL
{
"FLUSH",
handle_request_flush,
- PRIV_LOW,
CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
"FLUSH <filename>\n"
,
{
"FLUSHALL",
handle_request_flushall,
- PRIV_HIGH,
CMD_CONTEXT_CLIENT,
"FLUSHALL\n"
,
{
"PENDING",
handle_request_pending,
- PRIV_HIGH,
CMD_CONTEXT_CLIENT,
"PENDING <filename>\n"
,
{
"FORGET",
handle_request_forget,
- PRIV_HIGH,
CMD_CONTEXT_ANY,
"FORGET <filename>\n"
,
{
"QUEUE",
handle_request_queue,
- PRIV_LOW,
CMD_CONTEXT_CLIENT,
"QUEUE\n"
,
{
"STATS",
handle_request_stats,
- PRIV_LOW,
CMD_CONTEXT_CLIENT,
"STATS\n"
,
{
"HELP",
handle_request_help,
- PRIV_LOW,
CMD_CONTEXT_CLIENT,
"HELP [<command>]\n",
NULL, /* special! */
{
"BATCH",
batch_start,
- PRIV_LOW,
CMD_CONTEXT_CLIENT,
"BATCH\n"
,
{
".", /* BATCH terminator */
batch_done,
- PRIV_LOW,
CMD_CONTEXT_BATCH,
NULL,
NULL
{
"QUIT",
handle_request_quit,
- PRIV_LOW,
CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
"QUIT\n"
,
"Disconnect from rrdcached.\n"
- },
- {NULL,NULL,0,0,NULL,NULL} /* LAST ENTRY */
-};
+ }
+}; /* }}} command_t list_of_commands[] */
+static size_t list_of_commands_len = sizeof (list_of_commands)
+ / sizeof (list_of_commands[0]);
-static struct command *find_command(char *cmd)
+static command_t *find_command(char *cmd)
{
- struct command *c = COMMANDS;
-
- while (c->cmd != NULL)
- {
- if (strcasecmp(cmd, c->cmd) == 0)
- break;
- c++;
- }
+ size_t i;
- if (c->cmd == NULL)
- return NULL;
- else
- return c;
+ for (i = 0; i < list_of_commands_len; i++)
+ if (strcasecmp(cmd, list_of_commands[i].cmd) == 0)
+ return (&list_of_commands[i]);
+ return NULL;
}
+/* We currently use the index in the `list_of_commands' array as a bit position
+ * in `listen_socket_t.permissions'. This member schould NEVER be accessed from
+ * outside these functions so that switching to a more elegant storage method
+ * is easily possible. */
+static ssize_t find_command_index (const char *cmd) /* {{{ */
+{
+ size_t i;
+
+ for (i = 0; i < list_of_commands_len; i++)
+ if (strcasecmp(cmd, list_of_commands[i].cmd) == 0)
+ return ((ssize_t) i);
+ return (-1);
+} /* }}} ssize_t find_command_index */
+
+static int socket_permission_check (listen_socket_t *sock, /* {{{ */
+ const char *cmd)
+{
+ ssize_t i;
+
+ if (JOURNAL_REPLAY(sock))
+ return (1);
+
+ if (cmd == NULL)
+ return (-1);
+
+ if ((strcasecmp ("QUIT", cmd) == 0)
+ || (strcasecmp ("HELP", cmd) == 0))
+ return (1);
+ else if (strcmp (".", cmd) == 0)
+ cmd = "BATCH";
+
+ i = find_command_index (cmd);
+ if (i < 0)
+ return (-1);
+ assert (i < 32);
+
+ if ((sock->permissions & (1 << i)) != 0)
+ return (1);
+ return (0);
+} /* }}} int socket_permission_check */
+
+static int socket_permission_add (listen_socket_t *sock, /* {{{ */
+ const char *cmd)
+{
+ ssize_t i;
+
+ i = find_command_index (cmd);
+ if (i < 0)
+ return (-1);
+ assert (i < 32);
+
+ sock->permissions |= (1 << i);
+ return (0);
+} /* }}} int socket_permission_add */
+
/* check whether commands are received in the expected context */
-static int command_check_context(listen_socket_t *sock, struct command *cmd)
+static int command_check_context(listen_socket_t *sock, command_t *cmd)
{
- if (sock == NULL)
+ if (JOURNAL_REPLAY(sock))
return (cmd->context & CMD_CONTEXT_JOURNAL);
else if (sock->batch_start)
return (cmd->context & CMD_CONTEXT_BATCH);
int status;
char *cmd_str;
char *resp_txt;
- struct command *help = NULL;
+ command_t *help = NULL;
status = buffer_get_field (&buffer, &buffer_size, &cmd_str);
if (status == 0)
}
else
{
- help = COMMANDS;
+ size_t i;
+
resp_txt = "Command overview\n";
- while (help->cmd)
+ for (i = 0; i < list_of_commands_len; i++)
{
- if (help->syntax)
- add_response_info(sock, "%s", help->syntax);
- help++;
+ if (list_of_commands[i].syntax == NULL)
+ continue;
+ add_response_info (sock, "%s", list_of_commands[i].syntax);
}
}
return send_response(sock, RESP_OK, resp_txt);
} /* }}} int handle_request_help */
-/* if sock==NULL, we are in journal replay mode */
static int handle_request (DISPATCH_PROTO) /* {{{ */
{
char *buffer_ptr = buffer;
char *cmd_str = NULL;
- struct command *cmd = NULL;
+ command_t *cmd = NULL;
int status;
assert (buffer[buffer_size - 1] == '\0');
if (!cmd)
return send_response(sock, RESP_ERR, "Unknown command: %s\n", cmd_str);
- status = has_privilege(sock, cmd->min_priv);
- if (status <= 0)
- return status;
+ if (!socket_permission_check (sock, cmd->cmd))
+ return send_response(sock, RESP_ERR, "Permission denied.\n");
if (!command_check_context(sock, cmd))
return send_response(sock, RESP_ERR, "Can't use '%s' here.\n", cmd_str);
listen_socket_t *temp;
int status;
const char *path;
+ char *path_copy, *dir;
path = sock->addr;
if (strncmp(path, "unix:", strlen("unix:")) == 0)
path += strlen("unix:");
+ /* dirname may modify its argument */
+ path_copy = strdup(path);
+ if (path_copy == NULL)
+ {
+ fprintf(stderr, "rrdcached: strdup(): %s\n",
+ rrd_strerror(errno));
+ return (-1);
+ }
+
+ dir = dirname(path_copy);
+ if (rrd_mkdir_p(dir, 0777) != 0)
+ {
+ fprintf(stderr, "Failed to create socket directory '%s': %s\n",
+ dir, rrd_strerror(errno));
+ return (-1);
+ }
+
+ free(path_copy);
+
temp = (listen_socket_t *) rrd_realloc (listen_fds,
sizeof (listen_fds[0]) * (listen_fds_num + 1));
if (temp == NULL)
fprintf (stderr, "rrdcached: Garbage after address: %s\n", port);
return (-1);
}
- } /* if (*addr = ']') */
- else if (strchr (addr, '.') != NULL) /* Hostname or IPv4 */
+ } /* if (*addr == '[') */
+ else
{
port = rindex(addr, ':');
if (port != NULL)
free(queue_threads);
free(config_base_dir);
- free(config_pid_file);
pthread_mutex_lock(&cache_lock);
g_tree_destroy(cache_tree);
closelog ();
remove_pidfile ();
+ free(config_pid_file);
return (0);
} /* }}} int cleanup */
int option;
int status = 0;
- while ((option = getopt(argc, argv, "gl:L:f:w:z:t:Bb:p:Fj:h?")) != -1)
+ char **permissions = NULL;
+ size_t permissions_len = 0;
+
+ while ((option = getopt(argc, argv, "gl:P:f:w:z:t:Bb:p:Fj:h?")) != -1)
{
switch (option)
{
stay_foreground=1;
break;
- case 'L':
case 'l':
{
listen_socket_t *new;
memset(new, 0, sizeof(listen_socket_t));
strncpy(new->addr, optarg, sizeof(new->addr)-1);
- new->privilege = (option == 'l') ? PRIV_HIGH : PRIV_LOW;
+
+ /* Add permissions to the socket {{{ */
+ if (permissions_len != 0)
+ {
+ size_t i;
+ for (i = 0; i < permissions_len; i++)
+ {
+ status = socket_permission_add (new, permissions[i]);
+ if (status != 0)
+ {
+ fprintf (stderr, "read_options: Adding permission \"%s\" to "
+ "socket failed. Most likely, this permission doesn't "
+ "exist. Check your command line.\n", permissions[i]);
+ status = 4;
+ }
+ }
+ }
+ else /* if (permissions_len == 0) */
+ {
+ /* Add permission for ALL commands to the socket. */
+ size_t i;
+ for (i = 0; i < list_of_commands_len; i++)
+ {
+ status = socket_permission_add (new, list_of_commands[i].cmd);
+ if (status != 0)
+ {
+ fprintf (stderr, "read_options: Adding permission \"%s\" to "
+ "socket failed. This should never happen, ever! Sorry.\n",
+ permissions[i]);
+ status = 4;
+ }
+ }
+ }
+ /* }}} Done adding permissions. */
if (!rrd_add_ptr((void ***)&config_listen_address_list,
&config_listen_address_list_len, new))
}
break;
+ case 'P':
+ {
+ char *optcopy;
+ char *saveptr;
+ char *dummy;
+ char *ptr;
+
+ rrd_free_ptrs ((void *) &permissions, &permissions_len);
+
+ optcopy = strdup (optarg);
+ dummy = optcopy;
+ saveptr = NULL;
+ while ((ptr = strtok_r (dummy, ", ", &saveptr)) != NULL)
+ {
+ dummy = NULL;
+ rrd_add_strdup ((void *) &permissions, &permissions_len, ptr);
+ }
+
+ free (optcopy);
+ }
+ break;
+
case 'f':
{
int temp;
return (3);
}
+ if (rrd_mkdir_p (config_base_dir, 0777) != 0)
+ {
+ fprintf (stderr, "Failed to create base directory '%s': %s\n",
+ config_base_dir, rrd_strerror (errno));
+ return (3);
+ }
+
/* make sure that the base directory is not resolved via
* symbolic links. this makes some performance-enhancing
* assumptions possible (we don't have to resolve paths
*/
if (realpath(config_base_dir, base_realpath) == NULL)
{
- fprintf (stderr, "Invalid base directory '%s'.\n", config_base_dir);
- return 5;
- }
- else if (strncmp(config_base_dir,
- base_realpath, sizeof(base_realpath)) != 0)
- {
- fprintf(stderr,
- "Base directory (-b) resolved via file system links!\n"
- "Please consult rrdcached '-b' documentation!\n"
- "Consider specifying the real directory (%s)\n",
- base_realpath);
+ fprintf (stderr, "Failed to canonicalize the base directory '%s': "
+ "%s\n", config_base_dir, rrd_strerror(errno));
return 5;
}
}
_config_base_dir_len = len;
+
+ len = strlen (base_realpath);
+ while ((len > 0) && (base_realpath[len - 1] == '/'))
+ {
+ base_realpath[len - 1] = '\0';
+ len--;
+ }
+
+ if (strncmp(config_base_dir,
+ base_realpath, sizeof(base_realpath)) != 0)
+ {
+ fprintf(stderr,
+ "Base directory (-b) resolved via file system links!\n"
+ "Please consult rrdcached '-b' documentation!\n"
+ "Consider specifying the real directory (%s)\n",
+ base_realpath);
+ return 5;
+ }
}
break;
case 'j':
{
- struct stat statbuf;
const char *dir = journal_dir = strdup(optarg);
- status = stat(dir, &statbuf);
+ status = rrd_mkdir_p(dir, 0777);
if (status != 0)
{
- fprintf(stderr, "Cannot stat '%s' : %s\n", dir, rrd_strerror(errno));
+ fprintf(stderr, "Failed to create journal directory '%s': %s\n",
+ dir, rrd_strerror(errno));
return 6;
}
- if (!S_ISDIR(statbuf.st_mode)
- || access(dir, R_OK|W_OK|X_OK) != 0)
+ if (access(dir, R_OK|W_OK|X_OK) != 0)
{
fprintf(stderr, "Must specify a writable directory with -j! (%s)\n",
errno ? rrd_strerror(errno) : "");
case 'h':
case '?':
- printf ("RRDCacheD %s Copyright (C) 2008 Florian octo Forster\n"
+ printf ("RRDCacheD %s\n"
+ "Copyright (C) 2008,2009 Florian octo Forster and Kevin Brintnall\n"
"\n"
"Usage: rrdcached [options]\n"
"\n"
"Valid options are:\n"
" -l <address> Socket address to listen to.\n"
- " -L <address> Socket address to listen to ('FLUSH' only).\n"
+ " -P <perms> Sets the permissions to assign to all following "
+ "sockets\n"
" -w <seconds> Interval in which to write data.\n"
" -z <delay> Delay writes up to <delay> seconds to spread load\n"
" -t <threads> Number of write threads.\n"
if (journal_dir == NULL)
config_flush_at_shutdown = 1;
+ rrd_free_ptrs ((void *) &permissions, &permissions_len);
+
return (status);
} /* }}} int read_options */