summaryrefslogtreecommitdiffstats
path: root/tools/src/qsexec.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/src/qsexec.c376
1 files changed, 376 insertions, 0 deletions
diff --git a/tools/src/qsexec.c b/tools/src/qsexec.c
new file mode 100644
index 0000000..8a56b3e
--- /dev/null
+++ b/tools/src/qsexec.c
@@ -0,0 +1,376 @@
+/* -*-mode: c; indent-tabs-mode: nil; c-basic-offset: 2; -*-
+ */
+/**
+ * Command line execution utility for the quality of service module mod_qos.
+ *
+ * See http://mod-qos.sourceforge.net/ for further details.
+ *
+ * Copyright (C) 2023 Pascal Buchbinder
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+static const char revision[] = "$Id: qsexec.c 2654 2022-05-13 09:12:42Z pbuchbinder $";
+
+/* system */
+#include <stdio.h>
+#include <string.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+
+/* apr */
+#include <apr.h>
+#include <apr_strings.h>
+#include <apr_file_io.h>
+#include <apr_time.h>
+#include <apr_getopt.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_portable.h>
+#include <apr_support.h>
+
+#define PCRE2_CODE_UNIT_WIDTH 8
+#include <pcre2.h>
+
+#include "qs_util.h"
+
+#ifndef POSIX_MALLOC_THRESHOLD
+#define POSIX_MALLOC_THRESHOLD (10)
+#endif
+
+/* same as APR_SIZE_MAX which doesn't appear until APR 1.3 */
+#define QSUTIL_SIZE_MAX (~((apr_size_t)0))
+
+typedef struct {
+ int rm_so;
+ int rm_eo;
+} regmatch_t;
+
+static void usage(char *cmd, int man) {
+ if(man) {
+ //.TH [name of program] [section number] [center footer] [left footer] [center header]
+ printf(".TH %s 1 \"%s\" \"mod_qos utilities %s\" \"%s man page\n", qs_CMD(cmd), man_date, man_version, cmd);
+ }
+ printf("\n");
+ if(man) {
+ printf(".SH NAME\n");
+ }
+ printf("%s %s- parses the data received via stdin and executes the defined command on a pattern match.\n",
+ cmd, man ? "\\" : "");
+ printf("\n");
+ if(man) {
+ printf(".SH SYNOPSIS\n");
+ }
+ qs_man_print(man, "%s%s -e <pattern> [-t <number>:<sec>] [-c <pattern> [<command string>]]\n", man ? "" : "Usage: ", cmd);
+ qs_man_print(man, " [-p] [-u <user>] <command string>\n");
+ printf("\n");
+ if(man) {
+ printf(".SH DESCRIPTION\n");
+ } else {
+ printf("Summary\n");
+ }
+ qs_man_print(man, "%s reads log lines from stdin and searches for the defined pattern.\n", cmd);
+ qs_man_print(man, "It executes the defined command string on pattern match.\n");
+ printf("\n");
+ if(man) {
+ printf(".SH OPTIONS\n");
+ } else {
+ printf("Options\n");
+ }
+ if(man) printf(".TP\n");
+ qs_man_print(man, " -e <pattern>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Specifies the search pattern causing an event which shall trigger the\n");
+ qs_man_print(man, " command.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -t <number>:<sec>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Defines the number of pattern match within the the defined number of\n");
+ qs_man_print(man, " seconds in order to trigger the command execution. By default, every\n");
+ qs_man_print(man, " pattern match causes a command execution.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -c <pattern> [<command string>]\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Pattern which clears the event counter. Executes optionally a command\n");
+ qs_man_print(man, " if an event command has been executed before.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -p\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Writes data also to stdout (for piped logging).\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " -u <name>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Become another user, e.g. www-data.\n");
+ if(man) printf("\n.TP\n");
+ qs_man_print(man, " <command string>\n");
+ if(man) printf("\n");
+ qs_man_print(man, " Defines the event command string where $0-$9 are substituted by the\n");
+ qs_man_print(man, " submatches of the regular expression.\n");
+ printf("\n");
+ if(man) {
+ printf(".SH EXAMPLE\n");
+ } else {
+ printf("Example:\n");
+ }
+ qs_man_print(man, "Executes the deny.sh script providing the IP address of the\n");
+ qs_man_print(man, "client causing a mod_qos(031) messages whenever the log message\n");
+ qs_man_print(man, "appears 10 times within at most one minute:\n");
+ if(man) printf("\n");
+ qs_man_println(man, " ErrorLog \"|/usr/bin/%s -e \\'mod_qos\\(031\\).*, c=([0-9a-zA-Z:.]*)\\' -t 10:60 \\'/usr/local/bin/deny.sh $1\\'\"\n", cmd);
+ printf("\n");
+ if(man) {
+ printf(".SH SEE ALSO\n");
+ printf("qsdt(1), qsfilter2(1), qsgeo(1), qsgrep(1), qshead(1), qslog(1), qslogger(1), qspng(1), qsre(1), qsrespeed(1), qsrotate(1), qssign(1), qstail(1)\n");
+ printf(".SH AUTHOR\n");
+ printf("Pascal Buchbinder, http://mod-qos.sourceforge.net/\n");
+ } else {
+ printf("See http://mod-qos.sourceforge.net/ for further details.\n");
+ }
+ if(man) {
+ exit(0);
+ } else {
+ exit(1);
+ }
+}
+
+/*
+ * Substitutes for $0-$9 within the matching string.
+ * See ap_pregsub().
+ */
+char *qs_pregsub(apr_pool_t *pool, const char *input,
+ const char *source, size_t nmatch,
+ qs_regmatch_t pmatch[]) {
+ const char *src = input;
+ char *dest, *dst;
+ char c;
+ size_t no;
+ int len;
+ if(!source) {
+ return NULL;
+ }
+ if(!nmatch) {
+ return apr_pstrdup(pool, src);
+ }
+ /* First pass, find the size */
+ len = 0;
+ while((c = *src++) != '\0') {
+ if(c == '&')
+ no = 0;
+ else if (c == '$' && apr_isdigit(*src))
+ no = *src++ - '0';
+ else
+ no = 10;
+
+ if (no > 9) { /* Ordinary character. */
+ if (c == '\\' && (*src == '$' || *src == '&'))
+ src++;
+ len++;
+ }
+ else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
+ if(QSUTIL_SIZE_MAX - len <= pmatch[no].rm_eo - pmatch[no].rm_so) {
+ fprintf(stderr, "ERROR, integer overflow or out of memory condition");
+ return NULL;
+ }
+ len += pmatch[no].rm_eo - pmatch[no].rm_so;
+ }
+
+ }
+ dest = dst = apr_pcalloc(pool, len + 1);
+ /* Now actually fill in the string */
+ src = input;
+ while ((c = *src++) != '\0') {
+ if (c == '&')
+ no = 0;
+ else if (c == '$' && apr_isdigit(*src))
+ no = *src++ - '0';
+ else
+ no = 10;
+
+ if (no > 9) { /* Ordinary character. */
+ if (c == '\\' && (*src == '$' || *src == '&'))
+ c = *src++;
+ *dst++ = c;
+ }
+ else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) {
+ len = pmatch[no].rm_eo - pmatch[no].rm_so;
+ memcpy(dst, source + pmatch[no].rm_so, len);
+ dst += len;
+ }
+ }
+ *dst = '\0';
+ return dest;
+}
+
+int main(int argc, const char * const argv[]) {
+ const char *username = NULL;
+ int nr = 0;
+ char *line = calloc(1, MAX_LINE_BUFFER+1);
+ apr_pool_t *pool;
+ char *cmd = strrchr(argv[0], '/');
+ const char *command = NULL;
+ const char *pattern = NULL;
+ const char *clearcommand = NULL;
+ const char *clearpattern = NULL;
+ int executed = 0;
+ qs_regex_t *preg;
+ qs_regex_t *clearpreg;
+ qs_regmatch_t regm[QS_MAX_REG_MATCH];
+ time_t sec = 0;
+ int threshold = 0;
+ int counter = 0;
+ time_t countertime;
+ static int pass = 0;
+ apr_app_initialize(&argc, &argv, NULL);
+ apr_pool_create(&pool, NULL);
+
+ if(cmd == NULL) {
+ cmd = (char *)argv[0];
+ } else {
+ cmd++;
+ }
+
+ argc--;
+ argv++;
+ while(argc >= 1) {
+ if(strcmp(*argv,"-e") == 0) {
+ if (--argc >= 1) {
+ pattern = *(++argv);
+ }
+ } else if(strcmp(*argv,"-u") == 0) {
+ if (--argc >= 1) {
+ username = *(++argv);
+ }
+ } else if(strcmp(*argv,"-c") == 0) {
+ if (--argc >= 1) {
+ clearpattern = *(++argv);
+ if (argc >=1 && *argv[0] != '-') {
+ clearcommand = *(++argv);
+ argc--;
+ }
+ }
+ } else if(argc >= 1 && strcmp(*argv,"-t") == 0) {
+ if (--argc >= 1) {
+ char *str = apr_pstrdup(pool, *(++argv));
+ char *tme = strchr(str, ':');
+ if(tme == NULL) {
+ fprintf(stderr,"[%s]: ERROR, invalid number:sec format\n", cmd);
+ exit(1);
+ }
+ tme[0] = '\0';
+ tme++;
+ threshold = atoi(str);
+ sec = atol(tme);
+ if(threshold == 0 || sec == 0) {
+ fprintf(stderr,"[%s]: ERROR, invalid number:sec format\n", cmd);
+ exit(1);
+ }
+ }
+ } else if(argc >= 1 && strcmp(*argv,"-p") == 0) {
+ pass = 1;
+ } else if(strcmp(*argv,"-h") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"-?") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"-help") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"--help") == 0) {
+ usage(cmd, 0);
+ } else if(strcmp(*argv,"--man") == 0) {
+ usage(cmd, 1);
+ } else {
+ command = *argv;
+ }
+ argc--;
+ argv++;
+ }
+
+ if(pattern == NULL || command == NULL) {
+ usage(cmd, 0);
+ }
+
+ qs_setuid(username, cmd);
+
+ preg = apr_palloc(pool, sizeof(qs_regex_t));
+ if(qs_regcomp(preg, pattern, PCRE2_DOTALL) != 0) {
+ fprintf(stderr, "ERROR, could not compile '%s'\n", pattern);
+ exit(1);
+ }
+ apr_pool_pre_cleanup_register(pool, preg, qs_pregfree);
+ if(clearpattern) {
+ clearpreg = apr_palloc(pool, sizeof(qs_regex_t));
+ if(qs_regcomp(clearpreg, clearpattern, PCRE2_DOTALL) != 0) {
+ fprintf(stderr, "ERROR, could not compile '%s'\n", clearpattern);
+ exit(1);
+ }
+ apr_pool_pre_cleanup_register(pool, clearpattern, qs_pregfree);
+ }
+
+ while(fgets(line, MAX_LINE_BUFFER, stdin) != NULL) {
+ size_t len;
+ nr++;
+ if(pass) {
+ printf("%s", line);
+ fflush(stdout);
+ }
+ len = strlen(line);
+ if(clearpattern && (qs_regexec_len(clearpreg, line, len, QS_MAX_REG_MATCH, regm, 0) >= 0)) {
+ apr_pool_t *subpool;
+ apr_pool_create(&subpool, pool);
+ counter = 0;
+ countertime = 0;
+ if(clearcommand && executed) {
+ char *replaced = qs_pregsub(subpool, clearcommand, line, QS_MAX_REG_MATCH, regm);
+ if(!replaced) {
+ fprintf(stderr, "[%s]: ERROR, failed to substitute"
+ " submatches '%s' in (%s)\n", cmd, clearcommand, line);
+ } else {
+ int rc = system(replaced);
+ }
+ executed = 0;
+ }
+ apr_pool_destroy(subpool);
+ } else if(qs_regexec_len(preg, line, len, QS_MAX_REG_MATCH, regm, 0) >= 0) {
+ apr_pool_t *subpool;
+ char *replaced;
+ apr_pool_create(&subpool, pool);
+ replaced = qs_pregsub(subpool, command, line, QS_MAX_REG_MATCH, regm);
+ if(!replaced) {
+ fprintf(stderr, "[%s]: ERROR, failed to substitute"
+ " submatches '%s' in (%s)\n", cmd, command, line);
+ } else {
+ counter++;
+ if(counter == 1) {
+ countertime = time(NULL);
+ }
+ if(counter >= threshold) {
+ if(countertime + sec >= time(NULL)) {
+ int rc = system(replaced);
+ executed = 1;
+ }
+ countertime = 0;
+ counter = 0;
+ }
+ }
+ apr_pool_destroy(subpool);
+ }
+ }
+
+ apr_pool_destroy(pool);
+ return 0;
+}