/* * 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 #include #include #include "milter_log.h" extern int proxy_timeout; #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif #ifndef MAX #define MAX(a,b) ((a) > (b) ? (a) : (b)) #endif #define MILTER_CHECK_DONE_MSG() \ if (*state == SMFIR_DISCARD || \ *state == SMFIR_ACCEPT || \ *state == SMFIR_TEMPFAIL) \ { \ /* Abort the filters to let them know we are done with msg */ \ milter_abort_filter(m); \ } #define MILTER_CHECK_STATE(action) \ if ((m->mf_state == SMFS_DONE) || (m->mf_state == SMFS_ERROR) || \ (m->mf_state == SMFS_CLOSED)) \ action; #define EOM_TIMEOUT 600 extern void* xmalloc(size_t request); extern char* xstrdup(const char*); extern int soread(int,char*,int); extern int sowrite(int,char*,int); extern int conn_server(char*,int,int,char*); static time_t now(void) { auto time_t t; (void) time(&t); return t; } /* * Low-level packet operation functions */ /* * milter_formdata() -- compose milter data packet for milter_write() * * parameters: packet -- pointer to pointer to packet. packet * allocation is created dynamically, thus * first portion of data is to be placed * setting this *(char **) to NULL * * pktsize -- pointer to current packet size (it * changes authomagically as well * * data -- data to be appended to packet * * datasize -- size of the above * * returns: nothing, but memory allocation failure terminates * the program. */ static void milter_formdata(char** packet,unsigned int* pktsize,const char *data,size_t datasize) { if ((*pktsize+datasize) >= (MILTER_CHUNK_SIZE-1)) { FATAL("packet size too big"); } if (!(*packet = realloc(*packet,*pktsize+datasize+1))) { FATAL("realloc() failed"); } memcpy(*packet+*pktsize,data,datasize); *pktsize += datasize; return; } /* * milter_formstring() -- append a string to packet * * parameters: packet and pktsize -- identical to milter_formdata * string -- string to be appended */ static void milter_formstring(char** packet,unsigned int* pktsize,const char* string) { milter_formdata(packet,pktsize,string,strlen(string)+1); return; } /* * milter_write() -- write a milter command packet to network * * parameters: m -- milter to write (state should permit). * cmd -- command to send. * buf -- data packet (composed by milter_form*) * len -- packet size. * * returns: nothing, milter state changed to SMFS_ERROR * if error condition occurs. */ static void milter_write(struct milter* m,char cmd,char* buf,ssize_t len) { ssize_t sl; uint32_t nl; char data[MILTER_LEN_BYTES + 1]; if (len < 0 || len > MILTER_CHUNK_SIZE) { m->mf_state = SMFS_ERROR; return; } MILTER_CHECK_STATE(return); nl = htonl((len + 1)); /* add 1 for the cmd char */ (void) memcpy(data, (char *) &nl, sizeof(uint32_t)); data[sizeof(uint32_t)] = cmd; sl = sizeof(uint32_t) + 1; if(sowrite(m->mf_sock, (void *) data, sl) != sl) { ERROR("sowrite failed"); m->mf_state = SMFS_ERROR; return; } if (len <= 0 || buf == NULL) return; if(sowrite(m->mf_sock, (void *) buf, len) != len){ #ifdef DEBUG ERROR("sowrite failed"); #endif m->mf_state = SMFS_ERROR; return; } return; } /* * milter_sysread() -- read fixed size data packet from network * * parameters: fd -- file descriptor to read * buf -- data buffer * buf_sz -- data buffer size * * returns: MI_FAILURE on error, number of bytes read otherwise */ static int milter_sysread(int fd,char* buf,int buf_sz) { ssize_t cur_len, path_len; if(buf_sz == 0) return MI_SUCCESS; path_len = 0; cur_len = 0; while (1) { if((cur_len = soread(fd, buf + path_len, buf_sz - path_len)) <= 0) { #ifdef DEBUG ERROR("soread failed"); #endif return MI_FAILURE; } path_len += cur_len; if(path_len < buf_sz) continue; break; } return path_len; } /* * milter_read() -- read milter response packet from network * * parameters: m -- milter to read (should be in readable state) * *cmd -- pointer to response command read * *rlen -- pointer to returned packet size * * returns: dynamically allocated buffer with packet read */ static char * milter_read(struct milter* m, char* cmd, ssize_t* rlen) { ssize_t expln; ssize_t chk; uint32_t i; char *buf; char data[MILTER_LEN_BYTES + 1]; bzero(data, sizeof data); *rlen = 0; *cmd = '\0'; MILTER_CHECK_STATE(return NULL); if ((expln = milter_sysread(m->mf_sock, data, sizeof data)) == MI_FAILURE) { m->mf_state = SMFS_ERROR; return NULL; } if(expln != MILTER_LEN_BYTES + 1) { #ifdef DEBUG syslog(LOG_ERR, "libci_milter: milter_read: truncated packet header, %d bytes",expln); #endif m->mf_state = SMFS_ERROR; return NULL; } *cmd = data[MILTER_LEN_BYTES]; data[MILTER_LEN_BYTES] = '\0'; (void) memcpy(&i, data, MILTER_LEN_BYTES); expln = ntohl(i) - 1; if (expln == 0) return NULL; if(expln > MILTER_CHUNK_SIZE) { syslog(LOG_ERR,"milter transaction failed expln=%d cmd=%.255s", (int) expln, cmd); m->mf_state = SMFS_ERROR; return NULL; } buf = (char *) xmalloc(expln); if ((chk = milter_sysread(m->mf_sock, buf, expln)) == MI_FAILURE) { free(buf); buf = NULL; m->mf_state = SMFS_ERROR; return NULL; } if (chk != expln) { ERROR("truncated packet on milter protocol"); free(buf); buf = NULL; m->mf_state = SMFS_ERROR; return NULL; } *rlen = expln; return buf; } /* * milter_open() -- connect to remote milter filter * * parameters: m -- milter to connect to, SMFS_READY. * * returns: connected socket if sucessful, * m state changes to SMFS_OPEN. * MI_FAILURE on error, state changes to SMFS_ERROR. */ static int milter_open(struct milter* m) { int socketfd = -1; struct sockaddr *filter_addr = NULL; int filter_addrlen = 0; char *p, *p1; m->mf_sock = MI_FAILURE; p = xstrdup(m->mf_conn); if ((p1 = index(p, ':'))) { *(p1++) = '\0'; if (strcasecmp(p, "unix") == 0 || strcasecmp(p, "local") == 0) { filter_addr = (struct sockaddr *) xmalloc(sizeof(struct sockaddr_un)); filter_addrlen = sizeof (struct sockaddr_un); bzero(filter_addr,filter_addrlen); filter_addr->sa_family = AF_UNIX; if (strlen(p1) >= sizeof (((struct sockaddr_un*) 0)->sun_path)) { ERROR("unix socket path too long"); free(filter_addr); filter_addr = NULL; free(p); m->mf_state = SMFS_ERROR; return MI_FAILURE; } strncpy(((struct sockaddr_un*)filter_addr)->sun_path,p1, sizeof (((struct sockaddr_un*)0)->sun_path)); #ifdef DEBUG syslog(LOG_INFO, "DEBUG: libci_milter: milter_open: socket is local:%s", ((struct sockaddr_un*)filter_addr)->sun_path); #endif } else if (strcasecmp(p, "inet") == 0) { char *host; if (!(host = index(p1,'@'))) { ERROR("syntax error in inet socket description"); free(p); m->mf_state = SMFS_ERROR; return MI_FAILURE; } *(host++) = '\0'; if ((m->mf_sock =conn_server(host,atoi(p1),0,NULL))<0) { syslog(LOG_ERR, "milter_open: failed to connect socket: %m"); filter_addr = NULL; m->mf_state = SMFS_ERROR; return MI_FAILURE; } m->mf_state = SMFS_OPEN; return(m->mf_sock); } else { ERROR("unknown protocol"); filter_addr = NULL; free(p); m->mf_state = SMFS_ERROR; return MI_FAILURE; } } free(p); socketfd = socket(filter_addr->sa_family, SOCK_STREAM, 0); if(socketfd < 0) { ERROR("failed to create socket"); free(filter_addr); filter_addr = NULL; m->mf_state = SMFS_ERROR; return MI_FAILURE; } if(connect(socketfd, filter_addr, filter_addrlen)) { syslog(LOG_ERR, "milter_open: failed to connect socket: %m"); free(filter_addr); filter_addr = NULL; m->mf_state = SMFS_ERROR; return MI_FAILURE; } m->mf_state = SMFS_OPEN; m->mf_sock = socketfd; #ifdef DEBUG syslog(LOG_INFO, "DEBUG: libci_milter: milter_open: connected"); #endif free(filter_addr); return(m->mf_sock); } /* * milter_new() -- allocate new milter. * * parameters: line -- filter name (client-defined). * * returns: pointer to milter structure in SMFS_READY state */ static struct milter* milter_new(char* line) { struct milter *bm; bm = (struct milter *) xmalloc(sizeof *bm); bzero(bm, sizeof *bm); if(line) bm->mf_name = xstrdup(line); else bm->mf_name = xstrdup("milter"); bm->mf_state = SMFS_READY; bm->mf_sock = -1; bm->mf_conn = NULL; bm->mf_timeout = (time_t) EOM_TIMEOUT; return bm; } /* * milter_negotiate() -- get version and flags from filter * * parameters: m -- milter filter structure (SMFS_OPEN state). * * returns: 0 on success (MI_SUCCESS), -1 otherwise (MI_FAILURE) */ static int milter_negotiate(struct milter* m) { uint32_t fvers; uint32_t fflags; uint32_t pflags; char *response = NULL; char data[MILTER_OPTLEN]; char m_rcmd; ssize_t rlen; bzero(data, sizeof data); if (m->mf_sock < 0 || m->mf_state != SMFS_OPEN) { return MI_FAILURE; } fvers = htonl(SMFI_VERSION); fflags = htonl(SMFI_CURR_ACTS); pflags = htonl(SMFI_CURR_PROT); (void) memcpy(data, (char *) &fvers, MILTER_LEN_BYTES); (void) memcpy(data + MILTER_LEN_BYTES, (char *) &fflags, MILTER_LEN_BYTES); (void) memcpy(data + (MILTER_LEN_BYTES * 2), (char *) &pflags, MILTER_LEN_BYTES); (void) milter_write(m, SMFIC_OPTNEG, data, sizeof data); if (m->mf_state == SMFS_ERROR) return MI_FAILURE; response = milter_read(m, &m_rcmd, &rlen); if (m->mf_state == SMFS_ERROR || m_rcmd != SMFIC_OPTNEG) { if(m->mf_sock >= 0) { syslog(LOG_ERR, "libci_milter: milter_negotiate: (%.128s): to error state", m->mf_name); close(m->mf_sock); m->mf_sock = -1; } m->mf_state = SMFS_ERROR; return MI_FAILURE; } if (response == NULL || rlen < MILTER_LEN_BYTES) if(m->mf_sock >= 0) { ERROR("bad response for negotiate packet"); close(m->mf_sock); m->mf_sock = -1; m->mf_state = SMFS_ERROR; return MI_FAILURE; } (void) memcpy((char *) &fvers, response, MILTER_LEN_BYTES); if (rlen != MILTER_OPTLEN) { ERROR("bad response for negotiate packet"); close(m->mf_sock); m->mf_sock = -1; m->mf_state = SMFS_ERROR; return MI_FAILURE; } (void) memcpy((char *) &fflags, response + MILTER_LEN_BYTES, MILTER_LEN_BYTES); (void) memcpy((char *) &pflags, response + (MILTER_LEN_BYTES * 2), MILTER_LEN_BYTES); free(response); response = NULL; m->mf_fvers = ntohl(fvers); m->mf_fflags = ntohl(fflags); m->mf_pflags = ntohl(pflags); /* check for version compatibility */ if (m->mf_fvers == 1 || m->mf_fvers > SMFI_VERSION) { syslog(LOG_ERR, "libci_milter: milter_negotiate: incompatible protocol version %lx",m->mf_fvers); close(m->mf_sock); m->mf_sock = -1; m->mf_state = SMFS_ERROR; return MI_FAILURE; } /* check for filter feature mismatch */ if ((m->mf_fflags & SMFI_CURR_ACTS) != m->mf_fflags) { ERROR("feature mismatch"); close(m->mf_sock); m->mf_sock = -1; m->mf_state = SMFS_ERROR; return MI_FAILURE; } #ifdef DEBUG syslog(LOG_NOTICE,"libci_milter: milter_negotiate: negotiated"); #endif return MI_SUCCESS; } /* * milter_send_command() -- send a command and parse the response for * a filter * * parameters: m -- current milter filter * command -- command to send. * data -- data packet (see above). * sz -- packet size. * state -- return state word. * * returns: response string (may be NULL) */ static void milter_send_command (struct milter* m,char command,void* data,ssize_t sz,char* state) { char m_rcmd; ssize_t rlen; unsigned long skipflag; char *response; MILTER_CHECK_STATE(return); /* find skip flag and default failure */ switch (command) { case SMFIC_CONNECT: skipflag = SMFIP_NOCONNECT; break; case SMFIC_HELO: skipflag = SMFIP_NOHELO; break; case SMFIC_MAIL: skipflag = SMFIP_NOMAIL; break; case SMFIC_RCPT: skipflag = SMFIP_NORCPT; break; case SMFIC_HEADER: skipflag = SMFIP_NOHDRS; break; case SMFIC_BODY: skipflag = SMFIP_NOBODY; break; case SMFIC_EOH: skipflag = SMFIP_NOEOH; break; case SMFIC_BODYEOB: case SMFIC_OPTNEG: case SMFIC_MACRO: case SMFIC_ABORT: case SMFIC_QUIT: /* NOTE: not handled by milter_send_command() */ /* FALLTHROUGH */ default: skipflag = 0; break; } /* check if filter wants this command */ if (skipflag != 0 && (skipflag & m->mf_pflags)) return; /* send the command to the filter */ (void) milter_write(m, command, data, sz); if (m->mf_state == SMFS_ERROR) return; /* get the response from the filter */ response = milter_read(m, &m_rcmd, &rlen); if (m->mf_state == SMFS_ERROR) return; switch (m_rcmd) { case SMFIR_REPLYCODE: case SMFIR_REJECT: case SMFIR_DISCARD: case SMFIR_QUARANTINE: case SMFIR_TEMPFAIL: *state = m_rcmd; break; case SMFIR_ACCEPT: /* this filter is done with message/connection */ /* we do not have CLOSABLE state because we deal with one */ /* message per session */ m->mf_state = SMFS_DONE; break; case SMFIR_CONTINUE: /* if MAIL command is ok, filter is in message state */ if (command == SMFIC_MAIL) m->mf_state = SMFS_INMSG; break; default: /* Invalid response to command */ syslog(LOG_ERR, "libci_milter: milter_send_command: returned bogus response (%d)", m_rcmd); m->mf_state = SMFS_ERROR; break; } free(response); return; } /* * milter_abort_filter() -- abort SMFS_INMSG state to SMFS_DONE * (we don't need this filter anymore to * handle this message) * * parameters: m -- milter in SMFS_INMSG state * * returns: none. m changes state to SMFS_DONE on success * (or SMFS_ERROR on error) */ static void milter_abort_filter (struct milter* m) { #ifdef DEBUG syslog(LOG_INFO,"libci_milter: milter_abort_filter: abort filter %s", m->mf_name); #endif if (m->mf_sock < 0 || m->mf_state != SMFS_INMSG) { return; } (void) milter_write(m, SMFIC_ABORT, (char *) NULL, 0); if (m->mf_state != SMFS_ERROR) m->mf_state = SMFS_DONE; } /* * milter_init() -- inititalize, open and negotiate milter * * parameters: filter -- connection string * * returns: milter negotiated and in SMFS_OPEN state or NULL on * failure. */ struct milter* milter_init(char *filter) { struct milter* m; m = milter_new(NULL); m -> mf_conn = xstrdup(filter); if (milter_open(m) == MI_FAILURE) return(NULL); if (m->mf_sock < 0) { FATAL("internal error"); } if (milter_negotiate(m) == MI_FAILURE) return(NULL); return(m); } /* * milter_dohelo() -- pass connect info and HELO command to milter * * parameters: m -- milter to write (should be in SMFS_OPEN state) * e -- current envelope * state -- result code from milter * rladdr -- peer fqdn to pass to filter * riaddr -- peer ip in dotted notation to pass to filter * appname -- application name * * returns: MI_SUCCESS or MI_FAILURE * milter and envelope states are changed according * to situation. */ int milter_dohelo(struct milter* m,ENVELOPE* e,char* state, char* rladdr,char* riaddr,char* appname) { char ourhost[MAXHOSTNAMELEN]; char remoteid[512]; char **packet; unsigned int pktlen = 0; uint16_t port; char x; if ((SMFIP_NOCONNECT & m->mf_pflags) || (m->mf_state == SMFS_DONE) || (e->e_state & (MS_RJ | MS_QU))) return(MI_SUCCESS); MILTER_CHECK_STATE(return MI_FAILURE); #ifdef DEBUG syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dohelo(): start"); #endif /* * Create and send CONNECT macro */ packet = xmalloc(sizeof(char*)); *packet = NULL; milter_formstring(packet,&pktlen,"Cj"); gethostname(ourhost,MAXHOSTNAMELEN); milter_formstring(packet,&pktlen,ourhost); milter_formstring(packet,&pktlen,"_"); snprintf(remoteid,sizeof(remoteid)-1,"%.128s [%.64s]",rladdr,riaddr); milter_formstring(packet,&pktlen,remoteid); milter_formstring(packet,&pktlen,"{daemon_name}"); milter_formstring(packet,&pktlen,appname); milter_formstring(packet,&pktlen,"{if_name}"); milter_formstring(packet,&pktlen,ourhost); milter_write(m,SMFIC_MACRO,*packet,pktlen); if (m->mf_state == SMFS_ERROR) { e->e_state |= MS_ER; e->e_state |= ER_NE; return(MI_FAILURE); } /* * Create and send CONNECT command */ free(*packet); *packet = NULL; pktlen = 0; milter_formstring(packet,&pktlen,rladdr); x = SMFIA_UNKNOWN; milter_formdata(packet,&pktlen,&x,1); port = 0; milter_formdata(packet,&pktlen,(char*) &port,sizeof(uint16_t)); milter_send_command(m,SMFIC_CONNECT,*packet,pktlen,state); /* * HELO macros and command may go here */ if (m->mf_state == SMFS_ERROR) { e->e_state |= MS_ER; e->e_state |= ER_NE; return(MI_FAILURE); } switch (*state) { case SMFIR_CONTINUE: case SMFIR_ACCEPT: return(MI_SUCCESS); case SMFIR_REJECT: case SMFIR_QUARANTINE: case SMFIR_REPLYCODE: case SMFIR_DISCARD: e->e_state|=(*state==SMFIR_QUARANTINE)?MS_QU:MS_RJ; return(MI_SUCCESS); case SMFIR_TEMPFAIL: return(MI_FAILURE); default: syslog(LOG_ERR,"libci_milter: milter_dohelo: remote requested operation out of state or unsupported here, SMFIC_CONNECT:%c",*state); e->e_state |= (MS_ER|ER_PO); return(MI_FAILURE); } } /* * milter_dofrom() -- pass FROM command to milter * * parameters: m -- milter to write (should be in SMFS_OPEN state) * e -- current envelope with from address * state -- result code from milter * riaddr -- peer ip in dotted notation to pass to filter * * returns: MI_SUCCESS or MI_FAILURE * milter and envelope states are changed according * to situation. */ int milter_dofrom(struct milter* m,ENVELOPE* e,char* state,char* rladdr) { char **packet; unsigned int pktlen = 0; char *queueid; if ((SMFIP_NOMAIL & m->mf_pflags) || (m->mf_state == SMFS_DONE) || (e->e_state & (MS_RJ | MS_QU))) return(MI_SUCCESS); MILTER_CHECK_STATE(return MI_FAILURE); /* * Create and send MAIL macro */ #ifdef DEBUG syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dofrom(): start"); #endif packet = xmalloc(sizeof(char*)); if (!(queueid = basename(e->e_bfn))) { e->e_state |= MS_ER; e->e_state |= ER_ME; return(MI_FAILURE); } *packet = NULL; milter_formstring(packet,&pktlen,"Mi"); milter_formstring(packet,&pktlen,queueid); milter_formstring(packet,&pktlen,"{mail_mailer}"); milter_formstring(packet,&pktlen,"local"); milter_formstring(packet,&pktlen,"{mail_addr}"); milter_formstring(packet,&pktlen,e->e_envfrom); milter_formstring(packet,&pktlen,"{mail_host}"); milter_formstring(packet,&pktlen,rladdr); milter_write(m,SMFIC_MACRO,*packet,pktlen); if (m->mf_state == SMFS_ERROR) { e->e_state |= MS_ER; e->e_state |= ER_NE; return(MI_FAILURE); } /* * Create and send MAIL command */ free(*packet); *packet = NULL; pktlen = 0; milter_formstring(packet,&pktlen,e->e_envfrom); milter_send_command(m,SMFIC_MAIL,*packet,pktlen,state); MILTER_CHECK_DONE_MSG(); switch (*state) { case SMFIR_REJECT: case SMFIR_QUARANTINE: case SMFIR_REPLYCODE: case SMFIR_DISCARD: e->e_state|=(*state==SMFIR_QUARANTINE)?MS_QU:MS_RJ; case SMFIR_CONTINUE: case SMFIR_ACCEPT: return(MI_SUCCESS); case SMFIR_TEMPFAIL: return(MI_FAILURE); default: syslog(LOG_ERR,"libci_milter: milter_dofrom: remote requested operation out of state or unsupported here, SMFIC_MAIL:%c",*state); e->e_state |= (MS_ER|ER_PO); return(MI_FAILURE); } } /* * milter_dorcpt() -- pass RCPT commands to milter * * parameters: m -- milter to write (should be in SMFS_OPEN state) * e -- current envelope with receipient list * state -- result code from milter * riaddr -- peer ip in dotted notation to pass to filter * * returns: MI_SUCCESS or MI_FAILURE * milter and envelope states are changed according * to situation. * receipient list is updated to exclude rejects. */ int milter_dorcpt(struct milter* m,ENVELOPE* e,char* state,char* rladdr) { char **packet = NULL; unsigned int pktlen = 0; RCPT *r, *rp; if ((SMFIP_NORCPT & m->mf_pflags) || (m->mf_state == SMFS_DONE) || (e->e_state & (MS_RJ | MS_QU))) return(MI_SUCCESS); MILTER_CHECK_STATE(return MI_FAILURE); rp = NULL; r = e->e_rcpts; while (r) { /* * Create and send RCPT macro */ #ifdef DEBUG syslog(LOG_NOTICE,"DEBUG: milter_envelope.c: milter_dorcpt(): rcpt %s",r->r_value); #endif packet = xmalloc(sizeof(char*)); *packet = NULL; milter_formstring(packet,&pktlen,"R{rcpt_mailer}"); milter_formstring(packet,&pktlen,"local"); milter_formstring(packet,&pktlen,"{rcpt_addr}"); milter_formstring(packet,&pktlen,r->r_value); milter_formstring(packet,&pktlen,"{rcpt_host}"); milter_formstring(packet,&pktlen,rladdr); milter_write(m,SMFIC_MACRO,*packet,pktlen); if (m->mf_state == SMFS_ERROR) { e->e_state |= MS_ER; e->e_state |= ER_NE; return(MI_FAILURE); } /* * Create and send RCPT command */ free(*packet); *packet = NULL; pktlen = 0; milter_formstring(packet,&pktlen,r->r_value); milter_send_command(m,SMFIC_RCPT,*packet,pktlen, state); MILTER_CHECK_DONE_MSG(); switch (*state) { case SMFIR_REJECT: case SMFIR_QUARANTINE: e->e_state|=(*state==SMFIR_REJECT)?MS_RJ:MS_QU; case SMFIR_DELRCPT: case SMFIR_REPLYCODE: if (rp) { rp->r_link = r->r_link; free(r->r_value); free(r); r = rp->r_link; e->e_state |= OK_HC; } else { e->e_rcpts = r->r_link; free(r->r_value); free(r); if (e->e_rcpts) r = e->e_rcpts->r_link; e->e_state |= OK_HC; } break; case SMFIR_CONTINUE: rp = r; r = r->r_link; break; case SMFIR_ACCEPT: free(*packet); return(MI_SUCCESS); default: syslog(LOG_ERR,"libci_milter: milter_dorcpt: remote requested operation out of state or unsupported here, SMFIC_RCPT:%c",*state); e->e_state |= (MS_ER|ER_PO); return(MI_FAILURE); } } free(*packet); if (!(e->e_rcpts)) e->e_state |= MS_RJ; return(MI_SUCCESS); } /* * milter_headers() -- pass message headers to milter * * parameters: m -- milter to write (should be in SMFS_OPEN state) * e -- current envelope with headers * state -- result code from milter * * returns: MI_SUCCESS or MI_FAILURE * milter and envelope states are changed according * to situation. * headers are updated un filter's behalf. */ static void milter_headers (struct milter* m,ENVELOPE* e,char* state) { HDR *h; h = e->e_headers; for (; h != NULL; h = h->h_link) { char *buf; ssize_t s; /* don't send over deleted headers */ if (h->h_value == NULL) { continue; } s = strlen(h->h_field) + 1 + strlen(h->h_value) + 1; if (s < 0) continue; buf = (char *) xmalloc(s); (void) snprintf(buf, s, "%s%c%s", h->h_field, '\0', h->h_value); /* send it over */ milter_send_command(m, SMFIC_HEADER, buf, s, state); free(buf); buf = NULL; if (m->mf_state == SMFS_ERROR || m->mf_state == SMFS_DONE || *state != SMFIR_CONTINUE) break; } } /* * milter_body() -- pass message body to milter * this command just passes body to filter, * replacement actions are taken elseplace * * parameters: m -- milter to write (should be in SMFS_OPEN state) * e -- current envelope with e->bfd pointing to * message body start * state -- result code from milter * * returns: MI_SUCCESS or MI_FAILURE * milter and envelope states are changed according * to situation. */ static void milter_body (struct milter* m,ENVELOPE* e,char* state) { int len; char bufchar = '\0'; char prevchar = '\0'; char c; char *bp; char buf[MILTER_CHUNK_SIZE]; bp = buf; if(*state != SMFIR_CONTINUE) *state = SMFIR_CONTINUE; if(lseek(e->e_bfd, 0, SEEK_SET) == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; ERROR("failed to seek file"); } while ( (len = read(e->e_bfd, &c, 1)) != 0) { if(len == -1) { ERROR("failed to read data"); return; } /* Change LF to CRLF */ if (c == '\n') { /* Not a CRLF already? */ if (prevchar != '\r') { /* Room for CR now? */ if (bp + 2 > &buf[sizeof buf]) { /* No room, buffer LF */ bufchar = c; /* and send CR now */ c = '\r'; } else { /* Room to do it now */ *bp++ = '\r'; prevchar = '\r'; } } } *bp++ = (char) c; prevchar = c; if (bp >= &buf[sizeof buf]) { /* send chunk */ milter_send_command(m, SMFIC_BODY, buf, bp - buf, state); bp = buf; if (bufchar != '\0') { *bp++ = bufchar; bufchar = '\0'; prevchar = bufchar; } } if (m->mf_state == SMFS_ERROR || m->mf_state == SMFS_DONE || *state != SMFIR_CONTINUE) break; } /* check for read errors */ /* ????????????????????????????????????????????????????? */ /* ????????????????????????????????????????????????????? */ /* ????????????????????????????????????????????????????? */ /* send last body chunk */ if (bp > buf && m->mf_state != SMFS_ERROR && m->mf_state != SMFS_DONE && *state == SMFIR_CONTINUE) { /* send chunk */ milter_send_command(m, SMFIC_BODY, buf, bp - buf, state); bp = buf; } } /* * Actions requested by filter from xxfi_eom * called by milter_data. argument is raw response received from milter */ /* * milter_fixlinefeed() -- Supplementary function to fix line termination * in add/change header functions; according to milter specification, * filters do terminate lines in multiline headers with lf, but we deal with * cr/lf termination at this point. Warning: the function returns pointer to * a static buffer. * * Parameters: * rawheader -- raw header lines received from filter * * Returns: * pointer to a static buffer containing lines with proper termination */ static char* milter_fixlinefeed (char* rawheader) { static char fixedheader[MAX_LONGHEADER]; char *p,*p1; p = rawheader; p1 = fixedheader; while (*p && (p1 - fixedheader < (int) sizeof(fixedheader) - 3)) { *p1 = *p++; if (*p1 == '\n') { *(p1++) = '\r'; *(p1++) = '\n'; } else p1++; } *p1 = '\0'; return (fixedheader); } /* * milter_addheader() -- Add the supplied header to the message * * Parameters: * response -- encoded form of header/value. * rlen -- length of response. * e -- current envelope. * * Returns: * none */ static void milter_addheader (char* response,ssize_t rlen,ENVELOPE* e) { char *val; HDR *h = NULL; ssize_t rrlen; /* sanity checks */ if (response == NULL) { e->e_state |= (MS_ER|ER_PO); ERROR("NULL response"); return; } rrlen = strlen(response); if ((rlen < 2) || ((size_t) rrlen + 1 >= (size_t) rlen)) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } /* another sanity check - check if integral resonse size matches */ if (rrlen + strlen(response + rrlen + 1) + 2 != (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } /* Find separating NUL and fix line termination */ val = milter_fixlinefeed(response + rrlen + 1); if (*response == '\0') { ERROR("empty field name"); return; } /* add to e_msgsize */ e->e_msgsize += rrlen + 2 + strlen(val); syslog(LOG_INFO, "libci_milter: milter_addheader: Milter add: %.512s value with %.512s", response, val); h = make_header(response, val); add_header(e, h); e->e_state |= OK_HC; } /* * milter_insheader() -- Insert the supplied header * * Parameters: * response -- encoded form of header/value. * rlen -- length of response. * e -- current envelope. * * Returns: * none * * Notes: * Unlike milter_addheader(), this does not attempt to determine * if the header already exists in the envelope, even a * deleted version. It just blindly inserts. */ static void milter_insheader(char* response,ssize_t rlen,ENVELOPE* e) { int idx, i; char *field; char *val; HDR *h,*newh,*last = NULL; ssize_t rrlen; /* sanity checks */ if (response == NULL) { e->e_state |= (MS_ER|ER_PO); ERROR("NULL response"); return; } rrlen = strlen(response); if ((rlen < 2) || ((size_t) rrlen + 1 >= (size_t) rlen)) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } /* decode */ (void) memcpy((char *) &i, response, MILTER_LEN_BYTES); idx = ntohl(i); field = response + MILTER_LEN_BYTES; val = milter_fixlinefeed(field + strlen(field) + 1); /* another sanity check */ if (MILTER_LEN_BYTES + strlen(field) + 1 + strlen(field + strlen(field) + 1) + 1 != (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } if (*field == '\0') { ERROR("empty field name"); return; } /* add to e_msgsize */ e->e_msgsize += rrlen + 2 + strlen(val); syslog(LOG_INFO, "libci_milter: milter_insheader: Milter add: %.512s value with %.512s", response, val); newh = make_header(response, val); for (h = e->e_headers; h != NULL && idx > 0 ; h = h->h_link, idx--) last = h; if ((!h) || (!e->e_headers)) { add_header(e,newh); e->e_state |= OK_HC; return; } newh->h_link = last->h_link; last->h_link = newh; } /* * milter_changeheader() -- Change the supplied header in the message * * Parameters: * response -- encoded form of header/index/value. * rlen -- length of response. * e -- current envelope. * * Returns: * none */ static void milter_changeheader(char* response,ssize_t rlen,ENVELOPE* e) { int i, idx; char *field, *val; HDR *h; /* sanity checks */ if (response == NULL) { e->e_state |= (MS_ER|ER_PO); ERROR("NULL response"); return; } if (rlen < 2 || strlen(response) + 1 >= (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } /* Find separating NUL */ (void) memcpy((char *) &i, response, MILTER_LEN_BYTES); idx = ntohl(i); field = response + MILTER_LEN_BYTES; val = milter_fixlinefeed(field + strlen(field) + 1); /* another sanity check */ if (MILTER_LEN_BYTES + strlen(field) + 1 + strlen(field + strlen(field) + 1) + 1 != (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } if (*field == '\0') { ERROR("empty field name"); return; } for (h = e->e_headers; h != NULL; h = h->h_link) { if (strncasecmp(h->h_field, field, MIN(MAX_HEADER_LEN, MAX(strlen(h->h_field), strlen(field)))) == 0) { if (--idx <= 0) break; } } if (h == NULL) { if (*val == '\0') { #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_changeheader: delete (noop) %.255s:", field); #endif } else { /* treat modify value with no existing header as add */ #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_changeheader: add: %.255s:%.255s", field,val); #endif h = make_header(field, val); add_header(e, h); } return; } if (*val == '\0') { #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_changeheader: delete %.64s: %.255s\n", field, h->h_value == NULL ? "" : h->h_value); #endif } else { #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_changeheader: change %.64s: from %.64s to %.64s\n", field, h->h_value == NULL ? "" : h->h_value, val); #endif } if (h->h_value) free(h->h_value); h->h_value = NULL; if (*val) { h->h_value = xstrdup(val); e->e_msgsize += strlen(h->h_value); } e->e_state |= OK_HC; } /* * milter_addrcpt() -- Add the supplied recipient to the message * * Parameters: * response -- encoded form of recipient address. * rlen -- length of response. * e -- current envelope. * * Returns: * none */ static void milter_addrcpt (char* response,ssize_t rlen,ENVELOPE* e) { RCPT *r; /* sanity checks */ if (response == NULL) { e->e_state |= (MS_ER|ER_PO); ERROR("NULL response"); return; } if (*response == '\0' || strlen(response) + 1 != (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); syslog(LOG_ERR,"libci_milter: milter_addrcpt: protocol error (total len %d != rlen %d)\n", (int) strlen(response), (int) (rlen - 1)); return; } #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_addrcpt: add: %.255s", response); #endif for (r = e->e_rcpts; r != NULL; r = r->r_link) { if(strncasecmp(r->r_value, response, MIN(MAX_HEADER_LEN, MAX(strlen(r->r_value), strlen(response)))) == 0) { syslog(LOG_INFO, "libci_milter: milter_addrcpt: RCPT: %.255s already exist, skipping", response); return; } } r->r_link = (RCPT *) xmalloc (sizeof (RCPT)); r=r->r_link; r->r_link = NULL; r->r_value = xstrdup(response); e->e_state |= OK_HC; return; } /* * milter_delrcpt() -- delete the supplied recipient from the message * * Parameters: * response -- encoded form of recipient address. * rlen -- length of response. * e -- current envelope. * * Returns: * none */ static void milter_delrcpt (char* response,ssize_t rlen,ENVELOPE* e) { RCPT *r, *r_prev; /* sanity checks */ if (response == NULL) { e->e_state |= (MS_ER|ER_PO); ERROR("NULL response"); return; } if (*response == '\0' || strlen(response) + 1 != (size_t) rlen) { e->e_state |= (MS_ER|ER_PO); ERROR("protocol error"); return; } syslog(LOG_INFO,"libci_milter: milter_delrcpt: %.255s\n", response); r_prev = e->e_rcpts; for (r = e->e_rcpts; r != NULL; r = r->r_link) { if(strncasecmp(r->r_value, response, MIN(MAX_HEADER_LEN, MAX(strlen(r->r_value), strlen(response)))) == 0) { if(r_prev == NULL) { e->e_state |= (MS_ER|ER_ME); ERROR("internal error"); return; } if(r->r_link != NULL) r_prev->r_link = r->r_link; else r_prev->r_link = NULL; if(r->r_value != NULL) { free(r->r_value); r->r_value = NULL; } if(r != NULL) { free(r); r = NULL; } #ifndef SILENCIO syslog(LOG_INFO, "libci_milter: milter_delrcpt: RCPT: %.255s deleted", response); #endif e->e_state |= OK_HC; return; } r_prev = r; } e->e_state |= (MS_ER|ER_ME); syslog(LOG_ERR, "libci_milter: milter_delrcpt: RCPT: failed to delete recepient %.255s ", response); return; } /* * milter_replbody() -- replace the current data file with new body * the function takes data chunk and appends it * to new body (or creates new body when called * the first time) * * Parameters: * response -- encoded form of new body. * rlen -- length of response. * newfilter -- if first time called by a new filter * e -- current envelope. * * Returns: * MI_SUCCESS or MI_FAILURE */ static int milter_replbody (char* response,ssize_t rlen,bool newfilter,ENVELOPE* e) { static char prevchar; static int len_chk; int i; char buf[MAX_HEADER_LEN]; bzero(buf, sizeof(buf)); /* If a new filter, reset previous character and truncate data file */ if (newfilter) { if(close(e->e_bfd) == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to close file %.255s fd %d %m", e->e_bfn, e->e_bfd); return MI_FAILURE; } e->e_bfd = -1; if((e->e_bfd = open(e->e_bfn, O_RDWR)) == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to open file %.255s with O_RDWR option(s) %m", e->e_bfn); return MI_FAILURE; } if(lseek(e->e_bfd, 0, SEEK_SET) == -1) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to seek file %m"); return MI_FAILURE; } e->e_state |= OK_BC; /* Reset static variables*/ prevchar = '\000'; len_chk = 0; } if (response == NULL) { /* Flush the buffered '\r' */ if (prevchar == '\r') { if(write(e->e_bfd, "\r\n", 2) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } e->e_msgsize+=2; } return MI_SUCCESS; } for(i = 0; i < rlen; i++, len_chk++) { int blksize; /* as said in rfc, here MUST be crlf so let's put it anyway */ if((len_chk >= MAX_HEADER_LEN - 1) && ((i + 1) < rlen) && (response[i] != 0x0d) && (response[i + 1] != 0x0a )) { if(write(e->e_bfd, "\r\n", 2) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } e->e_msgsize+= 2; prevchar = (char)0; len_chk = 1; } /* Buffered char from last chunk */ if (i == 0 && prevchar == 0x0d) { prevchar = (char) 0; len_chk = 1; if(write(e->e_bfd, "\r\n", 2) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } e->e_msgsize+=2; if (response[i] == 0x0a) { len_chk = 0; continue; } } for (blksize = 0; (blksize + i) < (rlen - 1) ; blksize++) { if ((response[i+blksize] == 0x0d) || (response[i+blksize] == 0x0a)) break; if (len_chk + blksize >= MAX_HEADER_LEN -2) break; } if (blksize) { if(write(e->e_bfd, &response[i], blksize) != blksize) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } i += blksize - 1; len_chk += blksize - 1; e->e_msgsize += blksize; continue; } if (response[i] == 0x0d) { prevchar = 0x0d; if ((i + 1) >= rlen) continue; else { if (response[i + 1] != 0x0a) { prevchar = (char)0; len_chk = 0; if(write(e->e_bfd, "\r\n", 2) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } e->e_msgsize+=2; continue; } } } if(response[i] == 0x0a) { if(prevchar != 0x0d){ if(write(e->e_bfd, "\r\n", 2) != 2) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } prevchar = (char)0; len_chk = 0; e->e_msgsize+=2; continue; } prevchar = (char)0; len_chk = 0; } if(write(e->e_bfd, &response[i], 1) != 1) { e->e_state |= MS_ER; e->e_state |= ER_IO; syslog(LOG_ERR, "libci_milter: milter_replbody: failed to write to the file %.255s %m", e->e_bfn); return MI_FAILURE; } e->e_msgsize++; } return MI_SUCCESS; } /* * milter_quit_filter() -- close down a single filter * * Parameters: * m -- milter structure of filter to close down. * e -- current envelope. * * Returns: * none */ int milter_quit_filter (struct milter* m) { /* Never replace error state */ if (m->mf_state == SMFS_ERROR) return(MI_FAILURE); if (m->mf_sock < 0 || m->mf_state == SMFS_CLOSED || m->mf_state == SMFS_READY) { m->mf_sock = -1; m->mf_state = SMFS_CLOSED; return(MI_SUCCESS); } (void) milter_write(m, SMFIC_QUIT, (char *) NULL, 0); if (m->mf_sock >= 0) { (void) close(m->mf_sock); m->mf_sock = -1; } if (m->mf_state == SMFS_ERROR) return(MI_FAILURE); m->mf_state = SMFS_CLOSED; return(MI_SUCCESS); } /* * milter_data() -- send message headers/body and gather final results * * Parameters: * e -- current envelope. * state -- return state from response. * * Returns: * response string (may be NULL) * * Side effects: * - Uses milter_replbody to perform actual body change * - Can call the various milter action routines to * modify the envelope or message. */ # define MILTER_CHECK_RESULTS() \ if (*state == SMFIR_ACCEPT || \ m->mf_state == SMFS_DONE || \ m->mf_state == SMFS_ERROR) \ { \ if (m->mf_state != SMFS_ERROR) \ m->mf_state = SMFS_DONE; \ return MI_SUCCESS; \ } \ if (*state != SMFIR_CONTINUE) \ { \ m->mf_state = SMFS_DONE; \ goto finishup; \ } int milter_data (struct milter* m,ENVELOPE* e,char* state) { bool replbody = false; /* milter_replbody() called? */ bool replfailed = false; /* milter_replbody() failed? */ bool newfilter; /* reset on each new filter */ char m_rcmd; char *response = NULL; time_t eomsent; ssize_t rlen; *state = SMFIR_CONTINUE; if ((m->mf_state == SMFS_DONE) || (e->e_state & (MS_RJ | MS_QU))) return(MI_SUCCESS); if (*state != SMFIR_CONTINUE && *state != SMFIR_ACCEPT) { /* ** A previous filter has dealt with the message, ** safe to stop processing the filters. */ ERROR("bad state"); e->e_state |= (MS_ER|ER_ME); return(MI_FAILURE); } /* Now reset state for later evaluation */ *state = SMFIR_CONTINUE; newfilter = true; /* previous problem? */ if (m->mf_state == SMFS_ERROR) { return(MI_FAILURE); } /* sanity checks */ if (m->mf_sock < 0 || (m->mf_state != SMFS_OPEN && m->mf_state != SMFS_INMSG)) goto finishup; m->mf_state = SMFS_INMSG; /* check if filter wants the headers */ if (!(SMFIP_NOHDRS & m->mf_pflags)) { milter_headers(m, e, state); MILTER_CHECK_RESULTS(); } /* check if filter wants EOH */ if (!(SMFIP_NOEOH & m->mf_pflags)) { /* send it over */ milter_send_command(m, SMFIC_EOH, NULL, 0, state); MILTER_CHECK_RESULTS(); } /* check if filter wants the body */ if (!(SMFIP_NOBODY & m->mf_pflags) && e->e_bfd != -1) { milter_body(m, e, state); MILTER_CHECK_RESULTS(); } /* send the final body chunk */ milter_write(m, SMFIC_BODYEOB, NULL, 0); /* Get time EOM sent for timeout */ eomsent = now(); /* deal with the possibility of multiple responses */ while (*state == SMFIR_CONTINUE) { /* Check total timeout from EOM to final ACK/NAK */ if (m->mf_timeout > 0 && now() - eomsent >= m->mf_timeout) { ERROR("EOM ACK/NAK timeout"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_NE; break; } response = milter_read(m, &m_rcmd, &rlen); if (m->mf_state == SMFS_ERROR) { e->e_state |= (MS_ER|ER_NE); break; } switch (m_rcmd) { case SMFIR_REPLYCODE: syslog(LOG_INFO,"libci_milter: milter_data: milter=%.128s, reject=%.128s", m->mf_name, response); *state = m_rcmd; m->mf_state = SMFS_DONE; e->e_state |= MS_RJ; break; case SMFIR_REJECT: /* log msg at end of function */ case SMFIR_DISCARD: *state = m_rcmd; m->mf_state = SMFS_DONE; e->e_state |= MS_RJ; break; case SMFIR_TEMPFAIL: *state = m_rcmd; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; break; case SMFIR_QUARANTINE: *state = m_rcmd; m->mf_state = SMFS_DONE; e->e_state |= MS_QU; break; case SMFIR_CONTINUE: case SMFIR_ACCEPT: /* this filter is done with message */ if (replfailed) *state = SMFIR_TEMPFAIL; else *state = SMFIR_ACCEPT; m->mf_state = SMFS_DONE; break; case SMFIR_PROGRESS: eomsent = now(); break; case SMFIR_ADDHEADER: if (!(SMFIF_ADDHDRS & m->mf_fflags)) { #ifndef PARANOID syslog(LOG_WARNING, "libci_milter: milter_data: lied about adding headers, honoring request anyway"); #else syslog(LOG_ERR, "libci_milter: milter_data: lied about adding headers, aborting"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_PO; break; #endif } (void) milter_addheader(response, rlen, e); break; case SMFIR_INSHEADER: if (!(SMFIF_ADDHDRS & m->mf_fflags)) { #ifndef PARANOID syslog(LOG_WARNING, "libci_milter: milter_data: lied about adding headers, honoring request anyway"); #else syslog(LOG_ERR, "libci_milter: milter_data: lied about adding headers, aborting"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_PO; break; #endif } (void) milter_insheader(response, rlen, e); break; case SMFIR_CHGHEADER: if (!(SMFIF_CHGHDRS & m->mf_fflags)) { #ifndef PARANOID syslog(LOG_WARNING, "libci_milter: milter_data: lied about changing headers, honoring request anyway"); #else syslog(LOG_ERR, "libci_milter: milter_data: lied about changing headers, aborting"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_PO; break; #endif } (void)milter_changeheader(response, rlen, e); break; case SMFIR_ADDRCPT: if (!(SMFIF_ADDRCPT & m->mf_fflags)) { #ifndef PARANOID syslog(LOG_WARNING, "libci_milter: milter_data: lied about adding recipients, honoring request anyway"); #else syslog(LOG_ERR, "libci_milter: milter_data: lied about adding receipients, aborting"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_PO; break; #endif } (void)milter_addrcpt(response, rlen, e); break; case SMFIR_DELRCPT: if (!(SMFIF_DELRCPT & m->mf_fflags)) { #ifndef PARANOID syslog(LOG_WARNING, "libci_milter: milter_data: lied about removing recipients, honoring request anyway"); #else syslog(LOG_ERR, "libci_milter: milter_data: lied about removing receipients, aborting"); *state = SMFIR_TEMPFAIL; m->mf_state = SMFS_ERROR; e->e_state |= MS_ER; e->e_state |= ER_PO; break; #endif } milter_delrcpt(response, rlen, e); break; case SMFIR_REPLBODY: if (!(SMFIF_MODBODY & m->mf_fflags)) { syslog(LOG_ERR, "libci_milter: milter_data: lied about replacing body, rejecting request and tempfailing message"); replfailed = true; break; } /* already failed in attempt */ if (replfailed) break; if (milter_replbody(response, rlen, newfilter, e) < 0) replfailed = true; newfilter = false; replbody = true; break; default: /* Invalid response to command */ syslog(LOG_ERR, "libci_milter: milter_data: returned bogus response %c", m_rcmd); e->e_state |= (MS_ER|ER_PO); break; } if (m_rcmd != SMFIR_REPLYCODE && response != NULL) { free(response); response = NULL; } if (m->mf_state == SMFS_ERROR) break; } if (replbody && !replfailed) { /* flush possible buffered character */ milter_replbody(NULL, 0, !replbody, e); replbody = false; } finishup: /* leave things in the expected state if we touched it */ if (replfailed) { if (*state == SMFIR_CONTINUE || *state == SMFIR_ACCEPT) { *state = SMFIR_TEMPFAIL; if(response) { free(response); response = NULL; } } e->e_state |= MS_ER; return(MI_FAILURE); } MILTER_CHECK_DONE_MSG(); if ((*state == SMFIR_TEMPFAIL) || (e->e_state & MS_ER)) { e->e_state |= MS_ER; syslog(LOG_INFO, "libci_milter: milter_data: error"); return(MI_FAILURE); } if ((*state == SMFIR_REJECT) || (e->e_state & MS_RJ)) { e->e_state |= MS_RJ; syslog(LOG_INFO, "libci_milter: milter_data: reject"); } if ((*state == SMFIR_QUARANTINE) || (e->e_state & MS_QU)) { e->e_state |= MS_QU; syslog(LOG_INFO, "libci_milter: milter_data: quarantine"); } return(MI_SUCCESS); }