*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
- * Copyright 2000 The FreeRADIUS server project
- * Copyright 2000 Michael J. Hartwick <hartwick@hartwick.com>
+ * Copyright 2000-2004,2006 The FreeRADIUS server project
*/
-static const char rcsid[] = "$Id$";
-#include "autoconf.h"
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/rad_assert.h>
#include <sys/file.h>
-#include <stdlib.h>
-#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <signal.h>
# define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
#endif
-#include "radiusd.h"
-#include "rad_assert.h"
-
+#define MAX_ARGV (256)
/*
* Execute a program on successful authentication.
* Return 0 if exec_wait == 0.
int exec_wait,
char *user_msg, int msg_len,
VALUE_PAIR *input_pairs,
- VALUE_PAIR **output_pairs)
+ VALUE_PAIR **output_pairs,
+ int shell_escape)
{
VALUE_PAIR *vp;
+ char mycmd[1024];
char answer[4096];
- char *argv[256];
- char *buf, *p;
+ char argv_buf[4096];
+ char *argv[MAX_ARGV];
+ const char *from;
+ char *p, *to;
int pd[2];
pid_t pid, child_pid;
int argc = -1;
int comma = 0;
int status;
+ int i;
int n, left, done;
if (user_msg) *user_msg = '\0';
if (output_pairs) *output_pairs = NULL;
+ if (strlen(cmd) > (sizeof(mycmd) - 1)) {
+ radlog(L_ERR|L_CONS, "Command line is too long");
+ return -1;
+ }
+
+ /*
+ * Check for bad escapes.
+ */
+ if (cmd[strlen(cmd) - 1] == '\\') {
+ radlog(L_ERR|L_CONS, "Command line has final backslash, without a following character");
+ return -1;
+ }
+
+ strlcpy(mycmd, cmd, sizeof(mycmd));
+
+ /*
+ * Split the string into argv's BEFORE doing radius_xlat...
+ */
+ from = cmd;
+ to = mycmd;
+ argc = 0;
+ while (*from) {
+ int length;
+
+ /*
+ * Skip spaces.
+ */
+ if ((*from == ' ') || (*from == '\t')) {
+ from++;
+ continue;
+ }
+
+ argv[argc] = to;
+ argc++;
+
+ if (argc >= (MAX_ARGV - 1)) break;
+
+ /*
+ * Copy the argv over to our buffer.
+ */
+ while (*from && (*from != ' ') && (*from != '\t')) {
+ if (to >= mycmd + sizeof(mycmd) - 1) {
+ return -1; /* ran out of space */
+ }
+
+ switch (*from) {
+ case '"':
+ case '\'':
+ length = rad_copy_string(to, from);
+ if (length < 0) {
+ radlog(L_ERR|L_CONS, "Invalid string passed as argument for external program");
+ return -1;
+ }
+ from += length;
+ to += length;
+ break;
+
+ case '%':
+ if (from[1] == '{') {
+ *(to++) = *(from++);
+
+ length = rad_copy_variable(to, from);
+ if (length < 0) {
+ radlog(L_ERR|L_CONS, "Invalid variable expansion passed as argument for external program");
+ return -1;
+ }
+ from += length;
+ to += length;
+ } else { /* FIXME: catch %%{ ? */
+ *(to++) = *(from++);
+ }
+ break;
+
+ default:
+ *(to++) = *(from++);
+ }
+ } /* end of string, or found a space */
+
+ *(to++) = '\0'; /* terminate the string */
+ }
+
+ /*
+ * We have to have SOMETHING, at least.
+ */
+ if (argc <= 0) {
+ radlog(L_ERR, "Exec-Program: empty command line.");
+ return -1;
+ }
+
/*
- * Open a pipe for child/parent communication, if
- * necessary.
+ * Expand each string, as appropriate.
+ */
+ to = argv_buf;
+ left = sizeof(argv_buf);
+ for (i = 0; i < argc; i++) {
+ int sublen;
+
+ /*
+ * Don't touch argv's which won't be translated.
+ */
+ if (strchr(argv[i], '%') == NULL) continue;
+
+ sublen = radius_xlat(to, left - 1, argv[i], request, NULL);
+ if (sublen <= 0) {
+ /*
+ * Fail to be backwards compatible.
+ *
+ * It's yucky, but it won't break anything,
+ * and it won't cause security problems.
+ */
+ sublen = 0;
+ }
+
+ argv[i] = to;
+ to += sublen;
+ *(to++) = '\0';
+ left -= sublen;
+ left--;
+
+ if (left <= 0) {
+ radlog(L_ERR, "Exec-Program: Ran out of space while expanding arguments.");
+ return -1;
+ }
+ }
+ argv[argc] = NULL;
+
+ /*
+ * Open a pipe for child/parent communication, if necessary.
*/
if (exec_wait) {
if (pipe(pd) != 0) {
output_pairs = NULL;
}
- /*
- * Do the translation (as the parent) of the command to
- * execute. This MAY involve calling other modules, so
- * we want to do it in the parent.
- */
- radius_xlat(answer, sizeof(answer), cmd, request, NULL);
- buf = answer;
-
- /*
- * Log the command if we are debugging something
- */
- DEBUG("Exec-Program: %s", buf);
-
- /*
- * Build vector list of arguments and execute.
- *
- * FIXME: This parsing gets excited over spaces in
- * the translated strings, e.g. User-Name = "aa bb"
- * is passed as two seperate arguments, instead of one.
- *
- * What we SHOULD do instead is to split the exec program
- * buffer first, and then do the translation on every
- * subsequent string.
- */
- p = strtok(buf, " \t");
- if (p) do {
- argv[++argc] = p;
- p = strtok(NULL, " \t");
- } while(p != NULL);
-
- argv[++argc] = p;
- if (argc == 0) {
- radlog(L_ERR, "Exec-Program: empty command line.");
- return -1;
+ if (exec_wait) {
+ pid = rad_fork(); /* remember PID */
+ } else {
+ pid = fork(); /* don't wait */
}
- if ((pid = rad_fork(exec_wait)) == 0) {
+ if (pid == 0) {
#define MAX_ENVP 1024
- int i, devnull;
+ int devnull;
char *envp[MAX_ENVP];
int envlen;
char buffer[1024];
/*
* Child process.
*
- * We try to be fail-safe here. So if ANYTHING
+ * We try to be fail-safe here. So if ANYTHING
* goes wrong, we exit with status 1.
*/
* want to leave dangling FD's for the child process
* to play funky games with, so we close them.
*/
- for (i = 3; i < 256; i++) {
- close(i);
- }
+ closefrom(3);
/*
* Set up the environment variables.
* variable...
*/
snprintf(buffer, sizeof(buffer), "%s=", vp->name);
- for (p = buffer; *p != '='; p++) {
- if (*p == '-') {
- *p = '_';
- } else if (isalpha((int) *p)) {
- *p = toupper(*p);
+ if (shell_escape) {
+ for (p = buffer; *p != '='; p++) {
+ if (*p == '-') {
+ *p = '_';
+ } else if (isalpha((int) *p)) {
+ *p = toupper(*p);
+ }
}
}
n = strlen(buffer);
- vp_prints_value(buffer+n, sizeof(buffer) - n, vp, 1);
+ vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape);
envp[envlen++] = strdup(buffer);
+
+ /*
+ * Don't add too many attributes.
+ */
+ if (envlen == (MAX_ENVP - 1)) break;
}
envp[envlen] = NULL;
+
execve(argv[0], argv, envp);
radlog(L_ERR, "Exec-Program: FAILED to execute %s: %s",
argv[0], strerror(errno));
}
/*
- * We're not waiting, exit, and ignore any child's
- * status.
+ * We're not waiting, exit, and ignore any child's status.
*/
if (!exec_wait) {
return 0;
* Parse the output, if any.
*/
if (done) {
- n = T_INVALID;
+ n = T_OP_INVALID;
if (output_pairs) {
/*
* For backwards compatibility, first check
}
}
- if (n == T_INVALID) {
- radlog(L_DBG, "Exec-Program-Wait: plaintext: %s", answer);
+ if (n == T_OP_INVALID) {
+ DEBUG("Exec-Program-Wait: plaintext: %s", answer);
if (user_msg) {
- strNcpy(user_msg, answer, msg_len);
+ strlcpy(user_msg, answer, msg_len);
}
} else {
/*
}
/*
- * Replace any trailing comma by a NUL.
+ * Replace any trailing comma by a NUL.
*/
if (answer[strlen(answer) - 1] == ',') {
answer[strlen(answer) - 1] = '\0';
}
radlog(L_DBG,"Exec-Program-Wait: value-pairs: %s", answer);
- if (userparse(answer, &vp) == T_INVALID) {
+ if (userparse(answer, &vp) == T_OP_INVALID) {
radlog(L_ERR, "Exec-Program-Wait: %s: unparsable reply", cmd);
} else {
*output_pairs = vp;
}
} /* else the answer was a set of VP's, not a text message */
- } /* else we didn't read anything from the child. */
+ } /* else we didn't read anything from the child */
/*
* Call rad_waitpid (should map to waitpid on non-threaded
* or single-server systems).
*/
- child_pid = rad_waitpid(pid, &status, 0);
+ child_pid = rad_waitpid(pid, &status);
+ if (child_pid == 0) {
+ radlog(L_DBG, "Exec-Program: Timeout waiting for child");
+ return 2;
+ }
+
if (child_pid == pid) {
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);