*** empty log message ***
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 2 Jul 2003 08:35:13 +0000 (08:35 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 2 Jul 2003 08:35:13 +0000 (08:35 +0000)
mmsoftware/TODO
mmsoftware/apache-mmstat/apache-mmstat.8
mmsoftware/apache-mmstat/apache-mmstat.c [new file with mode: 0644]

index cb3a016..ddef384 100644 (file)
@@ -1,4 +1,11 @@
-$Id: TODO,v 1.16 2003/07/02 02:38:41 mmondor Exp $
+$Id: TODO,v 1.17 2003/07/02 08:31:27 mmondor Exp $
+
+
+
+APACHE-MMSTAT
+=============
+
+- Finish makefile/install scripts.
 
 
 
index 585f0af..d0a073d 100644 (file)
@@ -1,4 +1,4 @@
-.\" $Id: apache-mmstat.8,v 1.1 2003/07/02 08:27:51 mmondor Exp $
+.\" $Id: apache-mmstat.8,v 1.2 2003/07/02 08:35:13 mmondor Exp $
 .\"
 .\" Copyright (C) 2003, Matthew Mondor
 .\" All rights reserved.
@@ -120,7 +120,7 @@ apache|vhost|<vhost>|errors|<address>
 .Ed
 .Pp
 Several of these may be enabled and disabled depending on the other specified
-.Ar allowed
+.Ar allow
 options.
 .It Nm R
 Record statistics on vhost-specific referers (only possible if
diff --git a/mmsoftware/apache-mmstat/apache-mmstat.c b/mmsoftware/apache-mmstat/apache-mmstat.c
new file mode 100644 (file)
index 0000000..cd0c0d3
--- /dev/null
@@ -0,0 +1,389 @@
+/* $Id: apache-mmstat.c,v 1.1 2003/07/02 08:27:51 mmondor Exp $ */
+
+/*
+ * Copyright (C) 2003, Matthew Mondor
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by Matthew Mondor.
+ * 4. The name of Matthew Mondor may not be used to endorse or promote
+ *    products derived from this software without specific prior written
+ *    permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ *    any GNU Public License derivate.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <signal.h>
+
+#include <mmtypes.h>
+#include <mmreadcfg.h> /* Only used for user/group related functions */
+#include <mmstring.h>
+#include <mmstat.h>
+
+
+
+#define LINESIZ 4096
+
+
+
+int main(int, char **);
+static void log_parse(mmstat_t *, char *);
+static void sighandler(int);
+
+
+
+/* Our list of command line arguments */
+enum argline {
+    ARG_COMMAND = 0,
+    ARG_USER,
+    ARG_GROUPS,
+    ARG_CONF,
+    ARG_OPTIONS,
+    ARG_MAX
+};
+
+/* Logline columns which we expect */
+enum logline {
+    COL_VHOST = 0,
+    COL_REMOTEADDR,
+    COL_REFERER,
+    COL_USERAGENT,
+    COL_BYTES,
+    COL_METHOD,
+    COL_REQUEST,
+    COL_STATUS,
+    COL_MAX
+};
+
+
+
+/* Globals */
+bool LOG_GLOBAL;       /* G */
+bool LOG_VHOST;                /* V */
+bool LOG_REFERER;      /* R */
+bool LOG_USERAGENT;    /* U */
+bool LOG_REMOTEADDR;   /* A */
+bool LOG_REQUEST;      /* F */
+
+
+
+int main(int argc, char **argv)
+{
+    char *linebuf;
+    mmstat_t mms;
+    struct sigaction act;
+
+    /* Setup a signal handler for SIGSEGV so that we prevent core dumping if
+     * we ever crash.
+     */
+    act.sa_handler = sighandler;
+    act.sa_flags = SA_NOCLDWAIT;
+    sigemptyset(&act.sa_mask);
+    sigaction(SIGSEGV, &act, NULL);
+
+    /* We're normally started from apache, and run as the superuser. We
+     * therefore do all we can to be safe until we drop privileges... Let's
+     * first redirect unnecessary filedescriptors to /dev/null. But, keep
+     * stdin, of course, which we'll read logs from later on.
+     */
+    {
+       int fd;
+
+       if ((fd = open("/dev/null", O_RDWR)) != -1) {
+           dup2(fd, 1);
+           dup2(fd, 2);
+           if (fd > 2)
+               close(fd);
+       }
+    }
+
+    /* Now perform sanity checking on launching mode and user supplied
+     * arguments.
+     */
+
+    /* Apache launches us as uid 0, we'll drop privileges soon however */
+    if (getuid() != 0) {
+       syslog(LOG_NOTICE, "%s: Not started as uid 0 from apache!? (uid %d)",
+               argv[ARG_COMMAND], getuid());
+       exit(-1);
+    }
+
+    /* We only accept a fixed number of arguments so that we restrict the
+     * need for getopt() and other libraries, or a more complex system when
+     * we're root.
+     */
+    if (argc != ARG_MAX) {
+       syslog(LOG_NOTICE, "%s: Started with wrong parameters",
+               argv[ARG_COMMAND]);
+       exit(-1);
+    }
+
+    /* Make sure that supplied user and group(s) are valid, and if so,
+     * drop privileges already.
+     */
+    {
+       uid_t uid;
+       gid_t *gids;
+       int ngids;
+
+       if ((uid = mmgetuid(argv[ARG_USER])) == -1) {
+           syslog(LOG_NOTICE, "%s: Unknown user '%s'", argv[ARG_COMMAND],
+                   argv[ARG_USER]);
+           exit(-1);
+       }
+
+       if (!(gids = mmgetgidarray(&ngids, argv[ARG_GROUPS]))) {
+           syslog(LOG_NOTICE, "%s: One of following groups unknown: '%s'",
+                   argv[ARG_COMMAND], argv[ARG_GROUPS]);
+           exit(-1);
+       }
+
+       /* NOTE: mmdropprivs() uses setegid(2), setgid(2), setgroups(2),
+        * seteuid(2), setgid(2), and then verifies that it really changed to
+        * the expected user permissions, in order to return TRUE on success.
+        */
+       if (!mmdropprivs(uid, gids, ngids)) {
+           syslog(LOG_NOTICE, "%s: Cannot change uid and gids to safe privs",
+                   argv[ARG_COMMAND]);
+           exit(-1);
+       }
+       mmfreegidarray(gids);
+    }
+
+    /* Et voila, we're no longer the superuser. We can now proceed and
+     * perform our slave chores as mortals. First set the MMSTATCONF
+     * environment variable for mmstat(3) API to load the right configuration
+     * file. Then, call the logging function, just because we want the main
+     * loop out of main().
+     */
+    /* Log nothing by default, enable parts which were requested only. */
+    LOG_GLOBAL = LOG_VHOST = LOG_REFERER = LOG_USERAGENT = LOG_REMOTEADDR =
+       LOG_REQUEST = FALSE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'G'))
+       LOG_GLOBAL = TRUE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'V'))
+       LOG_VHOST = TRUE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'R'))
+       LOG_REFERER = TRUE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'U'))
+       LOG_USERAGENT = TRUE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'A'))
+       LOG_REMOTEADDR = TRUE;
+    if (mm_strchr(argv[ARG_OPTIONS], 'F'))
+       LOG_REQUEST = TRUE;
+
+    if (setenv("MMSTATCONF", argv[ARG_CONF], TRUE) != 0) {
+       syslog(LOG_NOTICE, "%s: Cannot setenv(3)", argv[ARG_COMMAND]);
+       exit(-1);
+    }
+
+    if (!mmstat_init(&mms, TRUE, TRUE)) {
+       syslog(LOG_NOTICE, "%s: Cannot initialize mmstat(3)",
+               argv[ARG_COMMAND]);
+       exit(-1);
+    }
+
+    /* We preferably don't want the line buffer to be on the stack */
+    if ((linebuf = malloc(LINESIZ)) == NULL) {
+       syslog(LOG_NOTICE, "%s: Cannot allocate line buffer",
+               argv[ARG_COMMAND]);
+       exit(-1);
+    }
+
+    log_parse(&mms, linebuf);
+
+    /* NOTREACHED */
+    exit(0);
+}
+
+
+/* ARGSUSED */
+static void sighandler(int sig)
+{
+    /* We only catch SIGSEGV with this handler, and exit normally. */
+    exit(0);
+}
+
+
+/* When we get called, privileges have been revoked and mmstat(3) has been
+ * successfully initialized.
+ */
+static void log_parse(mmstat_t *mms, char *line)
+{
+    char *cols[COL_MAX + 1];
+
+    /* We'll exit if the pipe is closed by apache */
+    while (fgets(line, LINESIZ - 1, stdin) == line) {
+       size_t len;
+       int status = 1;
+       char *ptr;
+
+       /* Strip ending "\n". If there are none, we ignore the line as it
+        * consists of an abnormally long request which exceeds LINESIZ.
+        * It's next continueing line will then obviously not match the
+        * expected columns and will as a result also be ignored. It's
+        * unfortunate that fgets(3) cannot report that the line was not
+        * terminated in a faster way, without us having to go strip the
+        * line termination, but oh well, I don't want to use mmfd(3) for
+        * this. I would if I needed additional rate/bandwidth limits however.
+        */
+       len = mm_strlen(line);
+       if (len > 0 && line[len - 1] == '\n')
+           line[len - 1] = '\0';
+       else continue;
+
+       /* Strip dangerous characters from line */
+       for (ptr = line; *ptr != '\0'; ptr++) {
+           if (*ptr < 32) {
+               status = 0;
+               break;
+           }
+           switch (*ptr) {
+           case ' ':           /* No spaces in key names */
+               *ptr = '_';
+               break;
+           case '*':           /* Considered as wildcards by mmstat(3) */
+           case '?':
+           case '%':           /* Why not, we use stdarg(3) alot */
+               *ptr = '$';
+               break;
+           }
+       }
+       if (status == 0)
+           continue;
+
+       /* Now separate line in columns and verify if the number of columns
+        * is the expected one. If it's not, ignore it.
+        */
+       if (mm_strspl(cols, line, COL_MAX, '|') != COL_MAX)
+           continue;
+
+       /* Verify that status is valid, it consists of the last field. If
+        * a malformed request or a user-supplied entry containing '|' was
+        * present, this would simply ignore the line, the correct behavior.
+        */
+       if ((status = atoi(cols[COL_STATUS])) == 0)
+           continue;
+
+       /* Start an mmstat(3) transaction, which makes sure that everything
+        * be processed atomically. This also is the recovery unit.
+        * Using a transaction is not a requirement for atomicity in this
+        * case, but it's more efficient than performing each operation
+        * independantly, beleive it or not (only one I/O syscall required).
+        */
+       mmstat_transact(mms, TRUE);
+
+       if (LOG_GLOBAL)
+           mmstat(mms, STAT_UPDATE, 1, "apache|total|requests");
+
+       switch (status) {
+       case 200:       /* Success */
+           {
+               long bytes;
+
+               bytes = atol(cols[COL_BYTES]);
+
+               if (LOG_GLOBAL)
+                   mmstat(mms, STAT_UPDATE, bytes, "apache|total|bytes");
+               if (LOG_VHOST) {
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|requests",
+                           cols[COL_VHOST]);
+                   mmstat(mms, STAT_UPDATE, bytes, "apache|vhost|%s|bytes",
+                           cols[COL_VHOST]);
+                   if (LOG_REQUEST)
+                       mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|%s|%s",
+                               cols[COL_VHOST], cols[COL_METHOD],
+                               cols[COL_REQUEST]);
+                   if (LOG_REFERER)
+                       mmstat(mms, STAT_UPDATE, 1,
+                               "apache|vhost|%s|referer|%s", cols[COL_VHOST],
+                               cols[COL_REFERER]);
+                   if (LOG_USERAGENT)
+                       mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|agent|%s",
+                               cols[COL_VHOST], cols[COL_USERAGENT]);
+               }
+           }
+           break;
+       case 404:       /* Not found */
+           if (LOG_GLOBAL)
+               mmstat(mms, STAT_UPDATE, 1, "apache|total|errors");
+           if (LOG_VHOST) {
+               mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|errors",
+                       cols[COL_VHOST]);
+               if (LOG_REFERER)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|referer|%s",
+                           cols[COL_VHOST], cols[COL_REFERER]);
+               if (LOG_USERAGENT)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|agent|%s",
+                           cols[COL_VHOST], cols[COL_USERAGENT]);
+           }
+           break;
+       case 400:       /* Bad request */
+           if (LOG_GLOBAL)
+               mmstat(mms, STAT_UPDATE, 1, "apache|total|errors");
+           if (LOG_VHOST) {
+               if (LOG_REFERER)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|referer|%s",
+                           cols[COL_VHOST], cols[COL_REFERER]);
+               if (LOG_USERAGENT)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|agent|%s",
+                           cols[COL_VHOST], cols[COL_USERAGENT]);
+           }
+           break;
+       case 403:       /* Denied */
+           if (LOG_GLOBAL) {
+               mmstat(mms, STAT_UPDATE, 1, "apache|total|denied");
+               if (LOG_REMOTEADDR)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|denied|%s",
+                           cols[COL_REMOTEADDR]);
+           }
+           if (LOG_VHOST) {
+               mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|denied",
+                       cols[COL_VHOST]);
+               if (LOG_REMOTEADDR)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|denied|%s",
+                           cols[COL_VHOST], cols[COL_REMOTEADDR]);
+               if (LOG_REFERER)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|referer|%s",
+                           cols[COL_VHOST], cols[COL_REFERER]);
+               if (LOG_USERAGENT)
+                   mmstat(mms, STAT_UPDATE, 1, "apache|vhost|%s|agent|%s",
+                           cols[COL_VHOST], cols[COL_USERAGENT]);
+           }
+           break;
+       }
+
+       /* Close transaction, that is, commit any changes. Once this is
+        * called, the statistics are relayed to the mmstat(8) daemon.
+        */
+       mmstat_transact(mms, FALSE);
+    }
+}