On Sun, Aug 06, 2006 at 09:21:04AM -0700, Chris Frost wrote:
> For this reason I have written mswatch. mswatch listens for mailbox changes
> from two sets of mailboxes and runs runs a mailbox synchronizer
> (I've developed mswatch using mbsync) when there are mailbox changes.
> This allows mailbox changes to propagate quickly and more efficiently than
> periodically synchronizing the mailboxes.
> 
heh ... see attachements (.forward entries & procmail rules not
included). i'm using this for years, but never got around to making
something publicable of it. oh, right, i even published a precursor of it
years ago: http://developer.kde.org/~ossi/sw/fastmail.html

i'll have a look at mswatch when i find time.

> mswatch currently includes one mailbox watcher which watches Maildirs
> - mailbox deletions are not propagated (the deleted mailbox is recreated)
>   when "mbsync <DELETED_MAILBOX>" is ran
>
possible todo. it's pretty dangerous, though.

> - maildir mailbox creations are not propagated when "mbsync <NEW_MAILBOX>"
>   is ran (mbsync errors)
>
mbsync -C

-- 
Hi! I'm a .signature virus! Copy me into your ~/.signature, please!
--
Chaos, panic, and disorder - my work here is done.
PROGS := tunneld tunnelk tunnelm

prefix := /usr/local

all: debug

debug:
        $(MAKE) CFLAGS+=-DDEBUG ndebug

ndebug: $(PROGS)

clean:
        rm -f $(PROGS) tunneld-*.sock tunnelk.log

%: %.o
#       $(CC) -s -o $@ $<
        $(CC) -o $@ $<

%.o: %.c tunnel.h tunnel.c
        $(CC) -O2 $(CFLAGS) -W -Wall -Wmissing-prototypes -Wwrite-strings 
-Wshadow -o $@ -c $<
#       $(CC) -g3 -W -Wall -Wmissing-prototypes -Wwrite-strings -Wshadow -o $@ 
-c $<

%.s: %.c tunnel.h tunnel.c
        $(CC) -g3 -W -Wall -Wmissing-prototypes -Wwrite-strings -Wshadow -S $<

tunnelm: tunnelk
        ln -sf $^ $@

install: tunneld
        cp tunneld $(prefix)/sbin
        strip $(prefix)/sbin/tunneld

upload ul:
        scp Makefile *.c *.h mail:src/mail/tunnel

.SUFFIXES:
static void 
Signal (int sig, void (*sh)(void), int flags)
{
    struct sigaction sa;

    sa.sa_handler = (void (*)(int))sh;
    sigemptyset (&sa.sa_mask);
    sa.sa_flags = flags;
    (void) sigaction (sig, &sa, 0);
}

int now, lping;

static void
reader (void *buf, int count)
{
    int ret, rlen;

    for (rlen = 0; rlen < count; rlen += ret) {
	ret = read(fd_in, (void *)((char *)buf + rlen), count - rlen);
	if (ret <= 0)
	    erraction("Tunnel read error\n");
    }
    if (rlen != count)
	erraction("Tunnel broken\n");
    alarm(pongtime);
#ifdef DUMP_COMM
    loggen("r:");
    for (rlen = 0; rlen < count; rlen++)
	printf(" %02x", ((unsigned char *)buf)[rlen]);
    printf("\n");
#endif
}

static void
writer (const void *buf, int count)
{
#ifdef DUMP_COMM
    int rlen;
#endif

    if (write(fd_out, buf, count) != count)
	erraction("Tunnel broken\n");
    lping = now;
#ifdef DUMP_COMM
    loggen("w:");
    for (rlen = 0; rlen < count; rlen++)
	printf(" %02x", ((unsigned char *)buf)[rlen]);
    printf("\n");
#endif
}

typedef enum {
    tComm,		// tunnel socket
    tLst,		// listeners
    tPreConn,		// dns lookup
    tConn,		// connections
    tNot,		// notifier socket
} fdType;

static struct pollfd *pollfds;
static struct {
    ushort type:4;	// fdType
    ushort ctype:4;	// connType
    ushort conn:8;	// int
} *fdparms;
static int npolls, rpolls;

typedef enum { cUnused = -1, cClosing = -2 } connState;

static int *conns;
static int nconns;

static int
add_fd(int fd, fdType type, connType ctype, int conn, int ev)
{
    int n = npolls++;
    if (rpolls < npolls) {
	rpolls = npolls;
	if (!(pollfds = realloc(pollfds, npolls * sizeof(*pollfds))) ||
	    !(fdparms = realloc(fdparms, npolls * sizeof(*fdparms))))
	    die("Out of memory\n");
    }
    pollfds[n].fd = fd;
    pollfds[n].events = ev; /* POLLERR & POLLHUP implicit */
    fdparms[n].type = type;
    fdparms[n].ctype = ctype;
    fdparms[n].conn = conn;
    dbg("added fd %d, type %d, ctype %d, conn %d, ev %#x\n", 
	fd, type, ctype, conn, ev);
    return n;
}

static void
close_poll(int n)
{
    int i;

    if (n >= npolls)
	die("Internal error: closing unknown poll\n");
    dbg("closing fd %d\n", pollfds[n].fd);
    close(pollfds[n].fd);
    npolls--;
    memcpy(pollfds + n, pollfds + n + 1, (npolls - n) * sizeof(*pollfds));
    memcpy(fdparms + n, fdparms + n + 1, (npolls - n) * sizeof(*fdparms));
    for (i = 0; i < nconns; i++)
	if (conns[i] > n)
	    conns[i]--;
}

static void
is_valid(int n)
{
    if (n >= nconns || conns[n] == cUnused)
	die("Internal error: unknown connection\n");
}

static int
is_open(int n)
{
    is_valid(n);
    return (conns[n] >= 0) && (pollfds[conns[n]].events & POLLIN);
}

//#define DEBUG_PING

typedef enum { pPing, pNotify,
	       pOpenReq, pOpenOk, pData, pClose } packType;
typedef enum { cDaemon, cForward } connType;

#ifdef DEBUG_PING
# define pingtime 5
# define pongtime 30
#else
# define pingtime 60
# define pongtime 100
#endif

#define MAX_BLOCK_LEN 1024

#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4)
# define ATTR_UNUSED __attribute__((unused))
# define ATTR_NORETURN __attribute__((noreturn))
# define ATTR_PRINTFLIKE(fmt,var) __attribute__((format(printf,fmt,var)))
#else
# define ATTR_UNUSED
# define ATTR_NORETURN
# define ATTR_PRINTFLIKE(fmt,var)
#endif

#ifndef DEBUG
# define D_ATTR_UNUSED ATTR_UNUSED
#else
# define D_ATTR_UNUSED
#endif

//#define VALGRIND 1
//#define DUMP_COMM
//#define SPY_SOCKS
//#define NO_DAEMON

#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <grp.h>
#include <pwd.h>

#include "tunnel.h"


static inline void idie(const char *msg, ...) ATTR_PRINTFLIKE(1,2) ATTR_NORETURN;
static inline void die(const char *msg, ...) ATTR_PRINTFLIKE(1,2) ATTR_NORETURN;
static inline void loggen(const char *msg, ...) ATTR_PRINTFLIKE(1,2);
static inline void dbg(const char *msg D_ATTR_UNUSED, ...) ATTR_PRINTFLIKE(1,2);

static void
logit(const char *msg, va_list va)
{
    time_t tim;
    char fmt[256], dbuf[32];

    (void) time(&tim);
    strftime(dbuf, sizeof(dbuf), "%Y-%m-%d %H:%M:%S", localtime(&tim));
    snprintf(fmt, sizeof(fmt), "%s %s", dbuf, msg);
    vprintf(fmt, va);
    fflush(stdout);
}

static inline void 
dbg(const char *msg D_ATTR_UNUSED, ...)
{
#ifdef DEBUG
    va_list va;

    va_start(va, msg);
    logit(msg, va);
    va_end(va);
#endif
}

static inline void 
loggen(const char *msg, ...)
{
    va_list va;

    va_start(va, msg);
    logit(msg, va);
    va_end(va);
}

static void sigterm(void) ATTR_NORETURN;

static inline void 
die(const char *msg, ...)
{
    va_list va;
	
    va_start(va, msg);
    logit(msg, va);
    sigterm();
}

static inline void 
idie(const char *msg, ...)
{
    va_list va;
	
    va_start(va, msg);
    vfprintf(stderr, msg, va);
    exit(1);
}

#include <setjmp.h>
static sigjmp_buf borked;
#define erraction(msg) do { loggen(msg); siglongjmp(borked, 1); } while(0)

static int fds[2];
#define fd_in fds[1]
#define fd_out fd_in

#include "tunnel.c"


static const char *pidfile = "/var/run/tunneld.pid";
static const char *post_conn, *post_disconn;

static int tun, nodelay, broken;

static char *
makeEnv(const char *name, const char *value)
{
    char *result;

    if (!(result = malloc((unsigned) (strlen(name) + strlen(value) + 2))))
	idie("Out of memory\n");
    sprintf(result, "%s=%s", name, value);
    return result;
}

static const char *notifier = ".mailinfo";
static struct {
    const char *remote, *local;
} *maps;
static int nmaps;

static void 
notify_user(const char *user, const char *sig, const char *mbx)
{
    char *env[4];
    struct passwd *pwd;
    struct stat st;
    int i;
    char mailinfo[128];

    for (i = 0; i < nmaps; i++)
	if (!strcmp(user, maps[i].remote)) {
	    user = maps[i].local;
	    break;
	}
    if (*user) {
	dbg("Running user notification for %s: %s %s\n", user, mbx, sig);
	if (!(pwd = getpwnam(user))) {
	    loggen("Unknown user %s\n", user);
	    return;
	}
    } else {
	dbg("Running user notification: %s %s\n", mbx, sig);
	if (!(pwd = getpwuid(getuid()))) {
	    loggen("Whoops - current user does not exist\n");
	    return;
	}
    }
    snprintf(mailinfo, sizeof(mailinfo), "%s/%s", pwd->pw_dir, notifier);
    if (stat(mailinfo, &st))
	return;
    if (!fork()) {
	endpwent();
	close(0); open("/dev/null", O_RDONLY);
	
	if ((!*user ||
	     ((!initgroups(pwd->pw_name, pwd->pw_gid) &&
	      !setgid(pwd->pw_gid) && !setuid(pwd->pw_uid)))) && 
            !chdir(pwd->pw_dir))
	{
	    env[0] = makeEnv("HOME", pwd->pw_dir);
	    env[1] = makeEnv("USER", pwd->pw_name);
	    env[2] = makeEnv("LOGNAME", pwd->pw_name);
	    env[3] = 0;
	    execle(mailinfo, mailinfo, sig, mbx, (char *)0, env);
	    loggen("Cannot exec %s.\n", mailinfo);
	}
	exit(1);
    }
}

static void 
killem(void)
{
    if (tun) {
	kill(tun, SIGTERM);
	while (tun)
	    pause();
    }
}

static void 
sigterm(void)
{
    killem();
    unlink(pidfile);
    loggen("tunnel daemon exiting ...\n");
    exit(0);
}

static void 
sigchld(void)
{
    pid_t pid;

dbg("SIGCHLD\n");
    while ((pid = waitpid(-1, 0, WNOHANG)) > 0) {
dbg("wait returns %d\n", pid);
	if (pid == tun) {
	    dbg("Tunnel command exited.\n");
	    tun = 0;
	    broken++;
	}
    }
}

static void 
sighup(void)
{
    dbg("SIGHUP received.\n");
    nodelay++;
    broken++;
}

static const char *waitfile;

static void 
sigalrm(void)
{
    struct stat st;

    if (!waitfile || stat(waitfile, &st))
	erraction("Ping timeout.\n");
    else
	alarm(pongtime);
}

static void
accept_conn(int fd, connType ctype, unsigned char *data, int dlen)
{
    int n;
    unsigned char buff[256];

    if ((fd = accept(fd, 0, 0)) < 0) {
	dbg("Accept failed: %s\n", strerror(errno));
	return;
    }
    fcntl(fd, F_SETFD, FD_CLOEXEC);

    for (n = 0; n < nconns; n++)
	if (conns[n] == cUnused)
	    goto haven;
    if (!(conns = realloc(conns, ++nconns * sizeof(*conns))))
	die("Out of memory\n");
  haven:
    conns[n] = add_fd(fd, tConn, ctype, n, 0);
    buff[0] = pOpenReq;
    buff[1] = n;
    buff[2] = ctype;
    memcpy(buff + 3, data, dlen);
    writer(buff, 3 + dlen);
}

static void
start_listen (int port, connType ctype, int idx)
{
    struct sockaddr_in inaddr;
    int fd;

    if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
	idie("Cannot open socket\n");
    fcntl(fd, F_SETFL, O_NONBLOCK);
    fcntl(fd, F_SETFD, FD_CLOEXEC);
#ifdef VALGRIND
    memset(&inaddr, 0, sizeof(inaddr));
#endif
    inaddr.sin_family = AF_INET;
    inaddr.sin_addr.s_addr = htonl(0x7f000001);
    inaddr.sin_port = htons(port);
    if (bind(fd, (struct sockaddr *)&inaddr, sizeof(inaddr)) < 0)
	idie("Cannot bind socket to port %d\n", port);
    if (listen(fd, 5) < 0)
	idie("Cannot listen on port %d\n", port);
    (void) add_fd(fd, tLst, ctype, idx, POLLIN);
}

typedef struct {
    ushort lport;
    const char *rcmd;
} rdaemon;

static rdaemon *rdms;
static int nrdms;

typedef struct {
    ushort lport, rport;
    const char *rhost;
} forward;

static forward *fwds;
static int nfwds;

#ifdef SPY_SOCKS
# define qput(s) printf(s)
# define qblock(b,l) \
    do { \
	int i; \
	for (i = 0; i < l; i++) { \
	    char c = (b)[i]; \
	    if (c == '\r' || c == '\n' || (c >= 32 && c < 127)) \
		putchar(c); \
	    else \
		printf("\\%#02ux", c); \
	} \
    } while(0)
#else
# define qput(s)
# define qblock(b,l)
#endif

static int
serve(void)
{
    forward *fwd;
    rdaemon *rdm;
    register int j, p, c;
    int i, connected = 0;
    volatile int in_poll;
    unsigned char buffer[MAX_BLOCK_LEN];

    Signal(SIGALRM, sigalrm, 0);
    in_poll = add_fd(fd_in, tComm, 0, 0, POLLIN);
    broken = 0;
    alarm(pongtime);
    lping = 0;
    now = time(0);
    if (sigsetjmp(borked, 1)) {
      bork:
	broken++;
    } else
    for (;;) {
	if (lping + pingtime <= now) {
	    buffer[0] = pPing;
	    writer(buffer, 1);
	}
	i = poll(pollfds, npolls, (lping - now + pingtime) * 1000);
	if (broken)
	    break;
	now = time(0);
	if (!i)
	    continue;
	if (i < 0) {
	    if (errno == EINTR)
		continue;
	    else
		die("Poll failed: %s\n", strerror(errno));
	}
	for (i = 0; i < npolls; i++) {
	    if (pollfds[i].revents & POLLNVAL)
		die("Internal error: polling invalid fd\n");
	    else if (pollfds[i].revents & (POLLERR | POLLHUP)) {
		dbg("error on fd %d, type %d, ctype %d\n", pollfds[i].fd, fdparms[i].type, fdparms[i].ctype);
		switch (fdparms[i].type) {
		    case tComm:
			goto bork;
		    case tLst:
			die("Internal error: listening socked broken\n");
		    case tConn:
			p = i;
			c = fdparms[p].conn;
		      reqclose:
			buffer[0] = pClose;
			buffer[1] = c;
			writer(buffer, 2);
			close_poll(p);
			conns[c] = cClosing;
			goto next;
		}
	    } else if (pollfds[i].revents & POLLIN) {
		dbg("input on fd %d, type %d, ctype %d\n", pollfds[i].fd, fdparms[i].type, fdparms[i].ctype);
		switch (fdparms[i].type) {
		    case tComm:
			reader(buffer, 1);
			switch(buffer[0]) {
			    case pData:
				reader(buffer + 1, 3);
				j = buffer[2] | (buffer[3] << 8);
				reader(buffer + 4, j);
				c = buffer[1];
				if (is_open(c)) {
				    qput("> ");
				    qblock(buffer + 4, j);
				    p = conns[c];
				    if (write(pollfds[p].fd, buffer + 4, j) != j)
					goto reqclose;
				}
				break;
			    case pOpenOk:
				reader(buffer + 1, 1);
				c = buffer[1];
				is_valid(c);
				p = conns[c];
				pollfds[p].events = POLLIN;
				break;
			    case pClose:	// req, ack & openfail
				reader(buffer + 1, 1);
				c = buffer[1];
				is_valid(c);
				if (conns[c] >= 0) {
				    writer(buffer, 2);
				    close_poll(conns[c]);
				}
				conns[c] = cUnused;
				goto next;
			    case pNotify:
				reader(buffer + 1, 3);
				j = (int)buffer[1] + (int)buffer[2] + (int)buffer[3];
				reader(buffer + 4, j);
				notify_user((char *)buffer + 4,
					    (char *)buffer + 4 + buffer[1],
					    (char *)buffer + 4 + buffer[1] + buffer[2]);
				break;
			    case pPing:
				if (!connected) {
				    connected = 1;
				    if (post_conn) {
					dbg("running post-connect command\n");
					system(post_conn);
				    }
				}
				break;
			}	// switch(buffer[0])
			break;
		    case tLst:
			switch(fdparms[i].ctype) {
			    case cDaemon:
				rdm = rdms + fdparms[i].conn;
				j = strlen(rdm->rcmd) + 1;
				buffer[0] = j;
				memcpy(buffer + 1, rdm->rcmd, j);
				j++;
				break;
			    case cForward:
				fwd = fwds + fdparms[i].conn;
				buffer[0] = fwd->rport & 255;
				buffer[1] = fwd->rport >> 8;
				j = strlen(fwd->rhost) + 1;
				buffer[2] = j;
				memcpy(buffer + 3, fwd->rhost, j);
				j += 3;
				break;
			    default:
				die("Internal error: unknown connection type %d\n", fdparms[i].ctype);
			}
			accept_conn(pollfds[i].fd, fdparms[i].ctype, buffer, j);
			goto next;
		    case tConn:
			p = i;
			c = fdparms[i].conn;
			j = read(pollfds[p].fd, buffer + 4, sizeof(buffer) - 4);
			if (!j)
			    goto reqclose;
			if (j > 0) {
			    if (is_open(c)) {
				qput("< ");
				qblock(buffer + 4, j);
				buffer[0] = pData;
				buffer[1] = c;
				buffer[2] = j & 255;
				buffer[3] = j >> 8;
				writer(buffer, j + 4);
			    }
			}
			break;
		}	// switch(type)
	    }	// if(readable)
	}	// i++
      next: ;
    }	// for(;;)
    alarm(0);
    for (i = 0; i < nconns; i++) {
	if (conns[i] >= 0)
	    close_poll(conns[i]);
    }
    nconns = 0;
    close_poll(in_poll);
    if (connected && post_disconn) {
	dbg("running post-disconnect command\n");
	system(post_disconn);
    }
    return connected;
}

static char *
istrdup(const char *str)
{
    char *es;
    if (!(es = strdup(str)))
	idie("Out of memory\n");
    return es;
}

static void
parse_lforward(const char *str)
{
    char *s, *es;
    int n = nfwds++;
    if (!(fwds = realloc(fwds, nfwds * sizeof(*fwds))))
	idie("Out of memory\n");
    fwds[n].rhost = "";
    es = istrdup(str);
    if ((s = strsep(&es, ":"))) {
	if ((fwds[n].lport = atoi(s)) > 0) {
	    if ((s = strsep(&es, ":"))) {
		if (*s)
		    fwds[n].rhost = istrdup(s);
		if ((s = strsep(&es, ":"))) {
		    if ((fwds[n].rport = atoi(s)) > 0) {
			free(es);
			return;
		    }
		}
	    }
	}
    }
    idie("Invalid port forward specification %s\n", str);
}

static void
parse_daemon(const char *str)
{
    char *s, *es;

    int n = nrdms++;
    if (!(rdms = realloc(rdms, nrdms * sizeof(*rdms))))
	idie("Out of memory\n");
    es = istrdup(str);
    if ((s = strsep(&es, ":"))) {
	if ((rdms[n].lport = atoi(s)) > 0) {
	    if ((s = strsep(&es, ":"))) {
		rdms[n].rcmd = istrdup(s);
		free(es);
		return;
	    }
	}
    }
    idie("Invalid remote daemon specification %s\n", str);
}

static void
parse_mapping(const char *str)
{
    char *s, *es;

    int n = nmaps++;
    if (!(maps = realloc(maps, nmaps * sizeof(*maps))))
	idie("Out of memory\n");
    es = istrdup(str);
    if ((s = strsep(&es, ":"))) {
	if ((maps[n].remote = istrdup(s))) {
	    if ((s = strsep(&es, ":"))) {
		maps[n].local = istrdup(s);
		free(es);
		return;
	    }
	}
    }
    idie("Invalid user mapping specification %s\n", str);
}

static char **
parseCmd(const char *ostr)
{
    char **arr, *sp, *nstr, *str, *dstr;
    int nst;

    if (!(arr = malloc (sizeof(char *))) ||
	!(nstr = str = strdup(ostr)))
	idie("Out of memory\n");
dbg("parsing '%s'\n", str);
    for (nst = 0, sp = dstr = 0;;) {
	char c = *str++;
	if (!c || isspace(c)) {
	    if (sp) {
dbg(" word is '%.*s'\n", dstr - sp, sp);
		if (!(arr = realloc (arr, (nst + 2) * sizeof(char *))) ||
		    !(arr[nst] = strndup(sp, dstr - sp)))
		    idie("Out of memory\n");
		arr[++nst] = 0;
		sp = 0;
	    }
	    if (!c) {
		free (nstr);
		return arr;
	    }
	} else {
	    if (!sp)
		dstr = sp = str - 1;
	    if (c == '\'') {
		for (;;) {
		    c = *str++;
		    if (!c)
			idie("Unexpected EOS in '-quote: '%s'.\n", ostr);
		    if (c == '\'')
			break;
		    *dstr++ = c;
		}
	    } else if (c == '"') {
		for (;;) {
		    c = *str++;
		    if (!c)
			idie("Unexpected EOS in \"-quote: '%s'.\n", ostr);
		    if (c == '"')
			break;
		    if (c == '\\') {
			c = *str++;
			if (!c)
			    idie("Unexpected EOS in \\-quote: '%s'\n", ostr);
		    }
		    *dstr++ = c;
		}
	    } else {
		if (c == '\\') {
		    c = *str++;
		    if (!c)
			idie("Unexpected EOS in \\-quote: '%s'\n", ostr);
		}
		*dstr++ = c;
	    }
	}
    }
}

static char **
parse_tunnel(const char *tunnel, const char *client)
{
    char tunbuf[1024];

    if (sizeof(tunbuf) <= 
	(unsigned)snprintf(tunbuf, sizeof(tunbuf), tunnel, client))
	idie("Tunnel command too long\n");
    return parseCmd (tunbuf);
}

int 
main(int argc ATTR_UNUSED, char **argv)
{
    char **argp;
    const char *logfile = "/dev/tty11";
    const char *client = "bin/tunnelk";
    const char *tunnel = 0;
#ifndef NO_DAEMON
    FILE *f;
#endif
    int i;

    for (argp = argv + 1; *argp; argp++) {
	if (!strcmp(*argp, "-l"))	// log file
	    logfile = *++argp;
	else if (!strcmp(*argp, "-p"))	// pid file
	    pidfile = *++argp;
	else if (!strcmp(*argp, "-L"))	// local port -> remote port
	    parse_lforward(*++argp);
	else if (!strcmp(*argp, "-D"))	// local port -> remote daemon
	    parse_daemon(*++argp);
	else if (!strcmp(*argp, "-e"))	// remote side tunnel executable
	    client = *++argp;
	else if (!strcmp(*argp, "-t"))	// tunnel command
	    tunnel = *++argp;
	else if (!strcmp(*argp, "-n"))	// mail arrived notification command
	    notifier = *++argp;
	else if (!strcmp(*argp, "-m"))	// map remote:local user
	    parse_mapping(*++argp);
	else if (!strcmp(*argp, "-c"))	// post-connect program
	    post_conn = *++argp;
	else if (!strcmp(*argp, "-d"))	// post-disconnect program
	    post_disconn = *++argp;
	else if (!strcmp(*argp, "-w"))	// ssh is waiting for user input indicator
	    waitfile = *++argp;
	else
	    idie("Unknown command line option '%s'\n", *argp);
    }
    if (!tunnel)
	idie("You must specify a tunnel command (-t \"cmd\")\n");
    if (!strstr(tunnel, "%s"))
	idie("Tunnel command must contain '%%s'\n");
    argp = parse_tunnel (tunnel, client);

    for (i = 0; i < nfwds; i++)
	start_listen(fwds[i].lport, cForward, i);
    for (i = 0; i < nrdms; i++)
	start_listen(rdms[i].lport, cDaemon, i);

#ifndef NO_DAEMON
    close(0);
    open("/dev/null", O_RDONLY);
    close(1);
    if (open(logfile, O_WRONLY | O_CREAT | O_APPEND, 0666) != 1)
	idie("Cannot open log file\n"); 
    dup2(1, 2);
    if (fork())
	exit(0);
    setsid();
    if ((f = fopen(pidfile, "w"))) {
	fprintf(f, "%d\n", getpid());
	fclose(f);
    }
#else
    Signal(SIGINT, sigterm, 0);
#endif

    Signal(SIGTERM, sigterm, 0);
    Signal(SIGHUP, sighup, 0);
    Signal(SIGCHLD, sigchld, SA_RESTART);
    Signal(SIGPIPE, (void (*)(void))SIG_IGN, 0);
    dbg("tunnel daemon started\n");
    for (;;) {
	if (socketpair(PF_UNIX, SOCK_STREAM, 0, fds) < 0) {
	    loggen("socketpair() failed\n");
	    exit(1);
	}
	if ((tun = fork()) < 0) {
	    loggen("fork() failed\n");
	    exit(1);
	} else if (!tun) {
	    dbg("Running tunnel command.\n");
	    dup2(fds[0], 1); dup2(fds[0], 0); close(fds[0]); close(fds[1]);
	    execvp(*argp, argp);
	    loggen("Cannot exec '%s'.\n", *argp);
	    exit(1);
	}
	close(fds[0]);
	fcntl(fds[1], F_SETFD, FD_CLOEXEC);
	if (serve())
	    nodelay++;
	killem();
	if (nodelay)
	    nodelay = 0;
	else {
	    if (!broken)
		loggen("Tunnel command exited unexpectedly.\n");
	    loggen("Sleeping 10 minutes ...\n");
	    sleep(600);
	    loggen("Woken up.\n");
	}
    }
    return 0;
}
#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <sys/wait.h>
#include <sys/poll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdarg.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <glob.h>

#include "tunnel.h"

#ifdef DEBUG
FILE *errfile;
#endif

static inline void dbg(const char *msg D_ATTR_UNUSED, ...) ATTR_PRINTFLIKE(1,2);
static inline void die(const char *msg, ...) ATTR_PRINTFLIKE(1,2) ATTR_NORETURN;


static inline void 
dbg(const char *msg D_ATTR_UNUSED, ...)
{
#ifdef DEBUG
    va_list va;
	
    va_start(va, msg);
    vfprintf(errfile, msg, va);
    va_end(va);
    if (strchr(msg, '\n'))
	fflush(errfile);
#endif
}

static inline void 
die(const char *msg D_ATTR_UNUSED, ...)
{
#ifdef DEBUG
    va_list va;
	
    va_start(va, msg);
    vfprintf(errfile, msg, va);
#endif
    exit(1);
}

#define erraction(msg) die(msg)

#define fd_in 0
#define fd_out 1

#include "tunnel.c"


static void 
sigchld(void)
{
    while (waitpid(-1, 0, WNOHANG) > 0);
}

static void 
sigalrm(void)
{
    die("Ping timeout\n");
}

static int 
tcp_connect(struct in_addr addr, u_short port)
{
    struct sockaddr_in server_in;
    int s;

    if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
	return -1;
    fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK);

    server_in.sin_family = AF_INET;
    server_in.sin_addr = addr;
    server_in.sin_port = port;
    connect(s, (struct sockaddr *)&server_in, sizeof(server_in));

    return s;
}

