[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: reading qmail Log File



+ Bob Hutchinson <hutchlists@xxxxxxxxxxxx>:

| > For that reason, I interpose an extra process between the
| > tcp socket and qmail-smtp, which logs certain responses from
| > qmail-smtp:
| >
| >   smtpd: pid 16927 250_ok_1112441572_qp_16929/
| 
| hmmm nice, I don't suppose you'd have a patch handy for that?

Not in the form of a patch, but I'll give you a do-it-yourself kit.
Drop the attached files into the qmail source.  Add lines to Makefile:

loggit: \
loggit.o env.a fd.a fs.a str.a stralloc.a alloc.a str.a strerr.a error.a substdio.a
	./load loggit env.a fd.a fs.a str.a stralloc.a alloc.a str.a strerr.a error.a substdio.a

loggit.o:\
loggit.c
	./compile loggit.c

and add loggit as a dependency to the "it" target.
Also, add loggit and loggit.o to TARGETS, and these lines to hier.c:

  c(auto_qmail,"bin","loggit",auto_uido,auto_gidq,0755);
  c(auto_qmail,"man/man8","loggit.8",auto_uido,auto_gidq,0644);


In my /service/smtp/run script, I have

PATH=/var/qmail/bin:$PATH
... (setting other shell variables) ...
exec \
  env - PATH=$PATH \
  tcpserver -v -x $smtpctl -u $qmaild -g $nofiles 0 smtp \
  loggit \
  qmail-smtpd 2>&1

(The files are dated 1998-09-08.  I haven't looked at them since.)


- Harald
#include <stdlib.h>
#include "str.h"
#include "stralloc.h"
#include "env.h"
#include "strerr.h"
#include "fd.h"
#include "fmt.h"

typedef struct { char *name; char **opt; } logopt;

/******************************************************************************
   Customization section:

   Add a line defining log_something like below,
   making sure to end the array of strings with a zero.
   Then add an entry to avail_logopts[], making sure it ends with {0,0}.

   The special case log_none is recognized, saving a pipe() and fork().
*/

static char *log_none[] = {0};
static char *log_all[] = {"",0};
static char *log_resp[] = {"4","5","250 ok ",0};

static logopt avail_logopts[] = 
{{"NONE", log_none},
 {"ALL", log_all},
 {"RESP", log_resp},
 {0, 0}};

/* The default log options are here */
static char **do_logopts[2] = { log_none, log_resp };

/* End of customization section
******************************************************************************/

static char **do_logopt = 0;


static void die_pipe() { strerr_die1x(111,"Couldn't open a pipe"); }
static void die_fork() { strerr_die1x(111,"Couldn't fork"); }
static void die_exec() { strerr_die1x(111,"Couldn't exec"); }
static void die_mem() { strerr_die1x(111,"Couldn't allocate memory"); }
static void die_fd() { strerr_die1x(111,"Couldn't move a file descriptor"); }
static void die_read() { strerr_die1x(111,"Read error"); }
static void die_write() { strerr_die1x(111,"Write error"); }
static void die_usage() { strerr_die1x(100,"Usage: loggit program arg ..."); }

static stralloc logstring={0};

static int checkline(char *buf, int count) {
  char **rule, *m, *r, *l;
  int x;

  for (rule=do_logopt; *rule; ++rule)
    {
      for (r=*rule, l=buf, x=count; *r && x; ++r, ++l, --x)
	if (*r != *l) break;
      if (!*r) return 1;
    }
  return 0;
}

char **getlogopt(char **dflt, char *name) {
  char *envname, *x, *y;
  logopt *try;
  if (envname=env_get(name))
    for (try=avail_logopts; try->name; ++try)
      for (x=try->name, y=envname; *x == *y; ++x, ++y)
	if (!*x) return try->opt;
  return dflt;
}

void do_log(void) {
  unsigned char *buf, c, x;
  int pos, done, trunc, i, w;
  if (!(buf = (char *) malloc(1024))) die_mem();
  done=0; trunc=0; pos=0;
  while (!done) {
    switch (read(0, buf+pos, 1)) {
    case -1: die_read();
    case 0: done=1; break;
    case 1: c=buf[pos++];
    }
    if (c == '\n' || pos == 1023 || done) {
      for (i=0; i<pos; i+=w) {
	w = write(1, buf+i, pos-i);
	if (w <= 0) die_write();
      }
      if (!trunc && checkline(buf,pos)) {
	for (i=0; i<pos; i++) {
	  x = buf[i];
	  if (x == ' ' || x == '\t') { buf[i] = '_'; continue; }
	  if (x == '\r') { buf[i] = '/'; continue; }
	  if (x == '\n') continue;
	  if (x&127 < 32 || x == 127) { buf[i] = '?'; continue; }
	}
	if (c != '\n') buf[pos++]='\n';
	for (i=0; i<logstring.len; i+=w) {
	  w = write(2, logstring.s+i, logstring.len-i);
	  if (w <= 0) break; /* ignore logging errors */
	}
	for (i=0; i<pos; i+=w) {
	  w = write(2, buf+i, pos-i);
	  if (w <= 0) break; /* ignore logging errors */
	}
      }
      trunc = (c != '\n');
      pos = 0;
    }
  }
  _exit(0);
}

void setup_log(int fd) {
  int p[2], child;
  if (pipe(p)==-1) die_pipe();
  switch (child=fork()) {
  case -1: die_fork();
  case 0:
    if (fd_move(fd,p[fd])==-1) die_fd();
    close(p[1-fd]);
    return;
  default:
    if (fd_move(1-fd,p[1-fd])==-1) die_fd();
    close(p[fd]);
    do_logopt=do_logopts[fd];
    /* ToDo: if fd == 0, watch the child */
    do_log(); /* Does not return */
  }
}

int main(int argc, char *argv[]) {
  int fd;
  char *envprog, *logname, s[FMT_ULONG];
  if (argc<2) die_usage();

  if (envprog=env_get("RUNPROGRAM")) {
    execlp(envprog, envprog, 0);
    die_exec(); }

  if (!(logname=env_get("PROGNAME"))) logname = "smtpd";
  if (!stralloc_copys(&logstring,logname)) die_mem();
  if (!stralloc_cats(&logstring,": pid ")) die_mem();
  s[fmt_ulong(s,getpid())]=0;
  if (!stralloc_cats(&logstring,s)) die_mem();
  if (!stralloc_cats(&logstring," z")) die_mem();
  logstring.s[--logstring.len]=0;

  do_logopts[0]=getlogopt(do_logopts[0],"LOGOPT0");
  do_logopts[1]=getlogopt(do_logopts[1],"LOGOPT1");
  for (fd=1; fd>=0; --fd)
    if (do_logopts[fd] != log_none) setup_log(fd);
  execvp(argv[1], argv+1);
  die_exec();
}
.TH loggit 8
.SH NAME
loggit \- Interpose a pipe and log the traffic
.SH SYNOPSIS
.B loggit
.I command
[
.I args ...
]
.SH DESCRIPTION
.B loggit
runs
.IR command ,
copying the standard output of
.I command
to standard output as well as a filtered, modified version to standard
error.  Optionally, standard input is treated similarly.  The precise
actions of
.B loggit
are controlled by environment variables.

.B loggit
was designed to run under
.I tcpserver
and in turn, to run an SMTP daemon.  Its default behaviour is to log
every temporary and permanent failure response from the daemon, as well
as the response confirming the acceptance of a message.

.B loggit
buffers data, and writes nothing until a newline is seen or the buffer
grows to 1023 bytes.  Thus, it can only be used to log daemons
involved in line oriented protocols, or deadlocks may occur.

.SH ENVIRONMENT
If
.B RUNPROGRAM
is set, the program named by this variable is run without arguments,
and no further action is taken.

If
.B LOGOPT0
or
.B LOGOPT1
is set, it must contain a string defining log actions to be taken on
file descriptor 0 or 1, respectively.  The value can be one of NONE,
ALL, or RESP, which mean to log nothing, everything, or any lines
beginning with one of the strings "4", "5", "250 ok ".  Note, however,
that other possibilities are easily added to the program by editing
the source code and recompiling.  Default values are
.BR LOGOPT0 =NONE
and
.BR LOGOPT1 =RESP.

If
.B PROGNAME
is set, it names a string to be added to the front of every log line.
Default is "smtpd".

.SH "SEE ALSO"
tcpserver(1), qmail-smtpd(8)