/* -*-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 #include #include #include #include /* apr */ #include #include #include #include #include #include #include #include #include #define PCRE2_CODE_UNIT_WIDTH 8 #include #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 [-t :] [-c []]\n", man ? "" : "Usage: ", cmd); qs_man_print(man, " [-p] [-u ] \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 \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 :\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 []\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 \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, " \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; }