|
diff --git a/common/conflex.c b/common/conflex.c |
|
index 045b655d..439bc30c 100644 |
|
--- a/common/conflex.c |
|
+++ b/common/conflex.c |
|
@@ -1028,6 +1028,8 @@ intern(char *atom, enum dhcp_token dfv) { |
|
return GETHOSTNAME; |
|
break; |
|
} |
|
+ if (!strncasecmp (atom + 1, "enerate", 8)) |
|
+ return GENERATE; |
|
if (!strcasecmp (atom + 1, "iaddr")) |
|
return GIADDR; |
|
if (!strcasecmp (atom + 1, "roup")) |
|
diff --git a/common/dhcp-eval.5 b/common/dhcp-eval.5 |
|
index 7dd52613..4a5568bd 100644 |
|
--- a/common/dhcp-eval.5 |
|
+++ b/common/dhcp-eval.5 |
|
@@ -239,6 +239,17 @@ Several of the boolean expressions above depend on the results of |
|
evaluating data expressions. A list of these expressions is provided |
|
here. |
|
.PP |
|
+.B generate (\fIexecutable\fB[, \fIargument\fB[, ...]])\fR |
|
+.PP |
|
+.RS 0.25i |
|
+The \fBgenerate\fR operator expects an executable as first parameter. It |
|
+can be either an absolute path or just a name that will be looked up in |
|
+the current PATH. Other arguments, if any, are passed as command line |
|
+arguments to the executable. \fIexecutable\fR and \fIargument\fR are data |
|
+expressions. The command is executed and generate() evaluates to its |
|
+standard output. If this output exceeds 1024 bytes, it will be truncated. |
|
+.RE |
|
+.PP |
|
.B substring (\fIdata-expr\fB, \fIoffset\fB, \fIlength\fB)\fR |
|
.PP |
|
.RS 0.25i |
|
diff --git a/common/parse.c b/common/parse.c |
|
index 3ac4ebf7..60ed7183 100644 |
|
--- a/common/parse.c |
|
+++ b/common/parse.c |
|
@@ -3485,6 +3485,8 @@ int parse_boolean (cfile) |
|
* data_expression :== SUBSTRING LPAREN data-expression COMMA |
|
* numeric-expression COMMA |
|
* numeric-expression RPAREN | |
|
+ * GENERATE LPAREN data-expression |
|
+ * [COMMA data-expression, ...] RPAREN | |
|
* CONCAT LPAREN data-expression COMMA |
|
* data-expression RPAREN |
|
* SUFFIX LPAREN data_expression COMMA |
|
@@ -3668,6 +3670,60 @@ int parse_non_binary (expr, cfile, lose, context) |
|
(*expr) -> op = expr_known; |
|
break; |
|
|
|
+ case GENERATE: |
|
+#ifdef ENABLE_GENERATE |
|
+#ifndef GENERATE_ARGV_PREALLOC |
|
+#define GENERATE_ARGV_PREALLOC 10 |
|
+#endif |
|
+ skip_token(&val, (unsigned *)0, cfile); |
|
+ if (!expression_allocate (expr, MDL)) |
|
+ log_fatal ("can't allocate expression"); |
|
+ (*expr) -> op = expr_generate; |
|
+ |
|
+ token = next_token (&val, (unsigned *)0, cfile); |
|
+ if (token != LPAREN) goto nolparen; |
|
+ if (!parse_data_expression (&(*expr) -> data.generate.exec_path, |
|
+ cfile, lose)) goto nodata; |
|
+ /* Parse 0 to n comma-separated arguments: */ |
|
+ int argv_size = GENERATE_ARGV_PREALLOC * sizeof(struct expression *); |
|
+ int argv_size_increment = argv_size; |
|
+ struct expression **argv = dmalloc(argv_size, MDL); |
|
+ int argc = 0; |
|
+ int comma_expected = 1; |
|
+ for (;;) { |
|
+ if (comma_expected) { |
|
+ token = next_token(&val, (unsigned *)0, cfile); |
|
+ if (token == RPAREN) break; |
|
+ if (token != COMMA) parse_warn(cfile, "expected comma"); |
|
+ comma_expected = 0; |
|
+ continue; |
|
+ } |
|
+ /* Neither a right parenthesis nor a comma? We expect |
|
+ * an argument: */ |
|
+ if (!parse_data_expression(&(argv[argc]), cfile, lose)) { |
|
+ parse_warn(cfile, "expected data expression"); |
|
+ } |
|
+ ++ argc; |
|
+ comma_expected = 1; |
|
+ /* Is it time to extend argv? */ |
|
+ if (!(argc % GENERATE_ARGV_PREALLOC)) { |
|
+ argv_size += argv_size_increment; |
|
+ argv = realloc(argv, argv_size); |
|
+ } |
|
+ } |
|
+ (*expr) -> data.generate.argc = argc; |
|
+ (*expr) -> data.generate.argv = argv; |
|
+#if defined (DEBUG_EXPRESSION_PARSE) |
|
+ print_expression ("generate expression", *expr); |
|
+#endif |
|
+#else /* ! ENABLE_GENERATE */ |
|
+ parse_warn(cfile, "define ENABLE_GENERATE in site.h to " |
|
+ "enable generate(); expressions."); |
|
+ skip_to_semi(cfile); |
|
+ *lose = 1; |
|
+ return 0; |
|
+#endif /* ENABLE_GENERATE */ |
|
+ break; |
|
case SUBSTRING: |
|
skip_token(&val, (unsigned *)0, cfile); |
|
if (!expression_allocate (expr, MDL)) |
|
diff --git a/common/print.c b/common/print.c |
|
index b42e7bc5..832deadd 100644 |
|
--- a/common/print.c |
|
+++ b/common/print.c |
|
@@ -546,6 +546,7 @@ static unsigned print_subexpression (expr, buf, len) |
|
{ |
|
unsigned rv, left; |
|
const char *s; |
|
+ int argc, i; |
|
|
|
switch (expr -> op) { |
|
case expr_none: |
|
@@ -616,6 +617,24 @@ static unsigned print_subexpression (expr, buf, len) |
|
} |
|
break; |
|
|
|
+ case expr_generate: |
|
+ argc = expr -> data.generate.argc; |
|
+ if (len > 11) { |
|
+ rv = 10; |
|
+ strcpy(buf, "(generate "); |
|
+ rv += print_subexpression(expr -> data.generate.exec_path, |
|
+ buf + rv, len - rv - 1 - argc); |
|
+ for (i = 0; i < argc; ++ i) { |
|
+ buf[rv++] = ' '; |
|
+ rv += print_subexpression(expr -> data.generate.argv[i], |
|
+ buf + rv, |
|
+ len - rv - 1 - (argc - i - 1)); |
|
+ } |
|
+ buf[rv++] = ')'; |
|
+ buf[rv] = 0; |
|
+ return rv; |
|
+ } |
|
+ break; |
|
case expr_substring: |
|
if (len > 11) { |
|
rv = 8; |
|
diff --git a/common/tree.c b/common/tree.c |
|
index ea787543..a931b221 100644 |
|
--- a/common/tree.c |
|
+++ b/common/tree.c |
|
@@ -1079,6 +1079,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state, |
|
case expr_gethostname: |
|
case expr_v6relay: |
|
case expr_concat_dclist: |
|
+ case expr_generate: |
|
log_error ("Data opcode in evaluate_boolean_expression: %d", |
|
expr -> op); |
|
return 0; |
|
@@ -1123,6 +1124,86 @@ int evaluate_boolean_expression (result, packet, lease, client_state, |
|
return 0; |
|
} |
|
|
|
+int exec_read(exec_path, argv, argc, result) |
|
+ struct data_string *exec_path; |
|
+ struct data_string *argv; |
|
+ int argc; |
|
+ struct data_string *result; |
|
+{ |
|
+ int i; |
|
+ char **exec_argv; |
|
+ int pipe_fd[2]; |
|
+ int success = 1; |
|
+ /* Amount of data read from the child process: */ |
|
+ int data_read = 0; |
|
+ |
|
+ /* Prepare execvp()'s 2nd parameter: */ |
|
+ exec_argv = dmalloc((argc + 2) * sizeof(char *), MDL); |
|
+ exec_argv[0] = (char *)exec_path->data; |
|
+ for (i = 0; i < argc; ++ i) { |
|
+ exec_argv[i + 1] = (char *)argv[i].data; |
|
+ } |
|
+ exec_argv[argc + 1] = NULL; |
|
+ |
|
+ if (pipe(pipe_fd) == -1) { |
|
+ log_error("exec_read(): pipe() failed, errno == %d", errno); |
|
+ return 0; |
|
+ } |
|
+#ifndef GENERATE_BUFSIZE |
|
+#define GENERATE_BUFSIZE 1024 |
|
+#endif |
|
+ result->data = dmalloc(GENERATE_BUFSIZE, MDL); |
|
+ result->len = GENERATE_BUFSIZE; |
|
+ if (fork()) { |
|
+ /* Close unused side of the pipe: */ |
|
+ close(pipe_fd[1]); |
|
+ // Read the child process's stdout: |
|
+ for (result->len = 0; result->len < GENERATE_BUFSIZE; ) { |
|
+ data_read = read(pipe_fd[0], |
|
+ (void *)result->data + result->len, |
|
+ GENERATE_BUFSIZE - result->len); |
|
+ /* Handle errors: */ |
|
+ if (data_read == -1) { |
|
+ log_error("exec_read(): read() failed, " |
|
+ "errno == %d (%s)", |
|
+ errno, strerror(errno)); |
|
+ success = 0; |
|
+ break; |
|
+ } |
|
+ /* Handle end of file: */ |
|
+ if (!data_read) break; |
|
+ result->len += data_read; |
|
+#if defined (DEBUG_EXPRESSIONS) |
|
+ log_debug("exec_read(): read %4d bytes (total: %4d, " |
|
+ "max: %4d) from %s", |
|
+ data_read, result->len, GENERATE_BUFSIZE, |
|
+ exec_path->data); |
|
+#endif |
|
+ } |
|
+ // Read only one buffer worth of data then close the file |
|
+ // descriptor: |
|
+ close(pipe_fd[0]); |
|
+ } |
|
+ else { |
|
+ /* Close unused side of the pipe: */ |
|
+ close(pipe_fd[0]); |
|
+ /* No stdin: */ |
|
+ close(STDIN_FILENO); |
|
+ /* Pipe stdout to the parent process: */ |
|
+ dup2(pipe_fd[1], STDOUT_FILENO); |
|
+ execvp((const char *)exec_path->data, (char * const *)exec_argv); |
|
+ /* Still there? execvp must have failed. */ |
|
+ log_error("exec_read(): execvp(%s) failed, errno == %d (%s)", |
|
+ exec_path->data, |
|
+ errno, strerror(errno)); |
|
+ close(pipe_fd[1]); |
|
+ close(STDOUT_FILENO); |
|
+ exit(1); |
|
+ } |
|
+ free(exec_argv); |
|
+ return success; |
|
+} |
|
+ |
|
int evaluate_data_expression (result, packet, lease, client_state, |
|
in_options, cfg_options, scope, expr, file, line) |
|
struct data_string *result; |
|
@@ -1145,8 +1226,45 @@ int evaluate_data_expression (result, packet, lease, client_state, |
|
struct binding_value *bv; |
|
struct packet *relay_packet; |
|
struct option_state *relay_options; |
|
+#ifdef ENABLE_GENERATE |
|
+ struct data_string *argv_data; |
|
+ int argc; |
|
+#endif |
|
|
|
switch (expr -> op) { |
|
+#ifdef ENABLE_GENERATE |
|
+ case expr_generate: |
|
+ memset(&data, 0, sizeof data); |
|
+ /* Evaluate exec_path into a static string: */ |
|
+ s0 = evaluate_data_expression(&data, packet, lease, |
|
+ client_state, in_options, |
|
+ cfg_options, scope, |
|
+ expr -> data.generate.exec_path, |
|
+ MDL); |
|
+ /* Do we have arguments? */ |
|
+ argv_data = 0; |
|
+ argc = expr->data.generate.argc; |
|
+ if (argc) { |
|
+ argv_data = dmalloc(argc * sizeof(struct data_string), MDL); |
|
+ /* Evaluate all arguments into static strings: */ |
|
+ for (i = 0; i < argc; ++ i) { |
|
+ s1 = evaluate_data_expression(&argv_data[i], packet, lease, |
|
+ client_state, in_options, |
|
+ cfg_options, scope, |
|
+ expr -> data.generate.argv[i], |
|
+ MDL); |
|
+ } |
|
+ } |
|
+#if defined (DEBUG_EXPRESSIONS) |
|
+ log_debug("data: generate: about to fork %s with %d args", data.data, argc); |
|
+ for (i = 0; i < argc; ++ i) { |
|
+ log_debug("data: generate: arg #%lu: %s", i, argv_data[i].data); |
|
+ } |
|
+#endif |
|
+ int success = exec_read(&data, argv_data, argc, result); |
|
+ if (argv_data) free(argv_data); |
|
+ return success; |
|
+#endif /* ENABLE_GENERATE */ |
|
/* Extract N bytes starting at byte M of a data string. */ |
|
case expr_substring: |
|
memset (&data, 0, sizeof data); |
|
@@ -2289,6 +2407,7 @@ int evaluate_numeric_expression (result, packet, lease, client_state, |
|
case expr_null: |
|
case expr_gethostname: |
|
case expr_v6relay: |
|
+ case expr_generate: |
|
log_error ("Data opcode in evaluate_numeric_expression: %d", |
|
expr -> op); |
|
return 0; |
|
@@ -2865,6 +2984,21 @@ void expression_dereference (eptr, file, line) |
|
file, line); |
|
break; |
|
|
|
+ case expr_generate: |
|
+ log_debug("expression_dereference(): case expr_generate"); |
|
+ if (expr -> data.generate.exec_path) |
|
+ expression_dereference (&expr -> data.generate.exec_path, |
|
+ file, line); |
|
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) { |
|
+ expression_dereference(&expr -> data.generate.argv[i], |
|
+ file, line); |
|
+ } |
|
+ /* Do not forget to free the array of arguments itself: */ |
|
+ if (expr -> data.generate.argv) { |
|
+ free(expr -> data.generate.argv); |
|
+ expr -> data.generate.argv = 0; |
|
+ } |
|
+ break; |
|
case expr_substring: |
|
if (expr -> data.substring.expr) |
|
expression_dereference (&expr -> data.substring.expr, |
|
@@ -3072,6 +3206,7 @@ int is_data_expression (expr) |
|
expr->op == expr_config_option || |
|
expr->op == expr_null || |
|
expr->op == expr_gethostname || |
|
+ expr->op == expr_generate || |
|
expr->op == expr_v6relay); |
|
} |
|
|
|
@@ -3111,6 +3246,7 @@ int is_compound_expression (expr) |
|
expr -> op == expr_extract_int8 || |
|
expr -> op == expr_extract_int16 || |
|
expr -> op == expr_extract_int32 || |
|
+ expr -> op == expr_generate || |
|
expr -> op == expr_v6relay); |
|
} |
|
|
|
@@ -3173,6 +3309,7 @@ static int op_val (op) |
|
case expr_gethostname: |
|
case expr_v6relay: |
|
case expr_concat_dclist: |
|
+ case expr_generate: |
|
return 100; |
|
|
|
case expr_equal: |
|
@@ -3267,6 +3404,7 @@ enum expression_context op_context (op) |
|
case expr_gethostname: |
|
case expr_v6relay: |
|
case expr_concat_dclist: |
|
+ case expr_generate: |
|
return context_any; |
|
|
|
case expr_equal: |
|
@@ -3365,6 +3503,21 @@ int write_expression (file, expr, col, indent, firstp) |
|
col = token_print_indent (file, col, indent, "", "", ")"); |
|
break; |
|
|
|
+ case expr_generate: |
|
+ col = token_print_indent(file, col, indent, "", "", "generate"); |
|
+ col = token_print_indent(file, col, indent, " ", "", "("); |
|
+ scol = col; |
|
+ col = write_expression(file, expr -> data.generate.exec_path, |
|
+ col, scol, 1); |
|
+ for (int i = 0; i < expr -> data.generate.argc; ++ i) { |
|
+ col = token_print_indent(file, col, scol, "", " ", ","); |
|
+ col = write_expression(file, |
|
+ expr -> data.generate.argv[i], |
|
+ col, scol, 0); |
|
+ } |
|
+ col = token_print_indent (file, col, indent, "", "", ")"); |
|
+ break; |
|
+ |
|
case expr_suffix: |
|
col = token_print_indent (file, col, indent, "", "", "suffix"); |
|
col = token_print_indent (file, col, indent, " ", "", "("); |
|
diff --git a/configure.ac b/configure.ac |
|
index a7974385..f1ac7a39 100644 |
|
--- a/configure.ac |
|
+++ b/configure.ac |
|
@@ -133,6 +133,16 @@ if test "$enable_execute" != "no" ; then |
|
[Define to include execute() config language support.]) |
|
fi |
|
|
|
+# generate() support. |
|
+AC_ARG_ENABLE(generate, |
|
+ AS_HELP_STRING([--enable-generate],[enable support for generate() in config (default is yes)])) |
|
+# generate() is on by default, so define if it is not explicitly disabled. |
|
+if test "$enable_generate" != "no" ; then |
|
+ enable_generate="yes" |
|
+ AC_DEFINE([ENABLE_GENERATE], [1], |
|
+ [Define to include generate() config language support.]) |
|
+fi |
|
+ |
|
# Server tracing support. |
|
AC_ARG_ENABLE(tracing, |
|
AS_HELP_STRING([--enable-tracing],[enable support for server activity tracing (default is yes)])) |
|
@@ -1051,6 +1061,7 @@ Features: |
|
debug: $enable_debug |
|
failover: $enable_failover |
|
execute: $enable_execute |
|
+ generate: $enable_generate |
|
binary-leases: $enable_binary_leases |
|
dhcpv6: $enable_dhcpv6 |
|
delayed-ack: $enable_delayed_ack |
|
diff --git a/includes/config.h.in b/includes/config.h.in |
|
index 1608f671..f20ad4dc 100644 |
|
--- a/includes/config.h.in |
|
+++ b/includes/config.h.in |
|
@@ -28,6 +28,9 @@ |
|
/* Define to include execute() config language support. */ |
|
#undef ENABLE_EXECUTE |
|
|
|
+/* Define to include generate() config language support. */ |
|
+#undef ENABLE_GENERATE |
|
+ |
|
/* Define to include Failover Protocol support. */ |
|
#undef FAILOVER_PROTOCOL |
|
|
|
diff --git a/includes/dhctoken.h b/includes/dhctoken.h |
|
index 5920f4ff..76b492db 100644 |
|
--- a/includes/dhctoken.h |
|
+++ b/includes/dhctoken.h |
|
@@ -181,6 +181,7 @@ enum dhcp_token { |
|
TOKEN_FDDI = 379, |
|
AUTHORITATIVE = 380, |
|
TOKEN_NOT = 381, |
|
+ GENERATE = 382, |
|
AUTHENTICATION = 383, |
|
IGNORE = 384, |
|
ACCEPT = 385, |
|
diff --git a/includes/site.h b/includes/site.h |
|
index 2ef69e41..d088388d 100644 |
|
--- a/includes/site.h |
|
+++ b/includes/site.h |
|
@@ -212,6 +212,11 @@ |
|
|
|
/* #define ENABLE_EXECUTE */ |
|
|
|
+/* Define this if you want to be able to fetch the output of external commands |
|
+ during conditional evaluation. */ |
|
+ |
|
+/* #define ENABLE_GENERATE */ |
|
+ |
|
/* 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 c8cce4b2..9014a197 100644 |
|
--- a/includes/tree.h |
|
+++ b/includes/tree.h |
|
@@ -191,7 +191,8 @@ enum expr_op { |
|
expr_iregex_match, |
|
expr_gethostname, |
|
expr_v6relay, |
|
- expr_concat_dclist |
|
+ expr_concat_dclist, |
|
+ expr_generate, |
|
}; |
|
|
|
struct expression { |
|
@@ -203,6 +204,13 @@ struct expression { |
|
struct expression *offset; |
|
struct expression *len; |
|
} substring; |
|
+ struct { |
|
+ /* generate(): path to the executable file to run: */ |
|
+ struct expression *exec_path; |
|
+ /* generate(): arguments to pass to the executable: */ |
|
+ int argc; |
|
+ struct expression **argv; |
|
+ } generate; |
|
struct expression *equal [2]; |
|
struct expression *and [2]; |
|
struct expression *or [2]; |