summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELNOTES6
-rw-r--r--common/conflex.c4
-rw-r--r--common/dhcp-eval.544
-rw-r--r--common/options.c6
-rw-r--r--common/parse.c67
-rw-r--r--common/print.c45
-rw-r--r--common/tree.c198
-rw-r--r--includes/dhcpd.h2
-rw-r--r--includes/dhctoken.h3
-rw-r--r--includes/site.h6
-rw-r--r--includes/tree.h6
11 files changed, 374 insertions, 13 deletions
diff --git a/RELNOTES b/RELNOTES
index a15b1758..eedf9409 100644
--- 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
diff --git a/common/conflex.c b/common/conflex.c
index fd4418b4..f91efc09 100644
--- a/common/conflex.c
+++ b/common/conflex.c
@@ -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"))
diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5
index b65714be..4aeb8837 100644
--- a/common/dhcp-eval.5
+++ b/common/dhcp-eval.5
@@ -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,
diff --git a/common/options.c b/common/options.c
index 449775d6..d2ef82c5 100644
--- a/common/options.c
+++ b/common/options.c
@@ -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)
{
diff --git a/common/parse.c b/common/parse.c
index 0bda8de4..385023ca 100644
--- a/common/parse.c
+++ b/common/parse.c
@@ -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);
diff --git a/common/print.c b/common/print.c
index a1c748ad..4438fbe5 100644
--- a/common/print.c
+++ b/common/print.c
@@ -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;
}
diff --git a/common/tree.c b/common/tree.c
index 140d0d53..9c316f75 100644
--- a/common/tree.c
+++ b/common/tree.c
@@ -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:
diff --git a/includes/dhcpd.h b/includes/dhcpd.h
index 994be098..f63cd924 100644
--- a/includes/dhcpd.h
+++ b/includes/dhcpd.h
@@ -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 *,
diff --git a/includes/dhctoken.h b/includes/dhctoken.h
index 7c3b38da..89df27b0 100644
--- a/includes/dhctoken.h
+++ b/includes/dhctoken.h
@@ -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 && \
diff --git a/includes/site.h b/includes/site.h
index b4d910a0..98d33254 100644
--- a/includes/site.h
+++ b/includes/site.h
@@ -167,6 +167,12 @@
/* #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. */
diff --git a/includes/tree.h b/includes/tree.h
index e70a6123..1a398822 100644
--- a/includes/tree.h
+++ b/includes/tree.h
@@ -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;