/* * Milter client-side API, a library to talk to Milter filters * * (C) Copyright 2003 ADVA Research Center * (C) Copyright 2003 ArkanoiD * Based on code by Alexander Kosheverov */ #include "ci_mfapi.h" #include "ci_mfdef.h" #include "ci_milter.h" #include "ci_milter_sys.h" #include #include #include #include #include #include #include "milter_log.h" #include "base64.h" extern void* xmalloc(size_t); extern char* xstrdup(const char*); extern struct milter* milter_init(char*); extern int milter_quit_filter(struct milter*); extern time_t sotimeout(int); extern void addlist(char*,char***); extern void freelist(char***); static void del_hdrs (ENVELOPE* env) { HDR *h, *p; int i = 0; h = env->e_headers; while(h != NULL && i < 100) { p = h; free(p->h_value); p->h_value = NULL; free(p->h_field); p->h_field = NULL; h = h->h_link; free(p); p = NULL; ++i; } } static char* get_hdrcrlf_line (int fd) { int len = 0; char ch; char buf[MAX_HEADER_LEN]; /* Max header leght 998 + 2 ("\r\n") */ int ret; bzero(buf, sizeof (buf)); while ((ret = (read(fd, &ch, 1))) > 0) { buf[len] = ch; if(ch == LF) { /* Is it CRLF? */ if (len > 1 && buf[len - 1] == CR) break; buf[len++] = CR; buf[len] = LF; break; } if (len <= MAX_HEADER_LEN - 2) len++; } if (ret == -1) { ERROR("failed to read from file"); return NULL; } if (ret == 0) /* EOF */ return NULL; buf[++len] = '\0'; return xstrdup(buf); } HDR * milter_read_header (int fd) { HDR *h = NULL; int len = 0; int totl_len = 0; register char *p; char *p_str; char *buf; char wsp; if ((p_str = get_hdrcrlf_line(fd)) == NULL) { ERROR("read_header: failed"); return NULL; } while (isspace(*p_str) && ((*p_str != CR) && (*p_str != LF))); p = strchr(p_str, ':'); len = strlen(p_str); if (p == NULL) return NULL; if ((strlen(p) - 2) <= 0) { #ifdef DEBUG syslog(LOG_WARNING, "libci_milter: read_header: warning: no value"); #endif return NULL; } *p++ = '\0'; h = (HDR *)xmalloc(sizeof(HDR)); h->h_field = xstrdup(p_str); totl_len = strlen(p); /* Check for WSP (RFC2822 2.2.3) */ buf = xstrdup(p); (void) free(p_str); p_str = NULL; p = NULL; while(read(fd, &wsp, 1) > 0) { if (wsp == 0x20 || wsp == '\t') { if(lseek(fd, -1 , SEEK_CUR) == -1) { ERROR("failed to seek file"); return NULL; } if ((p_str = get_hdrcrlf_line(fd)) == NULL) { ERROR("read long value failed"); return NULL; } len = strlen(p_str); if(len + totl_len + 1< MAX_LONGHEADER) { buf = realloc(buf, len + totl_len + 1); (void) memmove(buf + totl_len, p_str, len); totl_len += len; } else { /* This may mess truncated headers, but those are already broken, so who cares? */ syslog(LOG_ERR,"securityalert: header field exceeds builtin limit, %u", MAX_LONGHEADER); continue; } (void) free(p_str); p_str = NULL; } else { /* Returning descriptor to old place */ if(lseek(fd, -1 , SEEK_CUR) == -1) { ERROR("failed to seek file"); return NULL; } break; } } buf[totl_len] = '\0'; p = buf; while (isspace(*p)) p++; if ((len = strlen(p)) > 2) { h->h_value = (char *)xmalloc(len); memmove(h->h_value, p, len - 1); h->h_value[len-2] = '\0'; } else { h->h_value = xstrdup(" "); } free(buf); buf = NULL; free(p_str); p_str = NULL; return h; } void add_header (ENVELOPE* e,HDR* h) { HDR *c_h = e->e_headers; if(c_h == NULL)/* list is empty? */ { e->e_headers = h; h->h_link = NULL; } else { while(c_h->h_link != NULL) c_h = c_h->h_link; c_h->h_link = h; h->h_link = NULL; } e->e_msgsize += strlen(h->h_field) + strlen(h->h_value); } HDR * make_header (const char* field,const char* value) { HDR* h; h = (HDR *)xmalloc(sizeof(HDR)); h->h_field = xstrdup(field); h->h_value = xstrdup(value); return h; } ENVELOPE* milter_envelope_new() { ENVELOPE *env; env = (ENVELOPE *) xmalloc(sizeof (ENVELOPE)); env->e_envfrom = NULL; env->e_rcpts = NULL; env->e_headers = NULL; env->e_bfn = NULL; env->e_state =(int) 0; env->e_bfd = (int) -1; env->e_msgsize = (ssize_t)0; return(env); } void free_envelope (ENVELOPE* env) { if(env == NULL) return; (void)del_hdrs(env); if(env->e_bfn != NULL) { unlink(env->e_bfn); (void)free(env->e_bfn); env->e_bfn = NULL; } (void)free(env); env = NULL; return; } void free_milter (struct milter* m) { if(m == NULL) return; free(m->mf_name); m->mf_name = NULL; m->mf_fvers =0; m->mf_fflags =0; m->mf_pflags =0; free(m->mf_conn); m->mf_conn = NULL; close(m->mf_sock); m->mf_sock = -1; m->mf_state ='\0'; m->mf_timeout =0; free(m); m = NULL; return; } int milter_rebuild_msg (ENVELOPE* e,char* fn) { char buf[MILTER_CHUNK_SIZE]; RCPT *r; HDR *h; int fd; ssize_t len; bzero(buf, sizeof(buf)); #ifdef DEBUG snprintf(buf, strlen(fn) + 5, "%.255s", fn); #else snprintf(buf, strlen(fn) + 1, "%.255s", fn); #endif if((fd = open(buf, O_RDWR|O_TRUNC|O_CREAT)) == -1) { ERROR("failed to reopen message file"); e->e_state |= MS_ER; e->e_state |= ER_IO; return MI_FAILURE; } (void) bzero(buf, sizeof(buf)); r = e->e_rcpts; if (r == NULL) return MI_FAILURE; for(h = e->e_headers; h != NULL; h = h->h_link) { if (!h->h_value) continue; if ((write(fd,h->h_field,strlen(h->h_field)) != (int) strlen(h->h_field)) || (write(fd,": ",2) != 2) || (write(fd,h->h_value,strlen(h->h_value)) != (int) strlen(h->h_value)) || (write(fd,"\r\n",2) != 2) ) { e->e_state |= MS_ER; e->e_state |= ER_IO; return MI_FAILURE; } (void) bzero (buf, sizeof(buf)); } if(write(fd, "\r\n", 2)!=2) { e->e_state |= MS_ER; e->e_state |= ER_IO; return MI_FAILURE; } (void) bzero(buf, sizeof(buf)); if(lseek(e->e_bfd, 0, SEEK_SET) == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to seek message body"); return MI_FAILURE; } while ((len = read(e->e_bfd, buf, sizeof(buf))) != 0 ) { if (len == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to read message body"); return MI_FAILURE; } if(write(fd, buf, len)!=len) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to write message file"); return MI_FAILURE; } (void)bzero(buf, sizeof(buf)); } /* Are where CR and LF at the end? */ if(lseek(e->e_bfd, -2, SEEK_CUR) == -1) { /* * Skip if body is empty */ if (errno == EINVAL) goto finish; e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("libci_milter: rebuild_msg: failed to seek file"); return MI_FAILURE; } if((len = read(e->e_bfd, buf, sizeof(buf))) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to read message body"); return MI_FAILURE; } else { if(buf[0] != 0x0d && buf[1] != 0x0a) if(write(fd, "\r\n", 2)!=2) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to write message file"); return MI_FAILURE; } } finish: close(fd); fd = -1; close(e->e_bfd); e->e_bfd = -1; return MI_SUCCESS; } int milter_inspect_mail(char *appname, char *rladdr, char *riaddr, char *mconn, char *f_name, char *from, char ***to) { struct milter *m; ENVELOPE *env = NULL; char *state = NULL; int old_to; int fd,bfd; char **rcp; int estate; RCPT *r = NULL; HDR *h; char bodyfname[] = "/tmp/tempbodyXXXXXXXXXXX"; ssize_t len; char buf[MAX_HEADER_LEN]; old_to = sotimeout(300); state = (char *)xmalloc(sizeof *state); *state = SMFIR_CONTINUE; if (!(m = milter_init(mconn))) { (void) sotimeout (old_to); return(MS_ER|ER_NE); } env = milter_envelope_new(); if ((fd = open(f_name, O_RDWR)) == -1) { ERROR("cannot open quarantine file"); free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|ER_IO); } env->e_envfrom = (char*) xstrdup(from); for (rcp = *to; *rcp; rcp++) { if(!env->e_rcpts) { env->e_rcpts = (RCPT *) xmalloc (sizeof (RCPT)); r = env->e_rcpts; } else { if (r) { r->r_link = (RCPT *) xmalloc (sizeof (RCPT)); r = r->r_link; } else { FATAL("internal error - envelope not empty"); } } r->r_link = NULL; r->r_value = (char*) xstrdup(*rcp); } while ((h = milter_read_header(fd)) != NULL) { (void)add_header(env, h); } /* Descriptor fd now must be pointed to begining of the body */ /* * XXXX */ if ((bfd = mkstemp(bodyfname)) == -1) { ERROR("cannot create temporary file"); free_envelope(env); free_milter(m); close(fd); (void) sotimeout (old_to); return (MS_ER|ER_IO); } while ((len = read(fd, buf, sizeof(buf))) != 0 ) { if ((len == -1) || (write(bfd, buf, len) != len)) { ERROR("input/output error"); free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|ER_IO); } bzero(buf, sizeof(buf)); } close(fd); if(lseek(bfd, 0, SEEK_SET) == -1) { ERROR("failed to seek temporary file"); free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|ER_IO); } env->e_bfd = bfd; env->e_bfn = xstrdup(bodyfname); if ((milter_dohelo(m,env,state, rladdr,riaddr,appname) == MI_FAILURE) || (milter_dofrom(m,env,state,rladdr) == MI_FAILURE) || (milter_dorcpt(m,env,state,rladdr) == MI_FAILURE) || (milter_data(m,env,state) == MI_FAILURE) || (milter_quit_filter(m) == MI_FAILURE) ) { estate = env->e_state; free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|estate); } if(milter_rebuild_msg (env, f_name) == MI_FAILURE) { ERROR("failed to build a message"); } freelist(to); for (r = env->e_rcpts; r != NULL; r = r->r_link) addlist(r->r_value,to); estate = env->e_state; free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (estate); } int milter_inspect_binary(char *appname, char *rladdr, char *riaddr, char *mconn, char *f_name, char *from, char ***to, char *f_name_orig, char *ctype) { struct milter *m; ENVELOPE *env = NULL; char *state = NULL; int old_to; int fd,bfd; char **rcp; int estate; RCPT *r = NULL; char bodyfname[] = "/tmp/tempbodyXXXXXXXXXXX"; ssize_t len; char buf[MAX_HEADER_LEN]; old_to = sotimeout(300); state = (char *)xmalloc(sizeof *state); *state = SMFIR_CONTINUE; if (!(m = milter_init(mconn))) { (void) sotimeout (old_to); return(MS_ER|ER_NE); } env = milter_envelope_new(); if ((fd = open(f_name, O_RDWR)) == -1) { ERROR("cannot open quarantine file"); free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|ER_IO); } env->e_envfrom = (char*) xstrdup(from); for (rcp = *to; *rcp; rcp++) { if(!env->e_rcpts) { env->e_rcpts = (RCPT *) xmalloc (sizeof (RCPT)); r = env->e_rcpts; } else { if (r) { r->r_link = (RCPT *) xmalloc (sizeof (RCPT)); r = r->r_link; } else { FATAL("internal error - envelope not empty"); } } r->r_link = NULL; r->r_value = (char*) xstrdup(*rcp); } if (ctype) (void)add_header(env, make_header("Content-Type",ctype)); else (void)add_header(env, make_header("Content-Type","application/binary")); (void)add_header(env, make_header("Content-transfer-encoding","base64")); if (f_name_orig) snprintf(buf,MAX_HEADER_LEN,"attachment; filename=\"%.128s\"", f_name_orig); else snprintf(buf,MAX_HEADER_LEN,"attachment"); (void)add_header(env, make_header("Content-Disposition",buf)); (void)add_header(env, make_header("Mime-Version","1.0")); if ((bfd = mkstemp(bodyfname)) == -1) { ERROR("cannot create temporary file"); free_envelope(env); free_milter(m); close(fd); (void) sotimeout (old_to); return (MS_ER|ER_IO); } while ((len = read(fd, buf, 60)) != 0 ) { int enclen = 0; char *encbuf; if ((len == -1) || !(enclen = base64_encode(buf, len, &encbuf)) || (write(bfd, encbuf, enclen) != enclen) || (write(bfd, "\r\n", 2) != 2)) { syslog(LOG_ERR,"abort at len=%d enclen=%d", (int) len, enclen); ERROR("input/output error"); free_envelope(env); free_milter(m); free(encbuf); (void) sotimeout (old_to); return (MS_ER|ER_IO); } free(encbuf); bzero(buf, sizeof(buf)); } close(fd); if(lseek(bfd, 0, SEEK_SET) == -1) { ERROR("failed to seek temporary file"); free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|ER_IO); } env->e_bfd = bfd; env->e_bfn = xstrdup(bodyfname); if ((milter_dohelo(m,env,state, rladdr,riaddr,appname) == MI_FAILURE) || (milter_dofrom(m,env,state,rladdr) == MI_FAILURE) || (milter_dorcpt(m,env,state,rladdr) == MI_FAILURE) || (milter_data(m,env,state) == MI_FAILURE) || (milter_quit_filter(m) == MI_FAILURE) ) { estate = env->e_state; free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (MS_ER|estate); } freelist(to); for (r = env->e_rcpts; r != NULL; r = r->r_link) addlist(r->r_value,to); estate = env->e_state; free_envelope(env); free_milter(m); (void) sotimeout (old_to); return (estate); }