diff --git a/common/conflex.c b/common/conflex.c
index 57058e92..7ca9ba34 100644
--- a/common/conflex.c
+++ b/common/conflex.c
@@ -963,6 +963,8 @@ intern(char *atom, enum dhcp_token dfv) {
 			return TOKEN_FREE;
 		break;
 	      case 'g':
+		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 76f2605f..843682db 100644
--- a/common/dhcp-eval.5
+++ b/common/dhcp-eval.5
@@ -191,6 +191,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 1767c926..29ab1508 100644
--- a/common/parse.c
+++ b/common/parse.c
@@ -3399,6 +3399,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
@@ -3624,6 +3626,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:
 		token = next_token (&val, (unsigned *)0, cfile);
 		if (!expression_allocate (expr, MDL))
diff --git a/common/print.c b/common/print.c
index 24209c24..41bcfcbb 100644
--- a/common/print.c
+++ b/common/print.c
@@ -529,6 +529,7 @@ static unsigned print_subexpression (expr, buf, len)
 {
 	unsigned rv, left;
 	const char *s;
+	int argc, i;
 
 	switch (expr -> op) {
 	      case expr_none:
@@ -599,6 +600,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 c00b7619..c9075dc1 100644
--- a/common/tree.c
+++ b/common/tree.c
@@ -1376,6 +1376,7 @@ int evaluate_boolean_expression (result, packet, lease, client_state,
 	      case expr_null:
 	      case expr_filename:
 	      case expr_sname:
+	      case expr_generate:
 		log_error ("Data opcode in evaluate_boolean_expression: %d",
 		      expr -> op);
 		return 0;
@@ -1420,6 +1421,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;
@@ -1440,8 +1521,43 @@ int evaluate_data_expression (result, packet, lease, client_state,
 	struct binding *binding;
 	unsigned char *s;
 	struct binding_value *bv;
+	struct data_string *argv_data;
+	int argc;
 
 	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);
@@ -2421,6 +2537,7 @@ int evaluate_numeric_expression (result, packet, lease, client_state,
 	      case expr_config_option:
 	      case expr_leased_address:
 	      case expr_null:
+	      case expr_generate:
 		log_error ("Data opcode in evaluate_numeric_expression: %d",
 		      expr -> op);
 		return 0;
@@ -3046,6 +3163,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,
@@ -3281,6 +3413,7 @@ int is_data_expression (expr)
 		expr->op == expr_host_decl_name ||
 		expr->op == expr_leased_address ||
 		expr->op == expr_config_option ||
+		expr->op == expr_generate ||
 		expr->op == expr_null);
 }
 
@@ -3325,6 +3458,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_dns_transaction);
 }
 
@@ -3384,6 +3518,7 @@ static int op_val (op)
 	      case expr_binary_or:
 	      case expr_binary_xor:
 	      case expr_client_state:
+	      case expr_generate:
 		return 100;
 
 	      case expr_equal:
@@ -3477,6 +3612,7 @@ enum expression_context op_context (op)
 	      case expr_arg:
 	      case expr_funcall:
 	      case expr_function:
+	      case expr_generate:
 		return context_any;
 
 	      case expr_equal:
@@ -3575,6 +3711,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 a6c8b77e..e42de55d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -99,6 +99,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,
 	AC_HELP_STRING([--enable-tracing],
@@ -620,6 +630,7 @@ Features:
   debug:         $enable_debug
   failover:      $enable_failover
   execute:       $enable_execute
+  generate:      $enable_generate
 
 Developer:
   ATF unittests : $atf_path
diff --git a/includes/config.h.in b/includes/config.h.in
index a762e51f..a0523813 100644
--- a/includes/config.h.in
+++ b/includes/config.h.in
@@ -19,6 +19,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 7c4ffb75..8b2736bf 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 33e252c0..f6a5da3e 100644
--- a/includes/site.h
+++ b/includes/site.h
@@ -185,6 +185,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 aada7619..4b7d8e5c 100644
--- a/includes/tree.h
+++ b/includes/tree.h
@@ -198,7 +198,8 @@ enum expr_op {
 	expr_ucase,
 	expr_lcase,
 	expr_regex_match,
-	expr_iregex_match
+	expr_iregex_match,
+	expr_generate,
 };
 
 struct expression {
@@ -210,6 +211,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];
