Ejemplo n.º 1
0
/** Execute a program.
 *
 * @param[in,out] ctx to allocate new VALUE_PAIR (s) in.
 * @param[out] out buffer to append plaintext (non valuepair) output.
 * @param[in] outlen length of out buffer.
 * @param[out] output_pairs list of value pairs - Data on child's stdout will be parsed and
 *	added into this list of value pairs.
 * @param[in] request Current request (may be NULL).
 * @param[in] cmd Command to execute. This is parsed into argv[] parts, then each individual argv
 *	part is xlat'ed.
 * @param[in] input_pairs list of value pairs - these will be available in the environment of the
 *	child.
 * @param[in] exec_wait set to 1 if you want to read from or write to child.
 * @param[in] shell_escape values before passing them as arguments.
 * @param[in] timeout amount of time to wait, in seconds.
 * @return
 *	- 0 if exec_wait==0.
 *	- exit code if exec_wait!=0.
 *	- -1 on failure.
 */
int radius_exec_program(TALLOC_CTX *ctx, char *out, size_t outlen, VALUE_PAIR **output_pairs,
			REQUEST *request, char const *cmd, VALUE_PAIR *input_pairs,
			bool exec_wait, bool shell_escape, int timeout)

{
	pid_t pid;
	int from_child;
#ifndef __MINGW32__
	char *p;
	pid_t child_pid;
	int comma = 0;
	int status, ret = 0;
	ssize_t len;
	char answer[4096];
#endif

	RDEBUG2("Executing: %s:", cmd);

	if (out) *out = '\0';

	pid = radius_start_program(cmd, request, exec_wait, NULL, &from_child, input_pairs, shell_escape);
	if (pid < 0) {
		return -1;
	}

	if (!exec_wait) {
		return 0;
	}

#ifndef __MINGW32__
	len = radius_readfrom_program(from_child, pid, timeout, answer, sizeof(answer));
	if (len < 0) {
		/*
		 *	Failure - radius_readfrom_program will
		 *	have called close(from_child) for us
		 */
		RERROR("Failed to read from child output");
		return -1;

	}
	answer[len] = '\0';

	/*
	 *	Make sure that the writer can't block while writing to
	 *	a pipe that no one is reading from anymore.
	 */
	close(from_child);

	if (len == 0) {
		goto wait;
	}

	/*
	 *	Parse the output, if any.
	 */
	if (output_pairs) {
		/*
		 *	HACK: Replace '\n' with ',' so that
		 *	fr_pair_list_afrom_str() can parse the buffer in
		 *	one go (the proper way would be to
		 *	fix fr_pair_list_afrom_str(), but oh well).
		 */
		for (p = answer; *p; p++) {
			if (*p == '\n') {
				*p = comma ? ' ' : ',';
				p++;
				comma = 0;
			}
			if (*p == ',') {
				comma++;
			}
		}

		/*
		 *	Replace any trailing comma by a NUL.
		 */
		if (answer[len - 1] == ',') {
			answer[--len] = '\0';
		}

		if (fr_pair_list_afrom_str(ctx, answer, output_pairs) == T_INVALID) {
			RERROR("Failed parsing output from: %s: %s", cmd, fr_strerror());
			strlcpy(out, answer, len);
			ret = -1;
		}
	/*
	 *	We've not been told to extract output pairs,
	 *	just copy the programs output to the out
	 *	buffer.
	 */

	} else if (out) {
		strlcpy(out, answer, outlen);
	}

	/*
	 *	Call rad_waitpid (should map to waitpid on non-threaded
	 *	or single-server systems).
	 */
wait:
	child_pid = rad_waitpid(pid, &status);
	if (child_pid == 0) {
		RERROR("Timeout waiting for child");

		return -2;
	}

	if (child_pid == pid) {
		if (WIFEXITED(status)) {
			status = WEXITSTATUS(status);
			if ((status != 0) || (ret < 0)) {
				RERROR("Program returned code (%d) and output '%s'", status, answer);
			} else {
				RDEBUG2("Program returned code (%d) and output '%s'", status, answer);
			}

			return ret < 0 ? ret : status;
		}
	}

	RERROR("Abnormal child exit: %s", fr_syserror(errno));
#endif	/* __MINGW32__ */

	return -1;
}
Ejemplo n.º 2
0
/*
 *	Read the users, huntgroups or hints file.
 *	Return a PAIR_LIST.
 */