static void
set_conn(int conn, int p)
{
    if (conn > nconns)
	die("Invalid connection index\n");
    if (conn == nconns)
	if (!(conns = realloc(conns, ++nconns * sizeof(*conns))))
	    die("Out of memory\n");
    conns[conn] = p;
}

static char **
splitargs(const char *string)
{
    const char *word;
    char **argv;
    int nstrs;
    char ch;

    if (!(argv = malloc (sizeof(char *))))
	die("Out of memory\n");
    nstrs = 0;
    for (word = string; ; ++string) {
	ch = *string;
	if (!ch || ch == ' ' || ch == '\t') {
	    if (word != string) {
		if (!(argv = realloc(argv, (nstrs + 2) * sizeof(char *))) ||
		    !(argv[nstrs++] = strndup(word, string - word)))
		    die("Out of memory\n");
	    }
	    if (!ch) {
		argv[nstrs] = 0;
		return argv;
	    }
	    word = string + 1;
	}
    }
}

static inline int
init_not(void)
{
    int s;
    if ((s = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
	die("Cannot create notifier socket\n");
    return s;
}

static int notsock, orgpid;
static struct sockaddr_un notsa;

static void
close_not(void)
{
    if (orgpid == getpid()) {
	unlink(notsa.sun_path);
	dbg("unlinked %s at exit\n", notsa.sun_path);
    }
}

static inline int
creat_not(void)
{
    notsock = init_not();
    notsa.sun_family = AF_UNIX;
    snprintf(notsa.sun_path, sizeof(notsa.sun_path), "notify-%d.sock", getpid());
    unlink(notsa.sun_path);
    dbg("unlinked %s at init\n", notsa.sun_path);
    if (bind(notsock, &notsa, sizeof(notsa)))
	die("Cannot bind notifier socket\n");
    orgpid = getpid();
    atexit(close_not);
    return notsock;
}

static void
serve(const char *pnam)
{
    char **argv;
    register int j, p, c;
    int i, sfds[2];
    struct {
	struct in_addr addr;
	ushort port;
    } adbuf;
    unsigned char buffer[MAX_BLOCK_LEN];

    Signal(SIGCHLD, sigchld, SA_RESTART);
    Signal(SIGALRM, sigalrm, 0);
    Signal(SIGPIPE, (void (*)(void))SIG_IGN, 0);
    add_fd(fd_in, tComm, 0, 0, POLLIN);
    add_fd(fd_out, tComm, 0, 0, 0);
    add_fd(creat_not(), tNot, 0, 0, POLLIN);
    if (!fork()) {
	int pnl = strlen(pnam);
	char *buf = malloc(pnl + 6);
	if (buf) {
	    memcpy(buf, pnam, pnl);
	    memcpy(buf + pnl, ".init", 6);
	    execl(buf, buf, 0);
	}
    }
    alarm(pongtime);
    lping = 0;
    now = time(0);
    for (;;) {
	if (lping + pingtime <= now) {
	    buffer[0] = pPing;
	    writer(buffer, 1);
	}
	i = poll(pollfds, npolls, (lping + pingtime - now) * 1000);
	now = time(0);
	if (!i)
	    continue;
	if (i < 0) {
	    if (errno == EINTR)
		continue;
	    else
		die("Poll failed: %s\n", strerror(errno));
	}
	for (i = 0; i < npolls; i++) {
	    if (pollfds[i].revents & POLLNVAL)
		die("Internal error: polling invalid fd\n");
	    else if (pollfds[i].revents & (POLLERR/* | POLLHUP*/)) {
		switch (fdparms[i].type) {
		    case tComm:
			die("Tunnel broken\n");
		    case tPreConn:
		    case tConn:
			p = i;
			c = fdparms[p].conn;
			dbg("error on connection %d\n", c);
		      reqclose:
			dbg("requesting close of connection %d\n", c);
			buffer[0] = pClose;
			buffer[1] = c;
			writer(buffer, 2);
			close_poll(p);
			conns[c] = cClosing;
			goto next;
		}
	    } else if (pollfds[i].revents & POLLIN) {
		switch (fdparms[i].type) {
		    case tComm:
			reader(buffer, 1);
			switch(buffer[0]) {
			    case pData:
				reader(buffer + 1, 3);
				j = buffer[2] | (buffer[3] << 8);
				reader(buffer + 4, j);
				c = buffer[1];
				if (is_open(c)) {
				    p = conns[c];
				    dbg("writing %d bytes to connection %d\n", j, fdparms[i].conn);
				    if (write(pollfds[p].fd, buffer + 4, j) != j)
					goto reqclose;
				}
				break;
			    case pOpenReq:
				reader(buffer + 1, 2);
				c = buffer[1];
				switch(buffer[2]) {
				    case cForward:
					reader(buffer + 3, 3);
					reader(buffer + 6, buffer[5]);
					p = buffer[3] | (buffer[4] << 8);
					dbg("forward request %s:%hd, connection %d\n", buffer + 6, p, c);
					if (socketpair(PF_UNIX, SOCK_STREAM, 0, sfds) < 0 || 
					    ((j = fork()) < 0 && (close(sfds[0]), close(sfds[1]), 1))) {
					    dbg("failed\n");
					    buffer[0] = pClose;
					    //buffer[1] = c;
					    writer(buffer, 2);
					    set_conn(c, cClosing);
					} else {
					    if (!j) {
						struct hostent *he;
						dbg("looking up '%s'\n", buffer + 6);
						if (!buffer[6]) {
						    dbg("empty host name\n");
						    adbuf.addr.s_addr = htonl(0x7f000001);
						    adbuf.port = htons(p);
						    send(sfds[1], &adbuf, sizeof(adbuf), 0);
						} else if ((he = gethostbyname(buffer + 6)) && 
							    he->h_length == 4)
						{
						    dbg("lookup ok\n");
						    adbuf.addr = *(struct in_addr *)he->h_addr;
						    adbuf.port = htons(p);
						    send(sfds[1], &adbuf, sizeof(adbuf), 0);
						}
						dbg("lookup done\n");
						exit(0);
					    }
					    close(sfds[1]);
					    set_conn(c, add_fd(sfds[0], tPreConn, buffer[2], c, POLLIN));
					    goto next;
					}
					break;
				    case cDaemon:
					reader(buffer + 3, 1);
					reader(buffer + 4, buffer[3]);
					dbg("remode daemon request \"%s\", connection %d\n", buffer + 4, c);
					if (socketpair(PF_UNIX, SOCK_STREAM, 0, sfds) < 0 || 
					    ((j = fork()) < 0 && (close(sfds[0]), close(sfds[1]), 1))) {
					    dbg("failed\n");
					    buffer[0] = pClose;
					    //buffer[1] = c;
					    writer(buffer, 2);
					    set_conn(c, cClosing);
					} else {
					    if (!j) {
						dup2(sfds[1], 0); dup2(0, 1); dup2(0, 2);
						close(sfds[0]); close(sfds[1]);
						argv = splitargs(buffer + 4);
						execv(argv[0], argv);
						die("Cannot execute %s\n", argv[0]);
					    }
					    close(sfds[1]);
					    set_conn(c, add_fd(sfds[0], tConn, buffer[2], c, POLLOUT));
					    goto next;
					}
					break;
				}
				break;
			    case pClose:	// req, ack
				reader(buffer + 1, 1);
				c = buffer[1];
				dbg("close request for connection %d\n", c);
				is_valid(c);
				if (conns[c] >= 0) {
				    writer(buffer, 2);
				    close_poll(conns[c]);
				}
				conns[c] = cUnused;
				goto next;
			    case pPing:
				break;
			}	// switch(buffer[0])
			break;
		    case tPreConn:
			c = fdparms[i].conn;
			dbg("connecting %d\n", c);
			if (recv(pollfds[i].fd, &adbuf, sizeof(adbuf), 0) != sizeof(adbuf) ||
			    (j = tcp_connect(adbuf.addr, adbuf.port)) < 0)
			{
			    dbg("connect %d failed\n", c);
			    buffer[0] = pClose;
			    buffer[1] = c;
			    writer(buffer, 2);
			    set_conn(c, cClosing);
			    close_poll(i);
			    goto next;
			}
			else
			{
			    close(pollfds[i].fd);
			    fdparms[i].type = tConn;
			    pollfds[i].fd = j;
			    pollfds[i].events = POLLOUT;
			}
			break;
		    case tConn:
			dbg("reading connection %d\n", fdparms[i].conn);
			p = i;
			c = fdparms[i].conn;
			j = read(pollfds[p].fd, buffer + 4, sizeof(buffer) - 4);
			dbg("%d bytes\n", j);
			if (!j)
			    goto reqclose;
			if (j > 0) {
			    if (is_open(c)) {
				buffer[0] = pData;
				buffer[1] = c;
				buffer[2] = j & 255;
				buffer[3] = j >> 8;
				writer(buffer, j + 4);
			    }
			}
			break;
		    case tNot:
			c = recv(pollfds[i].fd, buffer + 4, sizeof(buffer) - 4, 0);
			if (c > 0)
			{
			    buffer[0] = pNotify;
			    buffer[1] = strlen(buffer + 4) + 1;
			    buffer[2] = strlen(buffer + 4 + buffer[1]) + 1;
			    buffer[3] = strlen(buffer + 4 + buffer[1] + buffer[2]) + 1;
			    writer(buffer, c + 4);
			}
			break;
		}	// switch(type)
	    } else if (pollfds[i].revents & POLLOUT) {
		switch (fdparms[i].type) {
		    case tConn:
			dbg("connection %d established\n", fdparms[i].conn);
			buffer[0] = pOpenOk;
			buffer[1] = fdparms[i].conn;
			writer(buffer, 2);
			pollfds[i].events = POLLIN;
			break;
		}	// switch(type)
	    }	// if(writeable)
	}	// i++
      next: ;
    }	// for(;;)
}

static void
notify(const char *user, const char *sig, const char *mbox)
{
    int s, ul, sl, ml;
    unsigned i;
    glob_t globbuf;
    struct sockaddr_un sa;
    char buff[758];

    dbg("notifying '%s' about mail in '%s' with signal '%s'\n", user, mbox, sig);
    if ((ul = strlen(user) + 1) > 255 ||
	(sl = strlen(sig) + 1) > 255 ||
	(ml = strlen(mbox) + 1) > 255)
	die("Invalid notification\n");
    memcpy(buff, user, ul);
    memcpy(buff + ul, sig, sl);
    memcpy(buff + ul + sl, mbox, ml);
    s = init_not();
    sa.sun_family = AF_UNIX;
    if (glob("notify-*.sock", GLOB_ERR | GLOB_NOSORT, 0, &globbuf))
	dbg("Nobody to notify\n");
    else
	for (i = 0; i < globbuf.gl_pathc; i++) {
	    sprintf(sa.sun_path, 
		    "%.*s", (int)sizeof(sa.sun_path) - 1, globbuf.gl_pathv[i]);
	    if (sendto(s, buff, ul + sl + ml, MSG_DONTWAIT, &sa, sizeof(sa)) < 0)
		dbg("Send to notifier socket %s failed\n", sa.sun_path);
	}
}

int main(int argc, char **argv)
{
    const char *prog;
#ifdef DEBUG
    errfile = fopen("tunnelk.log", "a");
#endif
    prog = strrchr(argv[0], '/');
    if (prog)
	prog++;
    else
	prog = argv[0];
    if (!strcmp(prog, "tunnelk")) {
	if (argc == 1)
	    serve(argv[0]);
	else
	    fprintf(stderr, "Tunnel daemon login-server-side part. Don't invoke manually!\n");
    } else if (!strcmp(prog, "tunnelm")) {
	if (argc == 4)
	    notify(argv[1], argv[2], argv[3]);
	else
	    fprintf(stderr, "Usage: tunnelm <user> <signal> <mailbox>\n");
    } else
	fprintf(stderr, "Must be named tunnelm or tunnelk!\n");
    return 0;
}
#! /bin/bash
# -x
#exec </dev/null >>$(me).log 2>&1;echo;date

ISYNC=/usr/local/bin/isync

# pattern throttle maxthrottle sleep
mailclasses="
inbox 3 10 0
*-null 60 180 300
* 20 120 240
"
maxfetchtime=320


maildir=`sed -ne 's, ~/, '"$HOME"'/,;s/^Maildir *\([^ ]\+\) *$/\1/p' ~/.isyncrc`

getpid()
{
  # bash bug: $$ won't be updated in a forked process [()&]
  bash -c 'echo $PPID'
}

mysleep()
{
  sleep $1 & pid=$!
  trap 'kill $pid; exit' 15
  wait $pid
  trap - 15
}

getlock()
{
  if ! lockfile -s 0 -r 0 -l $maxfetchtime ${lf[$1]}.lock 2>/dev/null
  then
    if test $force; then
      pid=$(cat <${lf[$1]}.pid 2>/dev/null)
      test -z "$pid" -o "$pid" = 0 && return 1
      kill $pid
      rm ${lf[$1]}.pid
    else
      return 1
    fi
  fi
}

freelock()
{
  force=
  cont=$((${delay2[i]}+$now-$(date '+%s')))
  if ((cont > 0)); then
    getpid > ${lf[i]}.pid
    mysleep $cont ${lf[i]}.pid
    rm ${lf[$i]}.pid
  fi
  rm -f ${lf[i]}.lock
  test -f ${lf[i]}.need # must be last!
}

throttle()
{
  if test -z "$force" -a ${delay1[$1]} != 0; then
    getpid > ${lf[$1]}.pid
    expire=$(($now+${maxdelay[$1]}))
    while :; do
      test $(date -r ${lf[$1]}.need '+%s') -lt $(($now-${delay1[$1]})) && break
      test $now -lt $expire || break
      mysleep ${delay1[$1]}
      touch ${lf[$1]}.lock
      now=$(date '+%s')
    done
    rm ${lf[$1]}.pid
    mysleep 1
  fi
}

getonly=
force=
bgnd=
info=
unset boxes

while test "$1"; do
  case $1 in
    -f) force=1; shift;;
    -g) getonly=-f; shift;;
    -b) bgnd=1; shift;;
    -i) info=$2; shift 2;;
    -a) IFS=$'\n'; boxes=($($ISYNC -ql)); IFS=$' \t\n'; shift;;
    -*) echo "unknown option '$1'"; exit 1;;
    *) break;;
  esac
