/** 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; }
/* * 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; }