int pairlist_read(TALLOC_CTX *ctx, char const *file, PAIR_LIST **list, int complain)
{
	FILE *fp;
	int mode = FIND_MODE_NAME;
	char entry[256];
	char buffer[8192];
	char const *ptr;
	VALUE_PAIR *check_tmp = NULL;
	VALUE_PAIR *reply_tmp = NULL;
	PAIR_LIST *pl = NULL, *t;
	PAIR_LIST **last = &pl;
	int lineno = 0;
	int entry_lineno = 0;
	FR_TOKEN parsecode;
#ifdef HAVE_REGEX_H
	VALUE_PAIR *vp;
	vp_cursor_t cursor;
#endif
	char newfile[8192];

	DEBUG2("reading pairlist file %s", file);

	/*
	 *	Open the file.  The error message should be a little
	 *	more useful...
	 */
	if ((fp = fopen(file, "r")) == NULL) {
		if (!complain)
			return -1;
		ERROR("Couldn't open %s for reading: %s",
				file, fr_syserror(errno));
		return -1;
	}

	/*
	 *	Read the entire file into memory for speed.
	 */
	while (fgets(buffer, sizeof(buffer), fp) != NULL) {
		lineno++;

		if (!feof(fp) && (strchr(buffer, '\n') == NULL)) {
			fclose(fp);
			ERROR("%s[%d]: line too long", file, lineno);
			pairlist_free(&pl);
			return -1;
		}

		/*
		 *	If the line contains nothing but whitespace,
		 *	ignore it.
		 */
		ptr = buffer;
		while (isspace((int) *ptr)) ptr++;

		if (*ptr == '#' || *ptr == '\n' || !*ptr) continue;

parse_again:
		if (mode == FIND_MODE_NAME) {
			/*
			 *	The user's name MUST be the first text on the line.
			 */
			if (isspace((int) buffer[0]))  {
				ERROR("%s[%d]: Entry does not begin with a user name",
				      file, lineno);
				fclose(fp);
				return -1;
			}

			/*
			 *	Get the name.
			 */		      
			ptr = buffer;
			getword(&ptr, entry, sizeof(entry), false);
			entry_lineno = lineno;

			/*
			 *	Include another file if we see
			 *	$INCLUDE filename
			 */
			if (strcasecmp(entry, "$INCLUDE") == 0) {
				while (isspace((int) *ptr)) ptr++;

				/*
				 *	If it's an absolute pathname,
				 *	then use it verbatim.
				 *
				 *	If not, then make the $include
				 *	files *relative* to the current
				 *	file.
				 */
				if (FR_DIR_IS_RELATIVE(ptr)) {
					char *p;

					strlcpy(newfile, file,
						sizeof(newfile));
					p = strrchr(newfile, FR_DIR_SEP);
					if (!p) {
						p = newfile + strlen(newfile);
						*p = FR_DIR_SEP;
					}
					getword(&ptr, p + 1, sizeof(newfile) - 1 - (p - newfile), false);
				} else {
					getword(&ptr, newfile, sizeof(newfile), false);
				}

				t = NULL;

				if (pairlist_read(ctx, newfile, &t, 0) != 0) {
					pairlist_free(&pl);
					ERROR("%s[%d]: Could not open included file %s: %s",
					       file, lineno, newfile, fr_syserror(errno));
					fclose(fp);
					return -1;
				}
				*last = t;

				/*
				 *	t may be NULL, it may have one
				 *	entry, or it may be a linked list
				 *	of entries.  Go to the end of the
				 *	list.
				 */
				while (*last)
					last = &((*last)->next);
				continue;
			} /* $INCLUDE ... */

			/*
			 *	Parse the check values
			 */
			rad_assert(check_tmp == NULL);
			rad_assert(reply_tmp == NULL);
			parsecode = fr_pair_list_afrom_str(ctx, ptr, &check_tmp);
			if (parsecode == T_INVALID) {
				pairlist_free(&pl);
				ERROR("%s[%d]: Parse error (check) for entry %s: %s",
					file, lineno, entry, fr_strerror());
				fclose(fp);
				return -1;
			}

			if (parsecode != T_EOL) {
				pairlist_free(&pl);
				talloc_free(check_tmp);
				ERROR("%s[%d]: Invalid text after check attributes for entry %s",
				      file, lineno, entry);
				fclose(fp);
				return -1;
			}

#ifdef HAVE_REGEX_H
			/*
			 *	Do some more sanity checks.
			 */
			for (vp = fr_cursor_init(&cursor, &check_tmp);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				if (((vp->op == T_OP_REG_EQ) ||
				     (vp->op == T_OP_REG_NE)) &&
				    (vp->da->type != PW_TYPE_STRING)) {
					pairlist_free(&pl);
					talloc_free(check_tmp);
					ERROR("%s[%d]: Cannot use regular expressions for non-string attributes in entry %s",
					      file, lineno, entry);
					fclose(fp);
					return -1;
				}
			}
#endif

			/*
			 *	The reply MUST be on a new line.
			 */
			mode = FIND_MODE_WANT_REPLY;
			continue;
		}

		/*
		 *	We COULD have a reply, OR we could have a new entry.
		 */
		if (mode == FIND_MODE_WANT_REPLY) {
			if (!isspace((int) buffer[0])) goto create_entry;

			mode = FIND_MODE_HAVE_REPLY;
		}

		/*
		 *	mode == FIND_MODE_HAVE_REPLY
		 */

		/*
		 *	The previous line ended with a comma, and then
		 *	we have the start of a new entry!
		 */
		if (!isspace((int) buffer[0])) {
		trailing_comma:
			pairlist_free(&pl);
			talloc_free(check_tmp);
			talloc_free(reply_tmp);
			ERROR("%s[%d]: Invalid comma after the reply attributes.  Please delete it.",
			      file, lineno);
			fclose(fp);
			return -1;
		}

		/*
		 *	Parse the reply values.  If there's a trailing
		 *	comma, keep parsing the reply values.
		 */
		parsecode = fr_pair_list_afrom_str(ctx, buffer, &reply_tmp);
		if (parsecode == T_COMMA) {
			continue;
		}

		/*
		 *	We expect an EOL.  Anything else is an error.
		 */
		if (parsecode != T_EOL) {
			pairlist_free(&pl);
			talloc_free(check_tmp);
			talloc_free(reply_tmp);
			ERROR("%s[%d]: Parse error (reply) for entry %s: %s",
			      file, lineno, entry, fr_strerror());
			fclose(fp);
			return -1;
		}

	create_entry:
		/*
		 *	Done with this entry...
		 */
		MEM(t = talloc_zero(ctx, PAIR_LIST));

		if (check_tmp) fr_pair_steal(t, check_tmp);
		if (reply_tmp) fr_pair_steal(t, reply_tmp);

		t->check = check_tmp;
		t->reply = reply_tmp;
		t->lineno = entry_lineno;
		check_tmp = NULL;
		reply_tmp = NULL;

		t->name = talloc_typed_strdup(t, entry);

		*last = t;
		last = &(t->next);

		/*
		 *	Look for a name.  If we came here because
		 *	there were no reply attributes, then re-parse
		 *	the current line, instead of reading another one.
		 */
		mode = FIND_MODE_NAME;
		if (feof(fp)) break;
		if (!isspace((int) buffer[0])) goto parse_again;
	}

	/*
	 *	We're at EOF.  If we're supposed to read more, that's
	 *	an error.
	 */
	if (mode == FIND_MODE_HAVE_REPLY) goto trailing_comma;

	/*
	 *	We had an entry, but no reply attributes.  That's OK.
	 */
	if (mode == FIND_MODE_WANT_REPLY) goto create_entry;

	/*
	 *	Else we were looking for an entry.  We didn't get one
	 *	because we were at EOF, so that's OK.
	 */

	fclose(fp);

	*list = pl;
	return 0;
}