]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
- A new common configuration executable statement, execute(), has been
authorDavid Hankins <dhankins@isc.org>
Mon, 31 Jul 2006 22:19:51 +0000 (22:19 +0000)
committerDavid Hankins <dhankins@isc.org>
Mon, 31 Jul 2006 22:19:51 +0000 (22:19 +0000)
  added.  This permits dhcpd or dhclient to execute a named external
  program with command line arguments specified from other configuration
  language.  Thanks to a patch written by Mattias Ronnblom, gotten to us
  via Robin Breathe. [ISC-Bugs #13728]

RELNOTES
common/conflex.c
common/dhcp-eval.5
common/options.c
common/parse.c
common/print.c
common/tree.c
includes/dhcpd.h
includes/dhctoken.h
includes/site.h
includes/tree.h

index a15b1758614a5c136b67c9956b8fa5310276dc37..eedf94096a9bc400015ab0b6fd0165f89ab880ba 100644 (file)
--- a/RELNOTES
+++ b/RELNOTES
@@ -156,6 +156,12 @@ and for prodding me into improving it.
   ifconfig hacks.  Many thanks go to the Kroger Co. for donating the
   hardware and funding the development.
 
+- A new common configuration executable statement, execute(), has been
+  added.  This permits dhcpd or dhclient to execute a named external
+  program with command line arguments specified from other configuration
+  language.  Thanks to a patch written by Mattias Ronnblom, gotten to us
+  via Robin Breathe.
+
                        Changes since 3.0.4
 
 - A warning that host statements declared within subnet or shared-network
index fd4418b43393f8a3afcfd4c570a86fc15e2a47a3..f91efc09e5fccd4ec9b457553b6c50ae7420ffa6 100644 (file)
@@ -34,7 +34,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: conflex.c,v 1.102 2006/07/25 13:25:59 shane Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
+"$Id: conflex.c,v 1.103 2006/07/31 22:19:51 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -736,6 +736,8 @@ static enum dhcp_token intern (atom, dfv)
                        return EVAL;
                if (!strcasecmp (atom + 1, "ncapsulate"))
                        return ENCAPSULATE;
+               if (!strcasecmp(atom + 1, "xecute"))
+                       return EXECUTE;
                break;
              case 'f':
                if (!strcasecmp (atom + 1, "atal"))
index b65714be5793ccce983165ab6828f4f19f4871c1..4aeb88376acabaa56a49fd56e98c810eb4c99abc 100644 (file)
@@ -1,4 +1,4 @@
-.\"    $Id: dhcp-eval.5,v 1.22 2006/07/17 15:33:34 dhankins Exp $
+.\"    $Id: dhcp-eval.5,v 1.23 2006/07/31 22:19:51 dhankins Exp $
 .\"
 .\" Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
 .\" Copyright (c) 1996-2003 by Internet Software Consortium
@@ -432,6 +432,48 @@ Rebind - DHCP client is in the REBINDING state - it has an IP address,
 and is trying to contact any server to renew it.   The next message to
 be sent will be a DHCPREQUEST, which will be broadcast.
 .RE
+.PP
+.B execute(\fIcommand-path\fB, \fIdata-expr1\fB ... \fIdata-exprN\fB);\fR
+.RS 0.25i
+.PP
+External command execution is made possible through \fBexecute();\fR
+expressions.  These expressions take a variable number of arguments, where
+the first is the command name (full path or only the name of the executable)
+and is followed by zero or more are data-expressions whose values will be
+evaluated and passed as external arguments (assumed to be text strings
+suitable for use as a command-line argument).  It returns the numeric return
+code of the external command, or one of the following special values:
+.TP 2
+.I \(bu
+125: Invalid arguments.
+.TP
+.I \(bu
+126: fork() failure
+.TP
+.I \(bu
+127: execvp() failure
+.TP
+.I \(bu
+-SIGNAL: Should the child exit due to a signal, rather than exiting normally
+with an exit status, the signal number multiplied by negative 1 will be
+returned.
+.PP
+Execute is synchronous, and the program will block until the external
+command being run has finished. Please note that lengthy program
+execution (for example, in an "on commit" in dhcpd.conf) may result in
+bad performance and timeouts.  Only external applications with very short
+execution times are suitable for use.
+.PP
+Passing user-supplied data to an external application might be dangerous.
+Make sure the external application checks input buffers for validity.
+Non-printable ASCII characters will be converted into dhcpd.conf language
+octal escapes ("\777"), make sure your external command handles them as
+such.
+.PP
+It is possible to use the execute expression in any context, not only
+on events. If you put it in a regular scope in the configuration file
+you will execute that command every time a scope is evaluated.
+.RE
 .SH REFERENCE: LOGGING
 Logging statements may be used to send information to the standard logging
 channels.  A logging statement includes an optional priority (\fBfatal\fR,
index 449775d605e05b4eaaca43eb6a577a531e39a65a..d2ef82c51cf833e71a2bb0ba31330329211b30e0 100644 (file)
@@ -34,7 +34,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: options.c,v 1.94 2006/07/25 13:36:58 shane Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
+"$Id: options.c,v 1.95 2006/07/31 22:19:51 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #define DHCP_OPTION_DATA
@@ -46,8 +46,6 @@ struct option *vendor_cfg_option;
 static void do_option_set PROTO ((pair *,
                                  struct option_cache *,
                                  enum statement_op));
-static int pretty_escape(char **, char *, const unsigned char **,
-                        const unsigned char *);
 static int pretty_text(char **, char *, const unsigned char **,
                         const unsigned char *, int);
 static int pretty_domain(char **, char *, const unsigned char **,
@@ -2694,7 +2692,7 @@ void do_packet (interface, packet, len, from_port, from, hfrom)
 #endif
 }
 
-static int
+int
 pretty_escape(char **dst, char *dend, const unsigned char **src,
              const unsigned char *send)
 {
index 0bda8de4d8467c15e15ea0e7f9ab4b7ce9e40755..385023ca307f466f5f4ce980deb316e482ce6e5c 100644 (file)
@@ -34,7 +34,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: parse.c,v 1.115 2006/07/26 15:43:52 shane Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
+"$Id: parse.c,v 1.116 2006/07/31 22:19:51 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -3684,6 +3684,71 @@ int parse_non_binary (expr, cfile, lose, context)
                        goto norparen;
                break;
 
+#ifdef ENABLE_EXECUTE
+             case EXECUTE:
+               token = next_token(&val, NULL, cfile);
+
+               if (!expression_allocate(expr, MDL))
+                       log_fatal("can't allocate expression.");
+
+               token = next_token(&val, NULL, cfile);
+               if (token != LPAREN) {
+                       parse_warn(cfile, "left parenthesis expected.");
+                       skip_to_semi(cfile);
+                       *lose = 1;
+                       return 0;
+               }
+
+               token = next_token(&val, NULL, cfile);
+               if (token != STRING) {
+                       parse_warn(cfile, "Expecting a quoted string.");
+                       skip_to_semi(cfile);
+                       *lose = 1;
+                       return 0;
+               }
+
+               (*expr)->data.execute.command = dmalloc(strlen(val) + 1, MDL);
+               if ((*expr)->data.execute.command == NULL)
+                       log_fatal("can't allocate command name");
+
+               strcpy((*expr)->data.execute.command, val);
+
+               token = next_token(&val, NULL, cfile);
+               ep = &(*expr)->data.execute.arglist;
+               i = 0;
+               while (token == COMMA) {
+                       if (!expression_allocate(ep, MDL))
+                               log_fatal ("can't allocate expression");
+
+                       if (!parse_data_expression(&(*ep)->data.arg.val,
+                                                  cfile, lose)) {
+                               skip_to_semi(cfile);
+                               *lose = 1;
+                               return 0;
+                       }
+                       ep = &(*ep)->data.arg.next;
+                       token = next_token(&val, NULL, cfile);
+                       i++;
+               }
+               (*expr)->data.execute.argc = i;
+               (*expr)->op = expr_execute;
+               if (token != RPAREN) {
+                       parse_warn(cfile, "right parenthesis expected.");
+                       skip_to_semi(cfile);
+                       *lose = 1;
+                       return 0;
+               }
+               break;
+#else
+             case EXECUTE:
+               parse_warn(cfile, "define ENABLE_EXECUTE in site.h to "
+                                 "enable execute(); expressions.");
+               skip_to_semi(cfile);
+               *lose = 1;
+               return 0;
+               break;
+#endif
+
                /* NOT EXISTS is special cased above... */
              not_exists:
                token = peek_token (&val, (unsigned *)0, cfile);
index a1c748ad6ce1bc024d868348bc70e65b0802ce6a..4438fbe541f66693c6f1482a121fb6aaf0b66221 100644 (file)
@@ -34,7 +34,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: print.c,v 1.60 2006/06/06 16:35:18 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
+"$Id: print.c,v 1.61 2006/07/31 22:19:51 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -461,7 +461,8 @@ static unsigned print_subexpression (expr, buf, len)
 {
        unsigned rv, left;
        const char *s;
-       
+       struct expression *next_arg;
+
        switch (expr -> op) {
              case expr_none:
                if (len > 3) {
@@ -1046,10 +1047,46 @@ static unsigned print_subexpression (expr, buf, len)
                                        rv += strlen (foo -> string);
                                }
                        }
-                       buf [rv] = ')';
-                       buf [rv++] = 0;
+                       buf [rv++] = ')';
+                       buf [rv] = 0;
+                       return rv;
+               }
+             case expr_execute:
+#ifdef ENABLE_EXECUTE
+               rv = 11 + strlen(expr->data.execute.command);
+               if (len > rv + 2) {
+                       sprintf(buf, "(execute \"%s\"",
+                               expr->data.execute.command);
+                       for(next_arg = expr->data.execute.arglist;
+                           next_arg;
+                           next_arg = next_arg->data.arg.next) {
+                               if (len <= rv + 3)
+                                       return 0;
+
+                               buf[rv++] = ' ';
+                               rv += print_subexpression(next_arg->
+                                                         data.arg.val,
+                                                         buf + rv,
+                                                         len - rv - 2);
+                       }
+
+                       if (len <= rv + 2)
+                               return 0;
+
+                       buf[rv++] = ')';
+                       buf[rv] = 0;
                        return rv;
                }
+#else
+               log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE is not "
+                         "defined.", MDL);
+#endif
+               break;
+
+             default:
+               log_fatal("Impossible case at %s:%d (undefined expression "
+                         "%d).", MDL, expr->op);
+               break;
        }
        return 0;
 }
index 140d0d535a4e3301328550cadc66dcb88b2620e9..9c316f75b25c0470603200ac01800f1078b6d259 100644 (file)
@@ -34,7 +34,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: tree.c,v 1.107 2006/07/17 15:33:34 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
+"$Id: tree.c,v 1.108 2006/07/31 22:19:51 dhankins Exp $ Copyright (c) 2004-2006 Internet Systems Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -51,6 +51,150 @@ struct __res_state resolver_state;
 int resolver_inited = 0;
 #endif
 
+
+#ifdef ENABLE_EXECUTE
+static unsigned long
+execute(char **args)
+{
+       pid_t p;
+
+       if (args == NULL || args[0] == NULL)
+               return 125;
+
+       p = fork();
+
+       if (p > 0) {
+               int status;
+               waitpid(p, &status, 0);
+
+               if (WIFEXITED(status))
+                       return WEXITSTATUS(status);
+               else
+                       return -WTERMSIG(status);
+       } else if (p == 0) {
+               execvp(args[0], args);
+               log_error("Unable to execute %s: %m", args[0]);
+               _exit(127);
+       }
+
+       return 126;
+}
+
+static void
+append_to_ary(char **ary_ptr, int *ary_size, int ary_capacity,
+             char *new_element)
+{
+       /* INSIST(ary_ptr != NULL); */
+       /* INSIST(ary_size != NULL); */
+       /* INSIST(ary_capacity > 1); */
+
+       if (new_element == NULL)
+               return;
+
+       if (*ary_size >= ary_capacity) {
+               log_fatal("Improbable error at %s:%d.", MDL);
+               return;
+       }
+
+       ary_ptr[(*ary_size)++] = new_element;
+}
+
+static char *
+data_string_to_char_string(struct data_string *d)
+{
+       char *str, *start, *end;
+       const unsigned char *pos;
+       int len;
+
+       if (d == NULL);
+               return NULL;
+
+       pos = d->data;
+
+       if (pos == NULL)
+               return NULL;
+
+       /* Per byte could be "\777" at worst, plus null terminator. */
+       len = (d->len * 4) + 1;
+       str = dmalloc(len, MDL);
+       if (!str)
+               return NULL;
+
+       start = str;
+       end = start + len;
+
+       if (pretty_escape(&start, end, &pos, pos + d->len) < 0) {
+               dfree(str, MDL);
+               return NULL;
+       }
+
+       /* dmalloc() sets the buffer to zero - there is no need to null
+        * terminate.
+        */
+
+       return str;
+}
+
+static int
+evaluate_execute(unsigned long *result, struct packet *packet,
+                struct lease *lease, struct client_state *client_state,
+                struct option_state *in_options,
+                struct option_state *cfg_options,
+                struct binding_scope **scope, struct expression *expr)
+{
+       int status;
+       int cmd_status;
+       int i;
+       struct data_string ds;
+       struct expression *next_arg;
+       char **arg_ary = NULL;
+       int arg_ary_size = 0;
+       int arg_ary_capacity = 0;
+
+       /* Need 1 bucket for the command, and 1 for the trailing NULL
+        * terminator.
+        */
+       i = expr->data.execute.argc + 2;
+       arg_ary = dmalloc(i * sizeof(char *), MDL);
+       /* Leave one bucket free for the NULL terminator. */
+       arg_ary_capacity = i - 1;
+
+       if (arg_ary == NULL)
+               return 0;
+
+       append_to_ary(arg_ary, &arg_ary_size, arg_ary_capacity,
+                     expr->data.execute.command);
+
+       for(next_arg = expr->data.execute.arglist;
+           next_arg;
+           next_arg = next_arg->data.arg.next) {
+               memset(&ds, 0, sizeof ds);
+               status = (evaluate_data_expression
+                         (&ds, packet, lease, client_state, in_options,
+                          cfg_options, scope, next_arg->data.arg.val, MDL));
+               if (!status) {
+                       if (arg_ary) {
+                               for (i=1; i < arg_ary_size; i++)
+                                       dfree(arg_ary[i], MDL);
+                               dfree(arg_ary, MDL);
+                       }
+                       return 0;
+               }
+               append_to_ary(arg_ary, &arg_ary_size, arg_ary_capacity,
+                             data_string_to_char_string(&ds));
+               data_string_forget(&ds, MDL);
+       }
+# if defined (DEBUG_EXPRESSIONS)
+       log_debug("exec: execute");
+# endif
+       *result = execute(arg_ary);
+       for (i=1; i < arg_ary_size; i++)
+               dfree(arg_ary[i], MDL);
+       dfree(arg_ary, MDL);
+       return 1;
+}
+#endif
+
 pair cons (car, cdr)
        caddr_t car;
        pair cdr;
@@ -864,6 +1008,7 @@ int evaluate_dns_expression (result, packet, lease, client_state, in_options,
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_const_int:
              case expr_lease_time:
              case expr_dns_transaction:
@@ -1229,6 +1374,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_const_int:
              case expr_lease_time:
              case expr_dns_transaction:
@@ -2165,6 +2311,7 @@ int evaluate_data_expression (result, packet, lease, client_state,
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_const_int:
              case expr_lease_time:
              case expr_dns_transaction:
@@ -2676,6 +2823,20 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
                        return 0;
                }
 
+             case expr_execute:
+#if defined (ENABLE_EXECUTE)
+                       status = evaluate_execute(result, packet, lease,
+                                                 client_state, in_options,
+                                                 cfg_options, scope, expr);
+# if defined (DEBUG_EXPRESSIONS)
+                       log_debug("num: execute() -> %d", status);
+# endif
+                       return status;
+#else
+                       log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE "
+                                 "is not defined).", MDL);
+#endif
+                       break;
              case expr_ns_add:
              case expr_ns_delete:
              case expr_ns_exists:
@@ -2690,6 +2851,11 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
 
              case expr_arg:
                break;
+
+             default:
+               log_fatal("Impossible case at %s:%d.  Undefined operator "
+                         "%d.", MDL, expr->op);
+               break;
        }
 
        log_error ("evaluate_numeric_expression: bogus opcode %d", expr -> op);
@@ -3100,6 +3266,7 @@ int is_numeric_expression (expr)
        return (expr -> op == expr_extract_int8 ||
                expr -> op == expr_extract_int16 ||
                expr -> op == expr_extract_int32 ||
+               expr -> op == expr_execute ||
                expr -> op == expr_const_int ||
                expr -> op == expr_lease_time ||
                expr -> op == expr_dns_transaction ||
@@ -3135,6 +3302,7 @@ int is_compound_expression (expr)
                expr -> op == expr_extract_int8 ||
                expr -> op == expr_extract_int16 ||
                expr -> op == expr_extract_int32 ||
+               expr -> op == expr_execute ||
                expr -> op == expr_dns_transaction);
 }
 
@@ -3163,6 +3331,7 @@ static int op_val (op)
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_encode_int8:
              case expr_encode_int16:
              case expr_encode_int32:
@@ -3261,6 +3430,7 @@ enum expression_context op_context (op)
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_encode_int8:
              case expr_encode_int16:
              case expr_encode_int32:
@@ -3321,6 +3491,7 @@ int write_expression (file, expr, col, indent, firstp)
        int firstp;
 {
        struct expression *e;
+       struct expression *next_arg;
        const char *s;
        char obuf [65];
        int scol;
@@ -3807,6 +3978,30 @@ int write_expression (file, expr, col, indent, firstp)
                                          expr -> data.variable);
                col = token_print_indent (file, col, indent, "", "", ")");
                break;
+             case expr_execute:
+#if defined(ENABLE_EXECUTE)
+               col = token_print_indent(file, col, indent, "", "",
+                                        "execute");
+               col = token_print_indent(file, col, indent, " ", "",
+                                        "(");
+               scol = col;
+               col = token_print_indent_concat(file, col, scol, "", "", "\"",
+                                               expr->data.execute.command,
+                                               "\"", NULL);
+               for(next_arg = expr->data.execute.arglist;
+                   next_arg;
+                   next_arg = next_arg->data.arg.next) {
+                       col = token_print_indent(file, col, scol, "", " ",
+                                                ",");
+                       col = write_expression(file, next_arg->data.arg.val,
+                                               col, scol, 0);
+               }
+               col = token_print_indent(file, col, indent, "", "", ")");
+#else
+               log_fatal("Impossible case at %s:%d (ENABLE_EXECUTE is not "
+                         "defined.", MDL);
+#endif
+               break;
 
              default:
                log_fatal ("invalid expression type in print_expression: %d",
@@ -4032,6 +4227,7 @@ int data_subexpression_length (int *rv,
              case expr_extract_int8:
              case expr_extract_int16:
              case expr_extract_int32:
+             case expr_execute:
              case expr_encode_int8:
              case expr_encode_int16:
              case expr_encode_int32:
index 994be098b19b709c8487dac0042def4d3b4c0125..f63cd924c04e73a0e984c8222b9b62a99f79492a 100644 (file)
@@ -1116,6 +1116,8 @@ int format_has_text(const char *);
 int format_min_length(const char *, struct option_cache *);
 const char *pretty_print_option PROTO ((struct option *, const unsigned char *,
                                        unsigned, int, int));
+int pretty_escape(char **, char *, const unsigned char **,
+                 const unsigned char *);
 int get_option (struct data_string *, struct universe *,
                struct packet *, struct lease *, struct client_state *,
                struct option_state *, struct option_state *,
index 7c3b38dadb36175058d4d7e834bf5472d7b485e0..89df27b062a1f8ddc777249dc550d7dcf5af7d3f 100644 (file)
@@ -324,7 +324,8 @@ enum dhcp_token {
        MAX_BALANCE = 628,
        MIN_BALANCE = 629,
        DOMAIN_LIST = 630,
-       LEASEQUERY = 631
+       LEASEQUERY = 631,
+       EXECUTE = 632
 };
 
 #define is_identifier(x)       ((x) >= FIRST_TOKEN &&  \
index b4d910a0e4ff823a9397ccef33b33c8160779f3c..98d332540a97e798c98d8ff1528f58fbcd70822f 100644 (file)
 
 /* #define DHCPD_LOG_FACILITY LOG_DAEMON */
 
+
+/* Define this if you want to be able to execute external commands
+   during conditional evaluation. */
+
+#define ENABLE_EXECUTE
+
 /* Define this if you aren't debugging and you want to save memory
    (potentially a _lot_ of memory) by allocating leases in chunks rather
    than one at a time. */
index e70a612309f5d229c4c796aa2205afc3349e9b6c..1a3988228586da8f0eb1dcd537a6498647cf14a9 100644 (file)
@@ -153,6 +153,7 @@ enum expr_op {
        expr_extract_int8,
        expr_extract_int16,
        expr_extract_int32,
+       expr_execute,
        expr_encode_int8,
        expr_encode_int16,
        expr_encode_int32,
@@ -274,6 +275,11 @@ struct expression {
                        char *name;
                        struct expression *arglist;
                } funcall;
+               struct {
+                       char *command;
+                       struct expression *arglist;
+                       int argc;
+               } execute;
                struct fundef *func;
        } data;
        int flags;