/* Distributed Checksum Clearinghouse * * dccif(), a sample C interface to the DCC interface daemon, dccifd * * --C-LICENSE-- * $Revision: 1.41 $ */ #include "dcc_defs.h" #include "dccif.h" #include "dcc_clnt.h" #include "sys/uio.h" #include #ifndef DCC_WIN32 #include #endif static void dccif_closes(int *fd1, int *fd2, int *fd3) { if (fd1 && *fd1 >= 0) { close(*fd1); *fd1 = -1; } if (fd2 && *fd2 >= 0) { close(*fd2); *fd2 = -1; } if (fd3 && *fd3 >= 0) { close(*fd3); *fd3 = -1; } } static void DCC_PF(6,7) dccif_pemsg(int ex_code, DCC_EMSG *emsg, int *fd1, int *fd2, int *fd3, const char *msg, ...) { va_list args; if (emsg) { va_start(args, msg); dcc_vpemsg(ex_code, emsg, msg, args); va_end(args); } dccif_closes(fd1, fd2, fd3); } static u_char dccif_sock(DCC_EMSG *emsg, const char *srvr_addr, /* home directory, FIFO, or host,port */ DCC_SOCKET *s, int* fd2, int* fd3) { struct stat sb; char host[DCC_MAXDOMAINLEN]; in_port_t port; DCC_SOCKU su; struct sockaddr_un sunsock; const char *cp; int error; /* srvr_addr should be a hostname,port if not a file or directory */ if (0 > stat(srvr_addr, &sb)) { if (!strchr(srvr_addr, ',')) { dccif_pemsg(EX_IOERR, emsg, s, fd2, fd3, "stat(%s): %s", srvr_addr, ERROR_STR()); return 0; } cp = dcc_parse_nm_port(emsg, srvr_addr, DCC_GET_PORT_INVALID, host, sizeof(host), &port, 0, 0, 0, 0); if (!cp) { dccif_closes(0, fd2, fd3); return 0; } if (*cp != '\0') { dccif_pemsg(EX_USAGE, emsg, 0, fd2, fd3, "invalid IP address: %s", srvr_addr); return 0; } if (host[0] == '\0') { dccif_pemsg(EX_NOHOST, emsg, 0, fd2, fd3, "missing host name in \"%s\"", srvr_addr); return 0; } dcc_host_lock(); if (!dcc_get_host(host, DCC_GETH_DEF, &error)) { dcc_host_unlock(); dccif_pemsg(EX_NOHOST, emsg, 0, fd2, fd3, "%s: %s", srvr_addr, H_ERROR_STR1(error)); return 0; } su = dcc_hostaddrs[0]; DCC_SU_PORT(&su) = port; dcc_host_unlock(); *s = socket(su.sa.sa_family, SOCK_STREAM, 0); if (*s < 0) { dccif_pemsg(EX_IOERR, emsg, s, fd2, fd3, "socket(AF_UNIX): %s", ERROR_STR()); return 0; } if (0 > connect(*s, &su.sa, DCC_SU_LEN(&su))) { dccif_pemsg(EX_IOERR, emsg, s, fd2, fd3, "connect(%s): %s", srvr_addr, ERROR_STR()); return 0; } return 1; } *s = socket(AF_UNIX, SOCK_STREAM, 0); if (*s < 0) { dccif_pemsg(EX_IOERR, emsg, s, fd2, fd3, "socket(AF_UNIX): %s", ERROR_STR()); return 0; } memset(&sunsock, 0, sizeof(sunsock)); sunsock.sun_family = AF_UNIX; if (S_ISDIR(sb.st_mode)) snprintf(sunsock.sun_path, sizeof(sunsock.sun_path), "%s/"DCC_DCCIF_UDS, srvr_addr); else BUFCPY(sunsock.sun_path, srvr_addr); #ifdef HAVE_SA_LEN sunsock.sun_len = SUN_LEN(&sunsock); #endif if (0 > connect(*s, (struct sockaddr *)&sunsock, sizeof(sunsock))) { dccif_pemsg(EX_IOERR, emsg, s, fd2, fd3, "connect(%s): %s", sunsock.sun_path, ERROR_STR()); return 0; } return 1; } static u_char dccif_writev(DCC_EMSG *emsg, int *out_fd, int *fd2, int *fd3, const struct iovec *iovs, int num_iovs, int wtotal) { int i; i = writev(*out_fd, iovs, num_iovs); if (i == wtotal) return 1; if (i < 0) dccif_pemsg(EX_IOERR, emsg, out_fd, fd2, fd3, "dccif writev(%d): %s", wtotal, ERROR_STR()); else dccif_pemsg(EX_IOERR, emsg, out_fd, fd2, fd3, "dccif writev(%d)=%d", wtotal, i); return 0; } static u_char dccif_write(DCC_EMSG *emsg, int *out_fd, int *fd2, int *fd3, const void *buf, int buf_len) { int i; i = write(*out_fd, buf, buf_len); if (i == buf_len) return 1; if (i < 0) dccif_pemsg(EX_IOERR, emsg, out_fd, fd2, fd3, "dccif writev(%d): %s", buf_len, ERROR_STR()); else dccif_pemsg(EX_IOERR, emsg, out_fd, fd2, fd3, "dccif writev(%d)=%d", buf_len, i); return 0; } static int dccif_read(DCC_EMSG *emsg, int *in_fd, int *fd2, int *fd3, char *buf, int buf_len, int min_read) { int total, i; total = 0; for (;;) { i = read(*in_fd, &buf[total], buf_len-total); if (i < 0) { dccif_pemsg(EX_IOERR, emsg, in_fd, fd2, fd3, "dccif read(): %s", ERROR_STR()); return -1; } total += i; if (total >= min_read) return total; if (i == 0) { dccif_pemsg(EX_IOERR, emsg, in_fd, fd2, fd3, "dccif read(): premature EOF"); return -1; } } } /* This function is mentioned in dccifd/dccif-test/dccif-test.c * and so cannot change lightly. */ u_char /* 0=failed or DCCIF_RESULT_* */ dccif(DCC_EMSG *emsg, /* put error message here */ int out_body_fd, /* -1 or write body to here */ char **out_body, /* 0 or pointer for resulting body */ const char *opts, /* blank separated string DCCIF_OPT_* */ const DCC_SOCKU *clnt_addr, /* SMTP client IPv4 or IPv6 address */ const char *clnt_name, /* optional SMTP client name */ const char *helo, const char *env_from, /* envelope sender */ DCCIF_RCPT *rcpts, /* pruned envelope recipients */ int in_body_fd, /* -1 or read body from here */ const char *in_body, /* 0 or start of incoming body */ const char *srvr_addr) /* home directory, FIFO, or host,port */ { static const char nl = '\n'; static const char cr = '\r'; int s; char clnt_str[INET6_ADDRSTRLEN+1+1]; struct iovec iovs[50], *iovp; int wtotal; # define ADD_IOV(b,l) {int _l = (l); iovp->iov_base = (char *)(b); \ iovp->iov_len = _l; ++iovp; wtotal += _l;} DCCIF_RCPT *rcpt; char buf[4*1024]; char result; int read_len, in_body_len, max_body_len, nxt; int i, j; if (!srvr_addr || *srvr_addr == '\0') srvr_addr = DCC_HOMEDIR; if (emsg) emsg->c[0] = '\0'; if (!clnt_addr) { clnt_str[0] = '\0'; } else { dcc_su2str2(clnt_str, sizeof(clnt_str), clnt_addr); } if (!dccif_sock(emsg, srvr_addr, &s, &in_body_fd, &out_body_fd)) return 0; /* first line of request */ iovp = iovs; wtotal = 0; if (opts) ADD_IOV(opts, strlen(opts)); ADD_IOV(&nl, 1); i = strlen(clnt_str); clnt_str[i++] = '\r'; ADD_IOV(clnt_str, i); if (i > 1 && clnt_name) ADD_IOV(clnt_name, strlen(clnt_name)); ADD_IOV(&nl, 1); if (helo) ADD_IOV(helo, strlen(helo)); ADD_IOV(&nl, 1); if (env_from) ADD_IOV(env_from, strlen(env_from)); ADD_IOV(&nl, 1); for (rcpt = rcpts; rcpt; rcpt = rcpt->next) { ADD_IOV(rcpt->addr, strlen(rcpt->addr)); ADD_IOV(&cr, 1); if (rcpt->user) ADD_IOV(rcpt->user, strlen(rcpt->user)); ADD_IOV(&nl, 1); if (iovp >= &iovs[DIM(iovs)-4-1]) { if (!dccif_writev(emsg, &s, &in_body_fd, &out_body_fd, iovs, iovp - iovs, wtotal)) return 0; iovp = iovs; wtotal = 0; } } ADD_IOV(&nl, 1); /* copy (some of) the body from the buffer to the daemon */ if (in_body) { in_body_len = strlen(in_body); ADD_IOV(in_body, in_body_len); } else { in_body_len = 0; } if (!dccif_writev(emsg, &s, &in_body_fd, &out_body_fd, iovs, iovp - iovs, wtotal)) return 0; /* copy (the rest of) the body from input file to the daemon */ if (in_body_fd >= 0) { for (;;) { i = read(in_body_fd, buf, sizeof(buf)); if (i <= 0) { if (i == 0) break; dccif_pemsg(EX_IOERR, emsg, &s, &in_body_fd, &out_body_fd, "read(body): %s", ERROR_STR()); return 0; } if (!dccif_write(emsg, &s, &in_body_fd, &out_body_fd, buf, i)) return 0; in_body_len += i; } if (0 > close(in_body_fd)) { dccif_pemsg(EX_IOERR, emsg, &s, &in_body_fd, &out_body_fd, "close(in_body_fd): %s", ERROR_STR()); return 0; } in_body_fd = -1; } /* tell the daemon it has all of the body */ if (0 > shutdown(s, 1)) { dccif_pemsg(EX_IOERR, emsg, &s, &in_body_fd, &out_body_fd, "shutdown(): %s", ERROR_STR()); return 0; } /* get the overall result */ read_len = dccif_read(emsg, &s, &in_body_fd, &out_body_fd, buf, sizeof(buf), 2); if (read_len < 0) return 0; result = buf[0]; if (result != DCCIF_RESULT_OK && result != DCCIF_RESULT_GREY && result != DCCIF_RESULT_REJECT && result != DCCIF_RESULT_SOME && result != DCCIF_RESULT_TEMP) { dccif_pemsg(EX_SOFTWARE, emsg, &s, &in_body_fd, &out_body_fd, "unrecognized dccifd result \"%.*s\"", 1, buf); return 0; } /* read the vector of results from the daemon */ nxt = 1; /* skip '\n' after result */ for (rcpt = rcpts; rcpt != 0; rcpt = rcpt->next) { if (++nxt >= read_len) { read_len = dccif_read(emsg, &s, &in_body_fd, &out_body_fd, buf, sizeof(buf), 2); if (read_len < 0) return 0; nxt = 0; } switch (buf[nxt]) { case DCCIF_RCPT_ACCEPT: case DCCIF_RCPT_REJECT: case DCCIF_RCPT_GREY: rcpt->ok = buf[nxt]; break; default: dccif_pemsg(EX_SOFTWARE, emsg, &s, &in_body_fd, &out_body_fd, "unrecognized dccifd recipient result" " \"%c\" for %s", buf[nxt], rcpt->addr); return 0; } } if (++nxt >= read_len) { read_len = dccif_read(emsg, &s, &in_body_fd, &out_body_fd, buf, 1, 1); if (read_len < 0) return 0; nxt = 0; } if (buf[nxt++] != '\n') { dccif_pemsg(EX_SOFTWARE, emsg, &s, &in_body_fd, &out_body_fd, "unrecognized dccifd text after results: \"%c\"", buf[nxt]); return 0; } /* copy the body from the daemon to the output buffer for file */ if (out_body_fd >= 0) { for (;;) { j = read_len - nxt; if (j <= 0) { j = dccif_read(emsg, &s, &in_body_fd, &out_body_fd, buf, sizeof(buf), 0); if (j < 0) return 0; if (j == 0) break; read_len = j; nxt = 0; } if (!dccif_write(emsg, &out_body_fd, &s, &in_body_fd, &buf[nxt], j)) return 0; read_len = 0; } if (0 > close(out_body_fd)) { dccif_pemsg(EX_IOERR, emsg, &s, &in_body_fd, &out_body_fd, "close(out_body_fd): %s", ERROR_STR()); return 0; } out_body_fd = -1; } else if (out_body) { char *body; max_body_len = in_body_len + DCC_MAX_XHDR_LEN+1; body = malloc(max_body_len); *out_body = body; j = read_len - nxt; if (j > 0) { if (j > max_body_len) j = max_body_len; memcpy(body, &buf[nxt], j); body += j; max_body_len -= j; } for (;;) { if (max_body_len <= 0) { dccif_pemsg(EX_SOFTWARE, emsg, &s, &in_body_fd, &out_body_fd, "too much body from dccifd"); free(*out_body); return 0; } j = dccif_read(emsg, &s, &in_body_fd, &out_body_fd, body, max_body_len, 0); if (j < 0) { free(*out_body); return 0; } if (j == 0) break; body += j; max_body_len -= j; } *body = '\0'; } if (0 > close(s)) { dccif_pemsg(EX_IOERR, emsg, &s, &in_body_fd, &out_body_fd, "close(socket): %s", ERROR_STR()); return 0; } return result; #undef ADD_IOV }