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
#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"
return EVAL;
if (!strcasecmp (atom + 1, "ncapsulate"))
return ENCAPSULATE;
+ if (!strcasecmp(atom + 1, "xecute"))
+ return EXECUTE;
break;
case 'f':
if (!strcasecmp (atom + 1, "atal"))
-.\" $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
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,
#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
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 **,
#endif
}
-static int
+int
pretty_escape(char **dst, char *dend, const unsigned char **src,
const unsigned char *send)
{
#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"
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);
#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"
{
unsigned rv, left;
const char *s;
-
+ struct expression *next_arg;
+
switch (expr -> op) {
case expr_none:
if (len > 3) {
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;
}
#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"
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;
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:
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:
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:
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:
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);
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 ||
expr -> op == expr_extract_int8 ||
expr -> op == expr_extract_int16 ||
expr -> op == expr_extract_int32 ||
+ expr -> op == expr_execute ||
expr -> op == expr_dns_transaction);
}
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:
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:
int firstp;
{
struct expression *e;
+ struct expression *next_arg;
const char *s;
char obuf [65];
int scol;
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",
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:
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 *,
MAX_BALANCE = 628,
MIN_BALANCE = 629,
DOMAIN_LIST = 630,
- LEASEQUERY = 631
+ LEASEQUERY = 631,
+ EXECUTE = 632
};
#define is_identifier(x) ((x) >= FIRST_TOKEN && \
/* #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. */
expr_extract_int8,
expr_extract_int16,
expr_extract_int32,
+ expr_execute,
expr_encode_int8,
expr_encode_int16,
expr_encode_int32,
char *name;
struct expression *arglist;
} funcall;
+ struct {
+ char *command;
+ struct expression *arglist;
+ int argc;
+ } execute;
struct fundef *func;
} data;
int flags;