diff options
-rw-r--r-- | RELNOTES | 6 | ||||
-rw-r--r-- | common/conflex.c | 4 | ||||
-rw-r--r-- | common/dhcp-eval.5 | 44 | ||||
-rw-r--r-- | common/options.c | 6 | ||||
-rw-r--r-- | common/parse.c | 67 | ||||
-rw-r--r-- | common/print.c | 45 | ||||
-rw-r--r-- | common/tree.c | 198 | ||||
-rw-r--r-- | includes/dhcpd.h | 2 | ||||
-rw-r--r-- | includes/dhctoken.h | 3 | ||||
-rw-r--r-- | includes/site.h | 6 | ||||
-rw-r--r-- | includes/tree.h | 6 |
11 files changed, 374 insertions, 13 deletions
@@ -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; |