/* * Copyright (c) 1999-2005 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. * */ #include SM_RCSID("@(#)$Id: smfi.c,v 8.74 2005/03/30 00:44:07 ca Exp $") #include #include "libmilter.h" static int smfi_header __P((SMFICTX *, int, int, char *, char *)); static int myisenhsc __P((const char *, int)); /* for smfi_set{ml}reply, let's be generous. 256/16 should be sufficient */ #define MAXREPLYLEN 980 /* max. length of a reply string */ #define MAXREPLIES 32 /* max. number of reply strings */ /* ** SMFI_HEADER -- send a header to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** cmd -- Header modification command ** hdridx -- Header index ** headerf -- Header field name ** headerv -- Header field value ** ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ static int smfi_header(ctx, cmd, hdridx, headerf, headerv) SMFICTX *ctx; int cmd; int hdridx; char *headerf; char *headerv; { size_t len, l1, l2, offset; int r; mi_int32 v; char *buf; struct timeval timeout; if (headerf == NULL || *headerf == '\0' || headerv == NULL) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; l1 = strlen(headerf) + 1; l2 = strlen(headerv) + 1; len = l1 + l2; if (hdridx >= 0) len += MILTER_LEN_BYTES; buf = malloc(len); if (buf == NULL) return MI_FAILURE; offset = 0; if (hdridx >= 0) { v = htonl(hdridx); (void) memcpy(&(buf[0]), (void *) &v, MILTER_LEN_BYTES); offset += MILTER_LEN_BYTES; } (void) memcpy(buf + offset, headerf, l1); (void) memcpy(buf + offset + l1, headerv, l2); r = mi_wr_cmd(ctx->ctx_sd, &timeout, cmd, buf, len); free(buf); return r; } /* ** SMFI_ADDHEADER -- send a new header to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** headerf -- Header field name ** headerv -- Header field value ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_addheader(ctx, headerf, headerv) SMFICTX *ctx; char *headerf; char *headerv; { if (!mi_sendok(ctx, SMFIF_ADDHDRS)) return MI_FAILURE; return smfi_header(ctx, SMFIR_ADDHEADER, -1, headerf, headerv); } /* ** SMFI_INSHEADER -- send a new header to the MTA (to be inserted) ** ** Parameters: ** ctx -- Opaque context structure ** hdridx -- index into header list where insertion should occur ** headerf -- Header field name ** headerv -- Header field value ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_insheader(ctx, hdridx, headerf, headerv) SMFICTX *ctx; int hdridx; char *headerf; char *headerv; { if (!mi_sendok(ctx, SMFIF_ADDHDRS) || hdridx < 0) return MI_FAILURE; return smfi_header(ctx, SMFIR_INSHEADER, hdridx, headerf, headerv); } /* ** SMFI_CHGHEADER -- send a changed header to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** headerf -- Header field name ** hdridx -- Header index value ** headerv -- Header field value ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_chgheader(ctx, headerf, hdridx, headerv) SMFICTX *ctx; char *headerf; mi_int32 hdridx; char *headerv; { if (!mi_sendok(ctx, SMFIF_CHGHDRS) || hdridx < 0) return MI_FAILURE; if (headerv == NULL) headerv = ""; return smfi_header(ctx, SMFIR_CHGHEADER, hdridx, headerf, headerv); } /* ** SMFI_ADDRCPT -- send an additional recipient to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** rcpt -- recipient address ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_addrcpt(ctx, rcpt) SMFICTX *ctx; char *rcpt; { size_t len; struct timeval timeout; if (rcpt == NULL || *rcpt == '\0') return MI_FAILURE; if (!mi_sendok(ctx, SMFIF_ADDRCPT)) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; len = strlen(rcpt) + 1; return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_ADDRCPT, rcpt, len); } /* ** SMFI_DELRCPT -- send a recipient to be removed to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** rcpt -- recipient address ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_delrcpt(ctx, rcpt) SMFICTX *ctx; char *rcpt; { size_t len; struct timeval timeout; if (rcpt == NULL || *rcpt == '\0') return MI_FAILURE; if (!mi_sendok(ctx, SMFIF_DELRCPT)) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; len = strlen(rcpt) + 1; return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_DELRCPT, rcpt, len); } /* ** SMFI_REPLACEBODY -- send a body chunk to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** bodyp -- body chunk ** bodylen -- length of body chunk ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_replacebody(ctx, bodyp, bodylen) SMFICTX *ctx; unsigned char *bodyp; int bodylen; { int len, off, r; struct timeval timeout; if (bodylen < 0 || (bodyp == NULL && bodylen > 0)) return MI_FAILURE; if (!mi_sendok(ctx, SMFIF_CHGBODY)) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; /* split body chunk if necessary */ off = 0; do { len = (bodylen >= MILTER_CHUNK_SIZE) ? MILTER_CHUNK_SIZE : bodylen; if ((r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_REPLBODY, (char *) (bodyp + off), len)) != MI_SUCCESS) return r; off += len; bodylen -= len; } while (bodylen > 0); return MI_SUCCESS; } /* ** SMFI_QUARANTINE -- quarantine an envelope ** ** Parameters: ** ctx -- Opaque context structure ** reason -- why? ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_quarantine(ctx, reason) SMFICTX *ctx; char *reason; { size_t len; int r; char *buf; struct timeval timeout; if (reason == NULL || *reason == '\0') return MI_FAILURE; if (!mi_sendok(ctx, SMFIF_QUARANTINE)) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; len = strlen(reason) + 1; buf = malloc(len); if (buf == NULL) return MI_FAILURE; (void) memcpy(buf, reason, len); r = mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_QUARANTINE, buf, len); free(buf); return r; } /* ** MYISENHSC -- check whether a string contains an enhanced status code ** ** Parameters: ** s -- string with possible enhanced status code. ** delim -- delim for enhanced status code. ** ** Returns: ** 0 -- no enhanced status code. ** >4 -- length of enhanced status code. ** ** Side Effects: ** none. */ static int myisenhsc(s, delim) const char *s; int delim; { int l, h; if (s == NULL) return 0; if (!((*s == '2' || *s == '4' || *s == '5') && s[1] == '.')) return 0; h = 0; l = 2; while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) ++h; if (h == 0 || s[l + h] != '.') return 0; l += h + 1; h = 0; while (h < 3 && isascii(s[l + h]) && isdigit(s[l + h])) ++h; if (h == 0 || s[l + h] != delim) return 0; return l + h; } /* ** SMFI_SETREPLY -- set the reply code for the next reply to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** rcode -- The three-digit (RFC 821) SMTP reply code. ** xcode -- The extended (RFC 2034) reply code. ** message -- The text part of the SMTP reply. ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_setreply(ctx, rcode, xcode, message) SMFICTX *ctx; char *rcode; char *xcode; char *message; { size_t len; char *buf; if (rcode == NULL || ctx == NULL) return MI_FAILURE; /* ### \0 */ len = strlen(rcode) + 2; if (len != 5) return MI_FAILURE; if ((rcode[0] != '4' && rcode[0] != '5') || !isascii(rcode[1]) || !isdigit(rcode[1]) || !isascii(rcode[2]) || !isdigit(rcode[2])) return MI_FAILURE; if (xcode != NULL) { if (!myisenhsc(xcode, '\0')) return MI_FAILURE; len += strlen(xcode) + 1; } if (message != NULL) { size_t ml; /* XXX check also for unprintable chars? */ if (strpbrk(message, "\r\n") != NULL) return MI_FAILURE; ml = strlen(message); if (ml > MAXREPLYLEN) return MI_FAILURE; len += ml + 1; } buf = malloc(len); if (buf == NULL) return MI_FAILURE; /* oops */ (void) sm_strlcpy(buf, rcode, len); (void) sm_strlcat(buf, " ", len); if (xcode != NULL) (void) sm_strlcat(buf, xcode, len); if (message != NULL) { if (xcode != NULL) (void) sm_strlcat(buf, " ", len); (void) sm_strlcat(buf, message, len); } if (ctx->ctx_reply != NULL) free(ctx->ctx_reply); ctx->ctx_reply = buf; return MI_SUCCESS; } /* ** SMFI_SETMLREPLY -- set multiline reply code for the next reply to the MTA ** ** Parameters: ** ctx -- Opaque context structure ** rcode -- The three-digit (RFC 821) SMTP reply code. ** xcode -- The extended (RFC 2034) reply code. ** txt, ... -- The text part of the SMTP reply, ** MUST be terminated with NULL. ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int #if SM_VA_STD smfi_setmlreply(SMFICTX *ctx, const char *rcode, const char *xcode, ...) #else /* SM_VA_STD */ smfi_setmlreply(ctx, rcode, xcode, va_alist) SMFICTX *ctx; const char *rcode; const char *xcode; va_dcl #endif /* SM_VA_STD */ { size_t len; size_t rlen; int args; char *buf, *txt; const char *xc; char repl[16]; SM_VA_LOCAL_DECL if (rcode == NULL || ctx == NULL) return MI_FAILURE; /* ### */ len = strlen(rcode) + 1; if (len != 4) return MI_FAILURE; if ((rcode[0] != '4' && rcode[0] != '5') || !isascii(rcode[1]) || !isdigit(rcode[1]) || !isascii(rcode[2]) || !isdigit(rcode[2])) return MI_FAILURE; if (xcode != NULL) { if (!myisenhsc(xcode, '\0')) return MI_FAILURE; xc = xcode; } else { if (rcode[0] == '4') xc = "4.0.0"; else xc = "5.0.0"; } /* add trailing space */ len += strlen(xc) + 1; rlen = len; args = 0; SM_VA_START(ap, xcode); while ((txt = SM_VA_ARG(ap, char *)) != NULL) { size_t tl; tl = strlen(txt); if (tl > MAXREPLYLEN) break; /* this text, reply codes, \r\n */ len += tl + 2 + rlen; if (++args > MAXREPLIES) break; /* XXX check also for unprintable chars? */ if (strpbrk(txt, "\r\n") != NULL) break; } SM_VA_END(ap); if (txt != NULL) return MI_FAILURE; /* trailing '\0' */ ++len; buf = malloc(len); if (buf == NULL) return MI_FAILURE; /* oops */ (void) sm_strlcpyn(buf, len, 3, rcode, args == 1 ? " " : "-", xc); (void) sm_strlcpyn(repl, sizeof repl, 4, rcode, args == 1 ? " " : "-", xc, " "); SM_VA_START(ap, xcode); txt = SM_VA_ARG(ap, char *); if (txt != NULL) { (void) sm_strlcat2(buf, " ", txt, len); while ((txt = SM_VA_ARG(ap, char *)) != NULL) { if (--args <= 1) repl[3] = ' '; (void) sm_strlcat2(buf, "\r\n", repl, len); (void) sm_strlcat(buf, txt, len); } } if (ctx->ctx_reply != NULL) free(ctx->ctx_reply); ctx->ctx_reply = buf; SM_VA_END(ap); return MI_SUCCESS; } /* ** SMFI_SETPRIV -- set private data ** ** Parameters: ** ctx -- Opaque context structure ** privatedata -- pointer to private data ** ** Returns: ** MI_SUCCESS/MI_FAILURE */ int smfi_setpriv(ctx, privatedata) SMFICTX *ctx; void *privatedata; { if (ctx == NULL) return MI_FAILURE; ctx->ctx_privdata = privatedata; return MI_SUCCESS; } /* ** SMFI_GETPRIV -- get private data ** ** Parameters: ** ctx -- Opaque context structure ** ** Returns: ** pointer to private data */ void * smfi_getpriv(ctx) SMFICTX *ctx; { if (ctx == NULL) return NULL; return ctx->ctx_privdata; } /* ** SMFI_GETSYMVAL -- get the value of a macro ** ** See explanation in mfapi.h about layout of the structures. ** ** Parameters: ** ctx -- Opaque context structure ** symname -- name of macro ** ** Returns: ** value of macro (NULL in case of failure) */ char * smfi_getsymval(ctx, symname) SMFICTX *ctx; char *symname; { int i; char **s; char one[2]; char braces[4]; if (ctx == NULL || symname == NULL || *symname == '\0') return NULL; if (strlen(symname) == 3 && symname[0] == '{' && symname[2] == '}') { one[0] = symname[1]; one[1] = '\0'; } else one[0] = '\0'; if (strlen(symname) == 1) { braces[0] = '{'; braces[1] = *symname; braces[2] = '}'; braces[3] = '\0'; } else braces[0] = '\0'; /* search backwards through the macro array */ for (i = MAX_MACROS_ENTRIES - 1 ; i >= 0; --i) { if ((s = ctx->ctx_mac_ptr[i]) == NULL || ctx->ctx_mac_buf[i] == NULL) continue; while (s != NULL && *s != NULL) { if (strcmp(*s, symname) == 0) return *++s; if (one[0] != '\0' && strcmp(*s, one) == 0) return *++s; if (braces[0] != '\0' && strcmp(*s, braces) == 0) return *++s; ++s; /* skip over macro value */ ++s; /* points to next macro name */ } } return NULL; } /* ** SMFI_PROGRESS -- send "progress" message to the MTA to prevent premature ** timeouts during long milter-side operations ** ** Parameters: ** ctx -- Opaque context structure ** ** Return value: ** MI_SUCCESS/MI_FAILURE */ int smfi_progress(ctx) SMFICTX *ctx; { struct timeval timeout; if (ctx == NULL) return MI_FAILURE; timeout.tv_sec = ctx->ctx_timeout; timeout.tv_usec = 0; return mi_wr_cmd(ctx->ctx_sd, &timeout, SMFIR_PROGRESS, NULL, 0); }