/* This file is part of gacopyz. Copyright (C) 2007 Sergey Poznyakoff 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 3, 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, see . */ #include typedef struct gacopyz_macro_def *gacopyz_macro_def_t; struct gacopyz_macro_def { char *name; char *value; }; enum gacopyz_srv_state { srv_init, /* Initialized but not connected */ srv_connected, /* Initialized and connected to the client */ srv_ready, /* Ready for processing requests */ srv_msgproc, /* Processing a message */ srv_done, /* Finished processing a message */ srv_closed, /* Finished and closed */ srv_disabled, /* Disabled for any further requests */ srv_error, /* Some error ocurred */ }; struct gacopyz_srv { char *id; /* Server identifier (not used yet) */ char *portspec; /* Port spec */ struct gacopyz_iod iod; enum gacopyz_srv_state state; unsigned long flags; gacopyz_macro_def_t def; size_t ndefs; size_t maxdefs; gacopyz_uint32_t source_addr; int onerror; void (*memerror) (gacopyz_srv_t, const char *, unsigned int); int version; int acts; int proto; char *buf; size_t bufsize; char *resp_ptr; size_t resp_size; }; void default_memerror (gacopyz_srv_t srv, const char *file, unsigned int line) { gacopyz_io_log(&srv->iod, SMI_LOG_FATAL, _("%s:%lu: Not enough memory"), file, line); abort(); } #define GACOPYZ_ASSERT(e) \ if (!(e)) { \ fprintf(stderr, "%s:%d: GACOPYZ usage error: assertion failed: %s\n", \ __FILE__, __LINE__, #e); \ abort(); \ } #define GACOPYZ_ALLOC(srv, expr) do \ if (!(expr)) { \ srv->memerror(srv, __FILE__, __LINE__); \ if (!(expr)) \ default_memerror(srv, __FILE__, __LINE__); \ } while (0) static struct gacopyz_macro_def * find_def(gacopyz_srv_t srv, const char *name, size_t len) { size_t i; for (i = 0; i < srv->ndefs; i++) if (strncmp(srv->def[i].name, name, len) == 0) return &srv->def[i]; return NULL; } static void add_def(gacopyz_srv_t srv, const char *name, size_t len, const char *value, int overwrite) { struct gacopyz_macro_def *def; if (def = find_def(srv, name, len)) { if (overwrite) free(def->value); } else { if (srv->ndefs == srv->maxdefs) { size_t maxdefs = srv->maxdefs + 64; GACOPYZ_ALLOC(srv, def = realloc(srv->def, sizeof(srv->def[0]) * maxdefs)); srv->maxdefs = maxdefs; srv->def = def; } def = srv->def + srv->ndefs++; GACOPYZ_ALLOC(srv, def->name = malloc(len + 1)); memcpy(def->name, name, len); def->name[len] = 0; } GACOPYZ_ALLOC(srv, def->value = strdup(value)); } void del_def(gacopyz_srv_t srv, const char *name, size_t len) { struct gacopyz_macro_def *def = find_def(srv, name, len); size_t n; if (!def) return; free(def->name); free(def->value); srv->ndefs--; memmove(def, def + 1, sizeof(srv->def[0]) *(srv->ndefs - (def - srv->def))); } static void srv_ensure_space(gacopyz_srv_t srv, size_t size) { if (size > srv->bufsize) { char *p; GACOPYZ_ALLOC(srv, p = realloc(srv->buf, size)); srv->buf = p; srv->bufsize = size; } } size_t macro_size(gacopyz_srv_t srv) { size_t i; size_t size = 0; struct gacopyz_macro_def *def; for (i = 0, def = srv->def; i < srv->ndefs; i++, def++) { size_t len = strlen(def->name); size += len; if (len > 1 && def->name[0] != '{') size += 2; /* for {} */ size += 1 + strlen (def->value) + 1; } return size; } void srv_format_macros(gacopyz_srv_t srv, unsigned char cmd, size_t *psize) { size_t i; char *p; size_t size = macro_size(srv) + 1; struct gacopyz_macro_def *def; srv_ensure_space(srv, size); p = srv->buf; *p++ = cmd; for (i = 0, def = srv->def; i < srv->ndefs; i++, def++) { size_t len = strlen(def->name); if (len > 1 && def->name[0] != '{') { *p++ = '{'; memcpy(p, def->name, len); p += len; *p++ = '}'; *p++ = 0; } else { len++; memcpy(p, def->name, len); p += len; } len = strlen(def->value) + 1; memcpy(p, def->value, len); p += len; } *psize = size; } #define GETNAMELEN(name, len) \ len = strlen(name); \ if (name[0] == '{' && name[len-1] == '}') { \ name++; \ len -= 2; \ } int gacopyz_srv_find_macro(gacopyz_srv_t srv, const char *name, const char **pval) { struct gacopyz_macro_def *def; size_t len; GACOPYZ_ASSERT(srv); GETNAMELEN(name, len); def = find_def(srv, name, len); if (def) { *pval = def->value; return MI_SUCCESS; } return MI_FAILURE; } void gacopyz_srv_define_macro0(gacopyz_srv_t srv, const char *name, const char *value, int overwrite) { size_t len; GACOPYZ_ASSERT(srv); GETNAMELEN(name, len); add_def(srv, name, len, value, overwrite); } void gacopyz_srv_define_macro(gacopyz_srv_t srv, const char *name, const char *value) { gacopyz_srv_define_macro0(srv, name, value, 1); } void gacopyz_srv_del_macro(gacopyz_srv_t srv, const char *name) { size_t len; GACOPYZ_ASSERT(srv); GETNAMELEN(name, len); del_def(srv, name, len); } void gacopyz_srv_clear_macros(gacopyz_srv_t srv) { size_t i; GACOPYZ_ASSERT(srv); for (i = 0; i < srv->ndefs; i++) { free(srv->def[i].name); free(srv->def[i].value); } srv->ndefs = 0; } void gacopyz_srv_iterate_macros(gacopyz_srv_t srv, int (*func)(const char *name, const char *value, void *data), void *data) { size_t i; GACOPYZ_ASSERT(srv); for (i = 0; i < srv->ndefs; i++) if (func(srv->def[i].name, srv->def[i].value, data)) break; } void gacopyz_srv_count_macros(gacopyz_srv_t srv, size_t *count) { GACOPYZ_ASSERT(srv); *count = srv->ndefs; } struct timeval default_gacopyz_timeout[GACOPYZ_TO_COUNT] = { { GACOPYZ_WRITE_TIMEOUT, 0 }, { GACOPYZ_READ_TIMEOUT, 0 }, { GACOPYZ_EOM_TIMEOUT, 0 }, { GACOPYZ_CONNECT_TIMEOUT, 0 } }; int gacopyz_srv_create(gacopyz_srv_t *p, const char *name, const char *portspec, unsigned logmask) { int i; gacopyz_srv_t srv = calloc(1, sizeof(*srv)); if (!srv) return MI_FAILURE; srv->id = strdup(name); srv->portspec = strdup(portspec); srv->iod.sd = -1; srv->iod.logmask = logmask; memcpy (srv->iod.timeout, default_gacopyz_timeout, sizeof srv->iod.timeout); srv->state = srv_init; srv->source_addr = INADDR_ANY; srv->onerror = SMFIR_TEMPFAIL; srv->version = SMFI_VERSION; srv->proto = SMFI_V2_PROT; srv->acts = SMFI_V2_ACTS; srv->memerror = default_memerror; *p = srv; return MI_SUCCESS; } static int parse_X_spec (const char *spec, char *xspec, char **id, char **port, struct timeval to[], int *onerr, int debug) { char *p, *q; int havef = 0, havet = 0; p = strtok(xspec, ","); if (!p) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec"), spec); return MI_FAILURE; } if (p[1] != '=') { *id = p; p = strtok(NULL, ","); if (!p) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec"), spec); return MI_FAILURE; } } do { while (*p && isspace(*p)) p++; if (!*p) break; if (p[1] != '=') { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "missing = near %s"), spec, p); return MI_FAILURE; } switch (p[0]) { case 'S': if (*port) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "duplicate field %s"), spec, p); return MI_FAILURE; } *port = p + 2; break; case 'F': if (havef) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "duplicate field %s"), spec, p); return MI_FAILURE; } havef = 1; if (strlen (p) != 3) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid field %s"), spec, p); return MI_FAILURE; } switch (p[2]) { case 'R': *onerr = SMFIR_REJECT; break; case 'T': *onerr = SMFIR_TEMPFAIL; break; default: if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid field %s"), spec, p); return MI_FAILURE; } break; case 'T': if (havet) { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "duplicate field %s"), spec, p); return MI_FAILURE; } havet = 1; for (q = p + 2; *q; q++) { int n; unsigned long t; char *ep; switch (*q) { case 'C': n = GACOPYZ_TO_CONNECT; break; case 'S': n = GACOPYZ_TO_WRITE; break; case 'R': n = GACOPYZ_TO_READ; break; case 'E': n = GACOPYZ_TO_EOM; break; default: if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid T field near %s"), spec, q); return MI_FAILURE; } if (*++q != ':') { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid T field near %s"), spec, q); return MI_FAILURE; } t = strtoul(++q, &ep, 0); switch (*ep) { case 0: case ';': break; case 's': ep++; break; case 'm': t *= 60; ep++; break; default: if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid T field near %s"), spec, q); return MI_FAILURE; } to[n].tv_sec = t; to[n].tv_usec = 0; q = ep; if (*q == 0) break; else if (*q != ';') { if (debug) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "invalid delimiter near %s"), spec, q); return MI_FAILURE; } } } } while (p = strtok(NULL, ",")); return MI_SUCCESS; } int gacopyz_srv_create_X(gacopyz_srv_t *p, const char *spec, unsigned logmask) { int rc; char *xspec = strdup(spec); char *id = "default"; char *port = NULL; struct timeval to[GACOPYZ_TO_COUNT]; int onerr = SMFIR_CONTINUE; memcpy(to, default_gacopyz_timeout, sizeof to); if (parse_X_spec(spec, xspec, &id, &port, to, &onerr, logmask & SMI_LOG_MASK(SMI_LOG_DEBUG)) == MI_FAILURE) { free(xspec); return MI_FAILURE; } if (!port) { if (logmask & SMI_LOG_MASK(SMI_LOG_DEBUG)) gacopyz_logmsg(SMI_LOG_DEBUG, _("%s: not a valid X spec, " "missing port"), spec); free(xspec); return MI_FAILURE; } rc = gacopyz_srv_create(p, id, port, logmask); free(xspec); if (rc) return MI_FAILURE; gacopyz_srv_set_all_timeouts(*p, to); gacopyz_srv_onerror(*p, onerr); return MI_SUCCESS; } int gacopyz_srv_onerror(gacopyz_srv_t srv, int code) { GACOPYZ_ASSERT(srv); switch (code) { case SMFIR_CONTINUE: case SMFIR_DISCARD: case SMFIR_REJECT: case SMFIR_TEMPFAIL: case SMFIR_SHUTDOWN: srv->onerror = code; return MI_SUCCESS; } return MI_FAILURE; } void gacopyz_srv_set_memerror(gacopyz_srv_t srv, void (*memerror)(gacopyz_srv_t, const char *, unsigned int)) { GACOPYZ_ASSERT(srv); srv->memerror = memerror; } void gacopyz_srv_set_all_timeouts(gacopyz_srv_t srv, struct timeval *tvp) { GACOPYZ_ASSERT(srv); memcpy(srv->iod.timeout, tvp, sizeof srv->iod.timeout); } int gacopyz_srv_set_timeout(gacopyz_srv_t srv, unsigned n, struct timeval *tvp) { GACOPYZ_ASSERT(srv); if (n >= GACOPYZ_TO_COUNT) return MI_FAILURE; srv->iod.timeout[n] = *tvp; return MI_SUCCESS; } int gacopyz_srv_set_timeout_sec(gacopyz_srv_t srv, unsigned n, time_t t) { struct timeval tv; tv.tv_usec = 0; tv.tv_sec = t; return gacopyz_srv_set_timeout(srv, n, &tv); } int gacopyz_srv_set_source(gacopyz_srv_t srv, unsigned long addr) { GACOPYZ_ASSERT(srv); srv->source_addr = htonl(addr); return MI_SUCCESS; } int gacopyz_srv_set_version(gacopyz_srv_t srv, int version) { GACOPYZ_ASSERT(srv); srv->version = version; return MI_SUCCESS; } int gacopyz_srv_set_protocol(gacopyz_srv_t srv, int proto) { GACOPYZ_ASSERT(srv); srv->proto = proto; return MI_SUCCESS; } int gacopyz_srv_set_actions(gacopyz_srv_t srv, int acts) { GACOPYZ_ASSERT(srv); srv->acts = acts; return MI_SUCCESS; } void gacopyz_srv_destroy(gacopyz_srv_t *p) { gacopyz_srv_t srv = *p; gacopyz_srv_clear_macros(srv); free(srv->portspec); free(srv->id); free(srv->def); free(srv->buf); free(srv); *p = 0; } static int srv_connect(gacopyz_srv_t srv, char *proto, char *port, char *path) { union { struct sockaddr sa; struct sockaddr_in s_in; struct sockaddr_un s_un; } addr; int socklen; int fd = -1; if (!proto || strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) { struct stat st; if (port) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Invalid connection type: %s; " "port is meaningless for UNIX sockets"), srv->portspec); return -1; } if (strlen(path) > sizeof addr.s_un.sun_path) { errno = EINVAL; gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("%s: UNIX socket name too long"), path); return -1; } addr.sa.sa_family = PF_UNIX; socklen = sizeof(addr.s_un); strcpy(addr.s_un.sun_path, path); if (stat(path, &st)) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("%s: cannot stat socket: %s"), path, strerror(errno)); return -1; } else { /* FIXME: Check permissions? */ if (!S_ISSOCK(st.st_mode)) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("%s: not a socket"), path); return -1; } } } else if (strcmp(proto, "inet") == 0) { short pnum; long num; char *p; addr.sa.sa_family = PF_INET; socklen = sizeof(addr.s_in); if (!port) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Invalid connection type: %s; " "missing port number"), srv->portspec); return -1; } num = pnum = strtol(port, &p, 0); if (*p == 0) { if (num != pnum) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Invalid connection type: %s; " "bad port number"), srv->portspec); return -1; } pnum = htons(pnum); } else { struct servent *sp = getservbyname(path, "tcp"); if (!sp) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Invalid connection type: %s; " "unknown port name"), srv->portspec); return -1; } pnum = sp->s_port; } if (!path) addr.s_in.sin_addr.s_addr = INADDR_ANY; else { struct hostent *hp = gethostbyname(path); if (!hp) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Unknown host name %s"), path); return -1; } addr.sa.sa_family = hp->h_addrtype; switch (hp->h_addrtype) { case AF_INET: memmove(&addr.s_in.sin_addr, hp->h_addr, 4); addr.s_in.sin_port = pnum; break; default: gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Invalid connection type: %s; " "unsupported address family"), srv->portspec); return -1; } } } else { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Unsupported protocol: %s"), proto); return -1; } fd = socket(addr.sa.sa_family, SOCK_STREAM, 0); if (fd == -1) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Unable to create new socket: %s"), strerror(errno)); return -1; } if (addr.sa.sa_family == PF_INET) { struct sockaddr_in s; s.sin_family = AF_INET; s.sin_addr.s_addr = srv->source_addr; s.sin_port = 0; if (bind (fd, (struct sockaddr*) &s, sizeof(s)) < 0) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Cannot bind to local address: %s"), strerror(errno)); close(fd); return -1; } } if (connect(fd, &addr.sa, socklen)) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Cannot connect to %s: %s"), srv->portspec, strerror(errno)); close(fd); return -1; } return fd; } int gacopyz_srv_connect(gacopyz_srv_t srv) { char *proto; char *port; char *path; int rc; GACOPYZ_ASSERT(srv); gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("Conecting to %s"), srv->portspec); GACOPYZ_ALLOC(srv, gacopyz_parse_connection(srv->portspec, &proto, &port, &path)==0); srv->iod.sd = srv_connect(srv, proto, port, path); free(proto); free(port); free(path); if (srv->iod.sd == -1) return MI_FAILURE; srv->state = srv_connected; return MI_SUCCESS; } int gacopyz_srv_negotiate(gacopyz_srv_t srv) { gacopyz_uint32_t ibuf[3]; size_t size; unsigned char cmd; GACOPYZ_ASSERT(srv); ibuf[0] = htonl(srv->version); ibuf[1] = htonl(srv->acts); ibuf[2] = htonl(srv->proto); if (gacopyz_send_command(&srv->iod, SMFIC_OPTNEG, ibuf, sizeof ibuf)) return MI_FAILURE; if (gacopyz_read_command(&srv->iod, &cmd, &size, &srv->buf, &srv->bufsize)) return MI_FAILURE; if (cmd != SMFIC_OPTNEG) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("gacopyz_srv_negotiate: got %c instead of %c"), cmd, SMFIC_OPTNEG); return MI_FAILURE; } if (size < sizeof ibuf) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("gacopyz_srv_negotiate: not enough data returned")); return MI_FAILURE; } ibuf[0] = ntohl(((gacopyz_uint32_t*)srv->buf)[0]); ibuf[1] = ntohl(((gacopyz_uint32_t*)srv->buf)[1]); ibuf[2] = ntohl(((gacopyz_uint32_t*)srv->buf)[2]); /* Check version */ if (ibuf[0] == 1 || ibuf[0] > SMFI_VERSION) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Client requested unsupported milter version: " "%d"), ibuf[0]); return MI_FAILURE; } /* Check action capabilities */ if ((ibuf[1] & SMFI_DEFAULT_ACTS) != ibuf[1]) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Filter abilities %#x do not match MTA " "milter abilities %#lx"), ibuf[1], SMFI_DEFAULT_ACTS); return MI_FAILURE; } /* Check protocol */ if ((ibuf[2] & SMFI_DEFAULT_PROT) != ibuf[2]) { gacopyz_io_log(&srv->iod, SMI_LOG_ERR, _("Protocol abilities %#x do not match MTA " "milter abilities %#lx"), ibuf[1], SMFI_DEFAULT_PROT); return MI_FAILURE; } srv->version = ibuf[0]; srv->acts = ibuf[1]; srv->proto = ibuf[2]; srv->state = srv_ready; return MI_SUCCESS; } static struct command_assoc { unsigned char cmd; int pflag; const char *action; const char *code; } command_tab[] = { { SMFIC_CONNECT, SMFIP_NOCONNECT, "connect", "554" }, { SMFIC_HELO, SMFIP_NOHELO, "helo", "550" }, { SMFIC_MAIL, SMFIP_NOMAIL, "mail", "550 5.7.1" }, { SMFIC_RCPT, SMFIP_NORCPT, "rcpt", "550 5.7.1" }, { SMFIC_HEADER, SMFIP_NOHDRS, "header", "550 5.7.1" }, { SMFIC_BODY, SMFIP_NOBODY, "body", "554 5.7.1" }, { SMFIC_EOH, SMFIP_NOEOH, "eoh", "550 5.7.1" }, { SMFIC_UNKNOWN, SMFIP_NOUNKNOWN, "unknown", "550 5.7.1" }, { SMFIC_DATA, SMFIP_NODATA, "data", "550 5.7.1" }, { 0, 0, "default", "550 5.7.1" } }; static struct command_assoc * find_cmd_assoc(unsigned char cmd) { struct command_assoc *cp; for (cp = command_tab; cp->cmd; cp++) if (cp->cmd == cmd) break; return cp; } #define is_rfc822_reject_code(s) \ (((s)[0] == '4' || (s)[0] == '5') \ && isascii((s)[1]) && isdigit((s)[1]) \ && isascii((s)[2]) && isdigit((s)[2])) #define DEFREPLY "Command rejected" static void verify_reply(gacopyz_srv_t srv, size_t size, const char *reject_code) { if (size < 3 || !is_rfc822_reject_code(srv->buf)) { srv_ensure_space(srv, strlen(reject_code) + 1 + sizeof(DEFREPLY)); strcpy(srv->buf, reject_code); strcat(srv->buf, " "); strcat(srv->buf, DEFREPLY); } } int gacopyz_srv_send_macros(gacopyz_srv_t srv, unsigned char cmd) { size_t size; srv_format_macros(srv, cmd, &size); if (gacopyz_send_command(&srv->iod, SMFIC_MACRO, srv->buf, size)) return MI_FAILURE; return MI_SUCCESS; } int gacopyz_srv_send_command(gacopyz_srv_t srv, unsigned char cmd, const void *data, size_t datasize) { unsigned char rcmd; size_t size; struct command_assoc *cp; gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("gacopyz_srv_send_command: size=%lu, cmd=%c"), datasize, cmd); cp = find_cmd_assoc(cmd); srv->resp_ptr = NULL; srv->resp_size = 0; if (cp->pflag && (srv->proto & cp->pflag)) { if (cmd == SMFIC_MAIL) srv->state = srv_msgproc; return SMFIR_CONTINUE; } if (gacopyz_srv_send_macros(srv, cmd)) return srv->onerror; if (gacopyz_send_command(&srv->iod, cmd, data, datasize)) return srv->onerror; if (cmd == SMFIC_HEADER && (srv->proto & SMFIP_NOHREPL)) return SMFIR_CONTINUE; if (gacopyz_read_command(&srv->iod, &rcmd, &size, &srv->buf, &srv->bufsize)) return srv->onerror; switch (rcmd) { case SMFIR_REPLYCODE: verify_reply(srv, size, cp->code); gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, reject=%s", cp->action, srv->buf); srv->resp_ptr = srv->buf; srv->resp_size = size; srv->state = srv_disabled; break; case SMFIR_REJECT: gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, reject", cp->action); break; case SMFIR_DISCARD: gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, discard", cp->action); srv->state = srv_disabled; break; case SMFIR_TEMPFAIL: gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, tempfail", cp->action); break; case SMFIR_ACCEPT: gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, accept", cp->action); srv->state = srv_disabled; break; case SMFIR_CONTINUE: gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "action=%s, continue", cp->action); if (cmd == SMFIC_MAIL) srv->state = srv_msgproc; break; default: gacopyz_io_log(&srv->iod, SMI_LOG_ERR, "action=%s, bogus response=%c", cp->action, rcmd); /*FIXME: Change to error state */ srv->state = srv_error; rcmd = srv->onerror; break; } return rcmd; } void gacopyz_srv_reply (gacopyz_srv_t srv, char **msg, size_t *size) { *msg = srv->resp_ptr; *size = srv->resp_size; } int gacopyz_srv_abort(gacopyz_srv_t srv) { GACOPYZ_ASSERT(srv); gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "gacopyz_srv_abort"); return gacopyz_send_command(&srv->iod, SMFIC_ABORT, NULL, 0); } int gacopyz_srv_quit(gacopyz_srv_t srv) { GACOPYZ_ASSERT(srv); gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, "gacopyz_srv_quit"); return gacopyz_send_command(&srv->iod, SMFIC_QUIT, NULL, 0); } int gacopyz_srv_helo(gacopyz_srv_t srv, const char *domain) { int rc; GACOPYZ_ASSERT(srv); if (srv->proto & SMFIP_NOHELO) rc = SMFIR_CONTINUE; else rc = gacopyz_srv_send_command(srv, SMFIC_HELO, domain, strlen(domain) + 1); return rc; } static void format_argv(gacopyz_srv_t srv, char **argv, char **buf, size_t *psize) { size_t len; char **ap; char *p; for (len = 1, ap = argv; *ap; ap++) len += strlen(*ap) + 1; GACOPYZ_ALLOC(srv, *buf = malloc(len)); for (p = *buf, ap = argv; *ap; ap++) { size_t len = strlen(*ap) + 1; memcpy(p, *ap, len); p += len; } *p = 0; *psize = len; } int gacopyz_srv_envfrom(gacopyz_srv_t srv, char **argv) { int rc; GACOPYZ_ASSERT(srv); if (srv->state != srv_ready) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_envfrom", srv->state); rc = SMFIR_CONTINUE; } else { size_t size; char *buf; format_argv(srv, argv, &buf, &size); rc = gacopyz_srv_send_command(srv, SMFIC_MAIL, buf, size); free (buf); } return rc; } int gacopyz_srv_envrcpt(gacopyz_srv_t srv, char **argv) { int rc; GACOPYZ_ASSERT(srv); if (srv->state != srv_msgproc) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_envrcpt", srv->state); rc = SMFIR_CONTINUE; } else { size_t size; char *buf; format_argv(srv, argv, &buf, &size); rc = gacopyz_srv_send_command(srv, SMFIC_RCPT, buf, size); free (buf); } return rc; } int gacopyz_srv_header(gacopyz_srv_t srv, char *name, char *value) { int rc; GACOPYZ_ASSERT(srv && name && value); if (srv->state != srv_msgproc) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_header", srv->state); rc = SMFIR_CONTINUE; } else { size_t nlen, vlen, size; char *buf; nlen = strlen (name); vlen = strlen (value); size = nlen + vlen + 2; GACOPYZ_ALLOC(srv, buf = malloc (size)); memcpy (buf, name, nlen + 1); memcpy (buf + nlen + 1, value, vlen + 1); rc = gacopyz_srv_send_command(srv, SMFIC_HEADER, buf, size); free (buf); } return rc; } int gacopyz_srv_eoh(gacopyz_srv_t srv) { int rc; GACOPYZ_ASSERT(srv); if (srv->state != srv_msgproc) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_eoh", srv->state); rc = SMFIR_CONTINUE; } else rc = gacopyz_srv_send_command(srv, SMFIC_EOH, NULL, 0); return rc; } int gacopyz_srv_body(gacopyz_srv_t srv, unsigned char *str, size_t size) { int rc; GACOPYZ_ASSERT(srv); if (srv->state != srv_msgproc) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_body", srv->state); rc = SMFIR_CONTINUE; } else rc = gacopyz_srv_send_command(srv, SMFIC_BODY, str, size); return rc; } int gacopyz_srv_eom(gacopyz_srv_t srv, unsigned char *chunk, size_t size) { GACOPYZ_ASSERT(srv); if (srv->state != srv_msgproc) { gacopyz_io_log(&srv->iod, SMI_LOG_DEBUG, _("%s: ignoring request; called in wrong state (%d)"), "gacopyz_srv_body", srv->state); srv->state = srv_done; return SMFIR_CONTINUE; } return gacopyz_srv_send_command(srv, SMFIC_BODYEOB, chunk, size); } int gacopyz_srv_close(gacopyz_srv_t srv) { GACOPYZ_ASSERT(srv); close (srv->iod.sd); srv->state = srv_init; return MI_SUCCESS; } int gacopyz_srv_data(gacopyz_srv_t srv) { GACOPYZ_ASSERT(srv); return gacopyz_srv_send_command(srv, SMFIC_DATA, NULL, 0); }