/* Copyright (c) 2003, WebThing Ltd Author: Nick Kew Current state: pre-release; some parts work, but none of it is suitable for an operational server. Subject to much change This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* CAVEAT - this is incomplete, and not recommended for production servers! */ #define BUFSIZE 128 #include #include #include #include #include #include module AP_MODULE_DECLARE_DATA tee_module ; typedef int (*tee_callback)(ap_filter_t*) ; typedef const char tee_dest ; typedef struct tee_hdrs { const char* key ; const char* val ; struct tee_hdrs* next ; } tee_hdrs ; typedef enum { tee_false , tee_query_string , tee_path , tee_cookie , tee_header , tee_env , tee_true } tee_dispatch_t ; typedef struct { tee_dispatch_t cond ; const char* key ; const char* val ; } tee_cond ; typedef struct { tee_callback open_callback ; tee_callback close_callback ; tee_dest* destn ; tee_hdrs* hdrs ; tee_cond* cond ; } tee_cfg ; typedef struct { int fd ; void* private ; tee_cfg* cfg ; } tee_ctxt ; static const char* tee_name = "tee-filter-don't-insert" ; /* per-request determination of whether or not to filter */ static const char* getArg(apr_pool_t* pool, const char* args, const char* name) { const char* val ; const char* end ; char after ; const char* key ; if ( ! args || ! name ) return 0 ; key = strstr(args, name) ; if ( ! key ) return 0 ; if ( ( key != args ) && ( key[-1] != '&' ) ) return getArg(pool, key+1, name) ; after = key[strlen(name)] ; if ( after != '=' ) if ( after ) return getArg(pool, key+1, name) ; else return 0 ; val = key + strlen(name) + 1 ; end = strchr(val, '&') ; if ( end ) return apr_pstrndup(pool, val, (end-val) ) ; else return apr_pstrdup(pool, val) ; } static apr_status_t tee_decide(request_rec* r) { tee_cfg* cfg = (tee_cfg*) ap_get_module_config(r->per_dir_config, &tee_module) ; int tee = 0 ; const char* val = 0 ; if ( cfg->cond ) { switch ( cfg->cond->cond ) { case tee_true: tee = 1 ; break ; case tee_false: break ; case tee_query_string: if ( val = getArg(r->pool, r->args, cfg->cond->key) , val ) //{ if ( ! cfg->cond->val || !strlen(cfg->cond->val) || ! strcasecmp(val, cfg->cond->val ) ) tee = 1 ; //} break ; /* case tee_path: case tee_cookie: case tee_header: case tee_env: */ default: ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Not implemented") ; break ; } } if ( tee ) { if ( ! cfg->destn ) ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Tee filter requires Destination") ; else { tee_ctxt* ctxt = apr_pcalloc(r->pool, sizeof(tee_ctxt)) ; ctxt->cfg = cfg ; ap_add_output_filter(tee_name, ctxt, r, r->connection) ; ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "Tee filter inserted") ; } } return APR_SUCCESS ; } /* Default callbacks for open/close ops */ static int tee_fopen(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; //ctx->fd = open(ctx->cfg->destn, O_WRONLY|O_CREAT|O_APPEND) ; ctx->private = fopen(ctx->cfg->destn, "a") ; ctx->fd = fileno(ctx->private) ; return ctx->fd ; } static int tee_fclose(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; fclose(ctx->private) ; ctx->fd = 0 ; return 0 ; } /* Default callbacks for open/close ops */ static int tee_popen(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; //ctx->fd = open(ctx->cfg->destn, O_WRONLY|O_CREAT|O_APPEND) ; ctx->private = popen(ctx->cfg->destn, "w") ; ctx->fd = fileno(ctx->private) ; return ctx->fd ; } static int tee_pclose(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; pclose(ctx->private) ; ctx->fd = 0 ; return 0 ; } /* Per-dir config initialisation */ static void* tee_config(apr_pool_t* pool, char* x) { tee_cfg* ret = apr_palloc(pool, sizeof(tee_cfg)) ; ret->open_callback = tee_fopen ; ret->close_callback = tee_fclose ; ret->destn = NULL ; ret->hdrs = NULL ; ret->cond = NULL ; return ret ; } static void* tee_merge(apr_pool_t* pool, void* BASE, void* ADD) { tee_cfg* base = (tee_cfg*) BASE ; tee_cfg* add = (tee_cfg*) ADD ; tee_cfg* conf = apr_palloc(pool, sizeof(tee_cfg)) ; conf->open_callback = ( add->open_callback == tee_fopen ) ? base->open_callback : add->open_callback ; conf->close_callback = ( add->close_callback == tee_fclose ) ? base->close_callback : add->close_callback ; conf->destn = add->destn ? add->destn : base->destn ; conf->cond = add->cond ? add->cond : base->cond ; conf->hdrs = add->hdrs ? add->hdrs : base->hdrs ; if ( add->hdrs && base->hdrs ) { /* chain base onto new */ tee_hdrs* newhdr = add->hdrs ; while ( newhdr->next ) newhdr = newhdr->next ; newhdr->next = base->hdrs ; } return conf ; } /* Other forms of tee selectable by config */ static int tcp_open(const char* host, int port) { int sock_fd = socket(PF_INET, SOCK_STREAM, 0) ; struct sockaddr_in sockaddr ; sockaddr.sin_family = AF_INET; sockaddr.sin_port = htons(port); if ( isdigit ( host[0] ) ) { unsigned long n = inet_addr(host) ; if ( n == (unsigned long)-1) { return 0 ; } sockaddr.sin_addr.s_addr = n ; } else { struct hostent* hp = gethostbyname(host) ; if ( ! hp ) { //error("bad hostname") ; return 0 ; } memcpy( &sockaddr.sin_addr, hp->h_addr, hp->h_length) ; } if ( connect(sock_fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == -1) { close(sock_fd) ; sock_fd = 0 ; } return sock_fd ; } /* static int send_hdr(void* CTX, const char* key, const char* val) { tee_ctxt* ctx = CTX ; write(ctx->fd, key, strlen(key)) ; write(ctx->fd, ": ", 2) ; write(ctx->fd, val, strlen(val)) ; write(ctx->fd, "\r\n", 2) ; return 1 ; } */ static void send_hdrs(tee_ctxt* ctx) { tee_hdrs* hdr ; for ( hdr = ctx->cfg->hdrs ; hdr ; hdr = hdr->next ) { write(ctx->fd, hdr->key, strlen(hdr->key)) ; write(ctx->fd, ": ", 2) ; write(ctx->fd, hdr->val, strlen(hdr->val)) ; write(ctx->fd, "\r\n", 2) ; } write(ctx->fd, "\r\n", 2) ; } //static int read_response(ap_filter_t* f, int code) { //} static int tee_smtp_open(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; size_t bytes ; const char* recipient = ctx->cfg->destn ; char* server = strrchr(ctx->cfg->destn, '@') ; size_t recip_sz = server - recipient ; if ( ! server++ ) return ctx->fd = 0 ; if ( ! strcasecmp(server, "mx") ) { // look up mx for recipient } if ( ! strncmp(recipient, "remote_user", recip_sz) ) recipient = f->r->user ; /* Other recipient types here */ if ( ! recipient ) return ctx->fd = 0 ; else if ( recipient != ctx->cfg->destn ) recip_sz = strlen(recipient) ; ctx->private = apr_palloc(f->r->pool, BUFSIZE) ; ctx->fd = tcp_open(server, 25) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "HELO ", 5) ; write(ctx->fd, f->r->hostname, strlen(f->r->hostname)) ; write(ctx->fd, "\r\n ", 2) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "MAIL FROM: mod_tee@", 19) ; write(ctx->fd, f->r->hostname, strlen(f->r->hostname)) ; write(ctx->fd, "\r\n ", 2) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "RCPT TO: ", 9) ; write(ctx->fd, recipient, recip_sz) ; write(ctx->fd, "\r\n ", 2) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "DATA\r\n", 6) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "To: ", 4) ; write(ctx->fd, recipient, recip_sz) ; write(ctx->fd, "\r\n ", 2) ; write(ctx->fd, "From: mod_tee@", 14) ; write(ctx->fd, f->r->hostname, strlen(f->r->hostname)) ; write(ctx->fd, "\r\n ", 2) ; if ( f->r->content_type ) { write(ctx->fd, "MIME-Version: 1.0\r\n", 19) ; write(ctx->fd, "Content-Type: ", 14) ; write(ctx->fd, f->r->content_type, strlen(f->r->content_type)) ; if ( f->r->content_encoding ) { write(ctx->fd, ";charset=", 9) ; write(ctx->fd, f->r->content_encoding, strlen(f->r->content_encoding)) ; } write(ctx->fd, "\r\n ", 2) ; } send_hdrs(ctx) ; return ctx->fd ; } static int tee_smtp_close(ap_filter_t* f) { tee_ctxt* ctx = f->ctx ; size_t bytes ; write(ctx->fd, "\r\n.\r\n", 5) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; write(ctx->fd, "quit\r\n", 6) ; bytes = read(ctx->fd, ctx->private, BUFSIZE) ; close(ctx->fd) ; return ctx->fd = 0 ; } /* The main filter */ static int tee_filter(ap_filter_t* f, apr_bucket_brigade* bb) { apr_bucket* b ; tee_ctxt* ctx = (tee_ctxt*)f->ctx ; if ( ! ctx->fd ) if ( ! ctx->cfg->open_callback(f) ) ap_remove_output_filter(f) ; for ( b = APR_BRIGADE_FIRST(bb) ; b != APR_BRIGADE_SENTINEL(bb) ; b = APR_BUCKET_NEXT(b) ) { if ( ! ctx->fd ) break ; if ( APR_BUCKET_IS_EOS(b) ) { ctx->cfg->close_callback(f) ; //close(ctx->fd) ; } else if ( ! APR_BUCKET_IS_METADATA(b) ) { const char* buf = 0 ; apr_size_t bytes = 0 ; if ( apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ) == APR_SUCCESS ) write (ctx->fd, buf, bytes) ; } } return ap_pass_brigade(f->next, bb) ; } static const char* tee_type(cmd_parms* cmd, void* CFG, const char* type, const char* destn) { tee_cfg* cfg = CFG ; if ( !strcasecmp(type, "smtp") ) { cfg->open_callback = tee_smtp_open ; cfg->close_callback = tee_smtp_close ; } if ( !strcasecmp(type, "pipe") ) { cfg->open_callback = tee_popen ; cfg->close_callback = tee_pclose ; } if ( !strcasecmp(type, "file") ) { cfg->open_callback = tee_fopen ; cfg->close_callback = tee_fclose ; } if ( destn ) cfg->destn = apr_pstrdup(cmd->pool, destn) ; return NULL ; } static const char* tee_hdr(cmd_parms* cmd, void* CFG, const char* key, const char* val) { tee_cfg* cfg = CFG ; tee_hdrs* hdr = apr_palloc(cmd->pool, sizeof(tee_hdrs)) ; hdr->key = apr_pstrdup(cmd->pool, key) ; hdr->val = apr_pstrdup(cmd->pool, val) ; hdr->next = cfg->hdrs ; cfg->hdrs = hdr ; return NULL ; } /* typedef enum { tee_false , tee_query_string , tee_path , tee_cookie , tee_header , tee_env , tee_true } tee_dispatch_t ; */ static const char* tee_condition(cmd_parms* cmd, void* CFG, const char* what, const char* key, const char* match) { tee_cfg* cfg = CFG ; if ( cfg->cond ) ap_log_perror(APLOG_MARK, APLOG_NOTICE, 0, cmd->pool, "Multiple TeeConditions not supported; ignoring previous one.") ; cfg->cond = apr_palloc(cmd->pool, sizeof(tee_cond)) ; if ( key ) cfg->cond->key = apr_pstrdup(cmd->pool, key) ; else cfg->cond->val = NULL ; if ( match ) cfg->cond->val = apr_pstrdup(cmd->pool, match) ; else cfg->cond->val = NULL ; if ( !strcasecmp(what, "query") ) cfg->cond->cond = tee_query_string ; else if ( !strcasecmp(what, "true") ) cfg->cond->cond = tee_true ; else cfg->cond->cond = tee_false ; return NULL ; } static const command_rec tee_cmds[] = { AP_INIT_TAKE12("TeeType", tee_type, NULL, OR_ALL, "(FILE|PIPE|TCP|UDP|SMTP|HTTP) [destination]") , AP_INIT_TAKE2("TeeHeader", tee_hdr, NULL, OR_ALL, "RFC822-style header and value") , AP_INIT_TAKE123("TeeCondition", tee_condition, NULL, OR_ALL, "Condition to trigger tee") , { NULL } } ; static void tee_hooks(apr_pool_t* p) { /* Don't use AddOutputFilter/etc on this. Use TeeCondition instead, and the builtin tee_decide will insert it if required. */ ap_register_output_filter(tee_name, tee_filter, NULL, AP_FTYPE_CONTENT_SET) ; ap_hook_insert_filter(tee_decide, NULL, NULL, APR_HOOK_MIDDLE) ; } module AP_MODULE_DECLARE_DATA tee_module = { STANDARD20_MODULE_STUFF, tee_config, tee_merge, NULL, NULL, tee_cmds, tee_hooks } ;