done
test -z "$1" && set -- "[EMAIL PROTECTED]"
test -z "$1" && echo "no mailbox specified" && exit 1

n=0
while test -n "$1"; do
  mbox[n]=$1
  lf[n]=~/tmp/getmail/$1
  mbe[n]=$maildir/$1/isyncerror
  eval `echo "${mailclasses:1:$((${#mailclasses}-2))}" |
  while read pat tdelay1 tmaxdelay tdelay2; do
    case $1 in
      $pat)
        echo "delay1[n]=$tdelay1 maxdelay[n]=$tmaxdelay delay2[n]=$tdelay2"
        break
        ;;
    esac
  done`
  shift
  echo "$info" >> ${lf[n]}.need
  test -n "$getonly" || touch ${lf[n]}.need.full
  getlock $n || continue
  ((n++))
done
test $n = 0 && exit

now=$(date '+%s')
if test -z "$bgnd"; then
  for i in "[EMAIL PROTECTED]"; do
    mv $i.need $i.doing
    test -z "$getonly" && rm -f $i.need.full
  done
  $ISYNC -C $getonly "[EMAIL PROTECTED]"
  for i in "[EMAIL PROTECTED]"; do
    . $i.doing
    rm $i.doing
  done
fi
for ((i=0; i<n; i++)); do
  (
    test -n "$bgnd" || { freelock && getlock $i; } || exit
    while :; do
      if test -f ${mbe[i]}; then
        freelock
        exit
      fi
      now=$(date '+%s')
      throttle $i
      mv ${lf[i]}.need ${lf[i]}.doing
      if test -f ${lf[i]}.need.full; then
        rm ${lf[i]}.need.full
        getonly=
      else
        getonly=-f
      fi
      if ! log=$($ISYNC -Cq $getonly ${mbox[i]} 2>&1); then
        if ! echo "$log" | egrep -q -i "(unexpected EOF|Connection 
refused|Connection reset by peer|Connection timed out|channel .* is locked\$)"; 
then
          echo "$log" > ${mbe[i]}
          echo "$log" | mail -s "getmail failed for '${mbox[i]}'" $(id -nu)
        fi
        cat ${lf[i]}.doing >> ${lf[i]}.need
        rm ${lf[i]}.doing
        test -z "$getonly" && touch ${lf[i]}.need.full
        freelock
        exit
      fi
      . ${lf[i]}.doing
      rm ${lf[i]}.doing
      freelock || exit
      getlock $i || exit
    done
  ) &
done
#! /bin/bash
# -x
#exec </dev/null >>$(me).log 2>&1;echo;date
exec <>/dev/null >&0 2>&1

export PATH=$HOME/bin:$PATH

if [ $2 = .cabot-mail ]; then
  exec fetchmail -f ~/.fetchmailrc.cabot -r $2
fi

if test ${2:0:5} = /var/; then
  mbox=inbox
else
  mbox=${2##*/}
fi

if test "x$1" = "x<delete>"; then
  exec getmail -b "$mbox"
else
  exec getmail -g -b -i "~/.mailinfo '$2 (remote)' '$1' &" "$mbox"
fi
-------------------------------------------------------------------------
Take Surveys. Earn Cash. Influence the Future of IT
Join SourceForge.net's Techsay panel and you'll get the chance to share your
opinions on IT & business topics through brief surveys -- and earn cash
http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV
_______________________________________________
isync-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/isync-devel

Reply via email to