[rrd-developers] [PATCH 1/5] src/rrd_daemon.c: Switch to per-socket, per-command permissions.

kevin brintnall kbrint at rufus.net
Sat May 23 23:30:17 CEST 2009


On Sat, May 23, 2009 at 01:04:24PM +0200, Florian Forster wrote:
> From: Florian Forster <octo at leeloo.lan.home.verplant.org>
> 
> This patch introduces per-socket, per-command permissions to the RRD
> caching daemon, RRDCacheD. This allows administrators to limit access to
> the daemon in a fine-grained manner.

Hi Florian,

Any ideas on how the authorization scheme may change once we have per-user
authentication (i.e. with client certs)?

Docs patch?

-- 
 kevin brintnall =~ /kbrint at rufus.net/

> The patch removes the command line option `-L' and introduces `-P'
> instead. The option must appear *before* the `-l' option and the given
> permissions are set for all following sockets or another `-P' option.
> 
> For example:
> 
>   rrdcached -P FLUSH,PENDING -l 10.2.3.4 -P UPDATE -l unix:/some/path
> 
> Allow `FLUSH' and `PENDING' commands to clients connected via IPv4
> networking and `UPDATE' to clients connected via a UNIX domain socket
> only.
> 
> There are a couple of exceptions:
> 
>   - The commands `HELP' and `QUIT' are always allowed.
>   - If the command `BATCH' is allowed, the command `.' is automatically
>     allowed, too.
> 
> By default, i.??e. if no `-P' option is specified, all commands will be
> allowed. As a shortcut to reset the behavior to the default behavior,
> you can use the slightly hackish `-P ""' syntax.
> 
> Signed-off-by: Florian Forster <octo at leeloo.lan.home.verplant.org>
> ---
>  src/rrd_daemon.c |  224 ++++++++++++++++++++++++++++++++++++------------------
>  1 files changed, 150 insertions(+), 74 deletions(-)
> 
> diff --git a/src/rrd_daemon.c b/src/rrd_daemon.c
> index 53904a3..6605ae5 100644
> --- a/src/rrd_daemon.c
> +++ b/src/rrd_daemon.c
> @@ -1,7 +1,7 @@
>  /**
>   * 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
> @@ -115,12 +115,6 @@
>  /*
>   * Types
>   */
> -typedef enum
> -{
> -  PRIV_LOW,
> -  PRIV_HIGH
> -} socket_privilege;
> -
>  typedef enum { RESP_ERR = -1, RESP_OK = 0 } response_code;
>  
>  struct listen_socket_s
> @@ -128,7 +122,6 @@ struct listen_socket_s
>    int fd;
>    char addr[PATH_MAX + 1];
>    int family;
> -  socket_privilege privilege;
>  
>    /* state for BATCH processing */
>    time_t batch_start;
> @@ -141,23 +134,25 @@ struct listen_socket_s
>  
>    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)
> @@ -458,7 +453,7 @@ static char *next_cmd (listen_socket_t *sock, ssize_t *len) /* {{{ */
>  
>    /* 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) /* {{{ */
> @@ -1041,20 +1036,6 @@ static void get_abs_path(char **filename, char *tmp)
>    *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;
> @@ -1083,7 +1064,7 @@ static int flush_file (const char *filename) /* {{{ */
>    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";
>  
> @@ -1472,11 +1453,10 @@ static int handle_request_quit (HANDLER_PROTO) /* {{{ */
>    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"
>      ,
> @@ -1491,7 +1471,6 @@ struct command COMMANDS[] = {
>    {
>      "WROTE",
>      handle_request_wrote,
> -    PRIV_HIGH,
>      CMD_CONTEXT_JOURNAL,
>      NULL,
>      NULL
> @@ -1499,7 +1478,6 @@ struct command COMMANDS[] = {
>    {
>      "FLUSH",
>      handle_request_flush,
> -    PRIV_LOW,
>      CMD_CONTEXT_CLIENT | CMD_CONTEXT_BATCH,
>      "FLUSH <filename>\n"
>      ,
> @@ -1509,7 +1487,6 @@ struct command COMMANDS[] = {
>    {
>      "FLUSHALL",
>      handle_request_flushall,
> -    PRIV_HIGH,
>      CMD_CONTEXT_CLIENT,
>      "FLUSHALL\n"
>      ,
> @@ -1518,7 +1495,6 @@ struct command COMMANDS[] = {
>    {
>      "PENDING",
>      handle_request_pending,
> -    PRIV_HIGH,
>      CMD_CONTEXT_CLIENT,
>      "PENDING <filename>\n"
>      ,
> @@ -1528,7 +1504,6 @@ struct command COMMANDS[] = {
>    {
>      "FORGET",
>      handle_request_forget,
> -    PRIV_HIGH,
>      CMD_CONTEXT_ANY,
>      "FORGET <filename>\n"
>      ,
> @@ -1538,7 +1513,6 @@ struct command COMMANDS[] = {
>    {
>      "QUEUE",
>      handle_request_queue,
> -    PRIV_LOW,
>      CMD_CONTEXT_CLIENT,
>      "QUEUE\n"
>      ,
> @@ -1551,7 +1525,6 @@ struct command COMMANDS[] = {
>    {
>      "STATS",
>      handle_request_stats,
> -    PRIV_LOW,
>      CMD_CONTEXT_CLIENT,
>      "STATS\n"
>      ,
> @@ -1561,7 +1534,6 @@ struct command COMMANDS[] = {
>    {
>      "HELP",
>      handle_request_help,
> -    PRIV_LOW,
>      CMD_CONTEXT_CLIENT,
>      "HELP [<command>]\n",
>      NULL, /* special! */
> @@ -1569,7 +1541,6 @@ struct command COMMANDS[] = {
>    {
>      "BATCH",
>      batch_start,
> -    PRIV_LOW,
>      CMD_CONTEXT_CLIENT,
>      "BATCH\n"
>      ,
> @@ -1593,7 +1564,6 @@ struct command COMMANDS[] = {
>    {
>      ".",   /* BATCH terminator */
>      batch_done,
> -    PRIV_LOW,
>      CMD_CONTEXT_BATCH,
>      NULL,
>      NULL
> @@ -1601,34 +1571,79 @@ struct command COMMANDS[] = {
>    {
>      "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 (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)
>      return (cmd->context & CMD_CONTEXT_JOURNAL);
> @@ -1646,7 +1661,7 @@ static int handle_request_help (HANDLER_PROTO) /* {{{ */
>    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)
> @@ -1667,14 +1682,15 @@ static int handle_request_help (HANDLER_PROTO) /* {{{ */
>    }
>    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);
>      }
>    }
>  
> @@ -1686,7 +1702,7 @@ 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');
> @@ -1705,9 +1721,8 @@ static int handle_request (DISPATCH_PROTO) /* {{{ */
>    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);
> @@ -2521,7 +2536,10 @@ static int read_options (int argc, char **argv) /* {{{ */
>    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)
>      {
> @@ -2529,7 +2547,6 @@ static int read_options (int argc, char **argv) /* {{{ */
>          stay_foreground=1;
>          break;
>  
> -      case 'L':
>        case 'l':
>        {
>          listen_socket_t *new;
> @@ -2543,7 +2560,40 @@ static int read_options (int argc, char **argv) /* {{{ */
>          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))
> @@ -2554,6 +2604,28 @@ static int read_options (int argc, char **argv) /* {{{ */
>        }
>        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;
> @@ -2724,13 +2796,15 @@ static int read_options (int argc, char **argv) /* {{{ */
>  
>        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"
> @@ -2766,6 +2840,8 @@ static int read_options (int argc, char **argv) /* {{{ */
>    if (journal_cur == NULL)
>      config_flush_at_shutdown = 1;
>  
> +  rrd_free_ptrs ((void *) &permissions, &permissions_len);
> +
>    return (status);
>  } /* }}} int read_options */
>  
> -- 
> 1.6.2.4
> 
> _______________________________________________
> rrd-developers mailing list
> rrd-developers at lists.oetiker.ch
> https://lists.oetiker.ch/cgi-bin/listinfo/rrd-developers



More information about the rrd-developers mailing list