From f5352ff057d3b7a0a558334dd1b14ff4ff7c1aef Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sat, 29 Nov 2003 23:47:33 +0000 Subject: [PATCH] Debianization. Organization: Straylight/Edgeware From: mdw --- .cvsignore | 1 + Makefile | 43 +++ config.h | 159 ++++++++ debian/.cvsignore | 3 + debian/changelog | 5 + debian/control | 18 + debian/copyright | 13 + debian/rules | 51 +++ libspamc.c | 919 ++++++++++++++++++++++++++++++++++++++++++++++ libspamc.h | 131 +++++++ utils.c | 144 ++++++++ utils.h | 24 ++ 12 files changed, 1511 insertions(+) create mode 100644 .cvsignore create mode 100644 Makefile create mode 100644 config.h create mode 100644 debian/.cvsignore create mode 100644 debian/changelog create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 libspamc.c create mode 100644 libspamc.h create mode 100644 utils.c create mode 100644 utils.h diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..d4672c5 --- /dev/null +++ b/.cvsignore @@ -0,0 +1 @@ +build libspamc.so.1.0.0 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2d0f5f1 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +## Throwaway makefile for libspamc + +CC = gcc +CFLAGS = -O2 -fPIC -DSPAMC_SSL +LIBS = -lssl + +MAJOR = 1 +MINOR = 0 +PATCH = 0 +VER = $(MAJOR).$(MINOR).$(PATCH) +BASE = libspamc +SO = $(BASE).so.$(MAJOR) +LD = gcc +FULL = $(BASE).so.$(VER) +LDLINK = $(BASE).so + +.SUFFIXES = .c .o +.c.o:; $(CC) -c $(CFLAGS) -o $@ $^ + +INST = +prefix = /usr/local +libdir = $(prefix)/lib +includedir = $(prefix)/include + +all: $(FULL) +clean:; rm -f *.o $(FULL) +install: all + mkdir -p $(INST)$(includedir) + install -m644 libspamc.h $(INST)$(includedir)/libspamc.h + mkdir -p $(INST)$(libdir) + install -m644 $(FULL) $(INST)$(libdir)/$(FULL) + ln -s -f $(FULL) $(INST)$(libdir)/$(SO) + ln -s -f $(FULL) $(INST)$(libdir)/$(LDLINK) +uninstall: + rm -f $(INST)$(includedir)/libspamc.h + rm -f $(INST)$(libdir)/$(FULL) + rm -f $(INST)$(libdir)/$(SO) + rm -f $(INST)$(libdir)/$(LDLINK) + +$(FULL): libspamc.o utils.o + $(LD) -Wl,-soname,$(SO) -shared -o $@ $^ $(LIBS) + +.PHONY: all clean install diff --git a/config.h b/config.h new file mode 100644 index 0000000..ca2ff58 --- /dev/null +++ b/config.h @@ -0,0 +1,159 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.in by autoheader. */ + +/* #undef CCDLFLAGS */ + +/* #undef LDDLFLAGS */ + +#define HAVE_SHUT_RD 1 + +#define HAVE_H_ERRNO 1 + +#define HAVE_OPTARG 1 + +/* #undef in_addr_t */ + +#define HAVE_INADDR_NONE 1 + +#define HAVE_EX__MAX 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GETOPT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#define HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `dl' library (-ldl). */ +#define HAVE_LIBDL 1 + +/* Define to 1 if you have the `inet' library (-linet). */ +/* #undef HAVE_LIBINET */ + +/* Define to 1 if you have the `nsl' library (-lnsl). */ +/* #undef HAVE_LIBNSL */ + +/* Define to 1 if you have the `socket' library (-lsocket). */ +/* #undef HAVE_LIBSOCKET */ + +/* Define to 1 if you have the `ssl' library (-lssl). */ +#define HAVE_LIBSSL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETDB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_CRYPTO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if you have the `shutdown' function. */ +#define HAVE_SHUTDOWN 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `socket' function. */ +#define HAVE_SOCKET 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the `strdup' function. */ +#define HAVE_STRDUP 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the `strtod' function. */ +#define HAVE_STRTOD 1 + +/* Define to 1 if you have the `strtol' function. */ +#define HAVE_STRTOL 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSEXITS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYSLOG_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "" + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `int' if doesn't define. */ +/* #undef gid_t */ + +/* Define to `unsigned long' if does not define. */ +/* #undef in_addr_t */ + +/* Define to `long' if does not define. */ +/* #undef off_t */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned' if does not define. */ +/* #undef size_t */ + +/* Define to `int' if doesn't define. */ +/* #undef uid_t */ diff --git a/debian/.cvsignore b/debian/.cvsignore new file mode 100644 index 0000000..064a21d --- /dev/null +++ b/debian/.cvsignore @@ -0,0 +1,3 @@ +libspamc libspamc-dev libspamc1 +*.debhelper *.substvars +files diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..308931d --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +libspamc (2.55-1) experimental; urgency=low + + * Debianized. + + -- Mark Wooding Wed, 12 Nov 2003 18:25:55 +0000 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..2a6c059 --- /dev/null +++ b/debian/control @@ -0,0 +1,18 @@ +Source: libspamc +Standards-Version: 3.1.1 +Section: libs +Priority: extra +Build-Depends: debhelper (>= 4.0.2) +Maintainer: Mark Wooding + +Package: libspamc1 +Architecture: any +Depends: ${shlibs:Depends} +Description: Client library for spamassassin + Just the shared library here. + +Package: libspamc-dev +Architecture: any +Depends: libspamc1 (= ${Source-Version}) +Description: Client library for spamassassin + Header and static library. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e475625 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,13 @@ +The source code comes from Mail::SpamAssassin-2.55.tar.gz, available from +http://www.spamassassin.org/. This package is a stopgap until the Debian +spamassassin package provides libspamc. The original COPYRIGHT file from +SpamAssassin says this: + +The code in the files of the SpamAssassin distribution are Copyright +2000-2002 Justin Mason and others, unless specified otherwise in that +particular file. + +All files in the SpamAssassin distribution fall under the same terms +as Perl itself, as described in the file named "License". + +Debianiazation by Mark Wooding. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..969571d --- /dev/null +++ b/debian/rules @@ -0,0 +1,51 @@ +#! /usr/bin/make -f + +export DH_COMPAT = 4 + +VER = 2.55 + +build: + make + touch build + +clean: + dh_clean + rm -f *.o *.so *.so.* build + +install: build + dh_clean + make install INST=debian/libspamc1 prefix=/usr + mkdir -p debian/libspamc-dev/usr/lib + mv debian/libspamc1/usr/include debian/libspamc-dev/usr + mv debian/libspamc1/usr/lib/*.so debian/libspamc-dev/usr/lib + +binary-indep: + +binary-arch: install + dh_testdir -a + dh_testroot -a + dh_makeshlibs -a -V + dh_installman -a + dh_compress -a + dh_installdocs -a + dh_strip -a + dh_shlibdeps -a + dh_gencontrol -a + dh_fixperms -a + dh_installdeb -a + dh_md5sums -a + dh_builddeb -a + +binary: binary-indep binary-arch + +source: + rm -rf =deb= + mkdir -p =deb=/libspamc-$(VER) + cp *.[ch] =deb=/libspamc-$(VER) + mkdir =deb=/libspamc-$(VER)/debian + for i in copyright rules changelog control; do \ + cp debian/$$i =deb=/libspamc-$(VER)/debian; done + d=`pwd`; cd ..; dpkg-source -b -i $$d/=deb=/libspamc-$(VER) + rm -rf =deb= + +.PHONY: binary binary-arch binary-indep clean install source diff --git a/libspamc.c b/libspamc.c new file mode 100644 index 0000000..5d5f988 --- /dev/null +++ b/libspamc.c @@ -0,0 +1,919 @@ +/* + * This code is copyright 2001 by Craig Hughes + * Portions copyright 2002 by Brad Jorsch + * It is licensed under the same license as Perl itself. The text of this + * license is included in the SpamAssassin distribution in the file named + * "License". + */ + +#include "config.h" +#include "libspamc.h" +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYSEXITS_H +#include +#endif +#ifdef HAVE_ERRNO_H +#include +#endif +#ifdef HAVE_SYS_ERRNO_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif + +#define MAX_CONNECT_RETRIES 3 +#define CONNECT_RETRY_SLEEP 1 + +/* RedHat 5.2 doesn't define Shutdown 2nd Parameter Constants */ +/* KAM 12-4-01 */ +#ifndef HAVE_SHUT_RD +#define SHUT_RD (0) /* No more receptions. */ +#define SHUT_WR (1) /* No more transmissions. */ +#define SHUT_RDWR (2) /* No more receptions or transmissions. */ +#endif + +#ifndef HAVE_H_ERRNO +#define h_errno errno +#endif + +#ifndef HAVE_OPTARG +extern char *optarg; +#endif + +#ifndef HAVE_INADDR_NONE +#define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +/* jm: turned off for now, it should not be necessary. */ +#undef USE_TCP_NODELAY + +#ifndef HAVE_EX__MAX +/* jm: very conservative figure, should be well out of range on almost all NIXes */ +#define EX__MAX 200 +#endif + +#undef DO_CONNECT_DEBUG_SYSLOGS +/* or #define DO_CONNECT_DEBUG_SYSLOGS 1 */ + +static const int ESC_PASSTHROUGHRAW = EX__MAX+666; + +/* set EXPANSION_ALLOWANCE to something more than might be + added to a message in X-headers and the report template */ +static const int EXPANSION_ALLOWANCE = 16384; + +/* set NUM_CHECK_BYTES to number of bytes that have to match at beginning and end + of the data streams before and after processing by spamd + Aug 7 2002 jm: no longer seems to be used + static const int NUM_CHECK_BYTES = 32; + */ + +/* Set the protocol version that this spamc speaks */ +static const char *PROTOCOL_VERSION="SPAMC/1.3"; + +int libspamc_timeout = 0; + +static int +try_to_connect (const struct sockaddr *argaddr, struct hostent *hent, + int hent_port, int *sockptr) +{ +#ifdef USE_TCP_NODELAY + int value; +#endif + int mysock = -1; + int status = -1; + int origerr; + int numloops; + int hostnum = 0; + struct sockaddr_in addrbuf, *addr; + struct in_addr inaddrlist[256]; + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + int dbgiter; char dbgbuf[2048]; int dbgbuflen = 0; +#endif + + /* NOTE: do not call syslog() (unless you are about to return) before + * we take a copy of the h_addr_list. + */ + + /* only one set of connection targets can be used. assert this */ + if (argaddr == NULL && hent == NULL) { + syslog (LOG_ERR, "oops! both NULL in try_to_connect"); + return EX_SOFTWARE; + } else if (argaddr != NULL && hent != NULL) { + syslog (LOG_ERR, "oops! both non-NULL in try_to_connect"); + return EX_SOFTWARE; + } + + /* take a copy of the h_addr_list part of the struct hostent */ + if (hent != NULL) { + memset (inaddrlist, 0, sizeof(inaddrlist)); + + for (hostnum=0; hent->h_addr_list[hostnum] != 0; hostnum++) { + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen, + "[%d %lx: %d.%d.%d.%d]", + hostnum, hent->h_addr_list[hostnum], + hent->h_addr_list[hostnum][0], + hent->h_addr_list[hostnum][1], + hent->h_addr_list[hostnum][2], + hent->h_addr_list[hostnum][3]); +#endif + + if (hostnum > 255) { + syslog (LOG_ERR, "too many address in hostent (%d), ignoring others", + hostnum); + break; + } + + if (hent->h_addr_list[hostnum] == NULL) { + /* shouldn't happen */ + syslog (LOG_ERR, "hent->h_addr_list[hostnum] == NULL! foo!"); + return EX_SOFTWARE; + } + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + dbgbuflen += snprintf (dbgbuf+dbgbuflen, 2047-dbgbuflen, + "[%d: %d.%d.%d.%d] ", sizeof (struct in_addr), + hent->h_addr_list[hostnum][0], + hent->h_addr_list[hostnum][1], + hent->h_addr_list[hostnum][2], + hent->h_addr_list[hostnum][3]); +#endif + + memcpy ((void *) &(inaddrlist[hostnum]), + (void *) hent->h_addr_list[hostnum], + sizeof (struct in_addr)); + } + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: %d %s", hostnum, dbgbuf); dbgbuflen = 0; +#endif + } + + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + for (dbgiter = 0; dbgiter < hostnum; dbgiter++) { + syslog (LOG_DEBUG, "dbg: host addr %d/%d = %lx at %lx", dbgiter, hostnum, + inaddrlist[dbgiter].s_addr, &(inaddrlist[dbgiter])); + } +#endif + + hent = NULL; /* cannot use hent after this point, syslog() may overwrite it */ + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: socket"); +#endif + + if(-1 == (mysock = socket(PF_INET,SOCK_STREAM,0))) + { + origerr = errno; /* take a copy before syslog() */ + syslog (LOG_ERR, "socket() to spamd failed: %m"); + switch(origerr) + { + case EPROTONOSUPPORT: + case EINVAL: + return EX_SOFTWARE; + case EACCES: + return EX_NOPERM; + case ENFILE: + case EMFILE: + case ENOBUFS: + case ENOMEM: + return EX_OSERR; + default: + return EX_SOFTWARE; + } + } + +#ifdef USE_TCP_NODELAY + /* TODO: should this be up above the connect()? */ + value = 1; /* make this explicit! */ + if(-1 == setsockopt(mysock,0,TCP_NODELAY,&value,sizeof(value))) + { + switch(errno) + { + case EBADF: + case ENOTSOCK: + case ENOPROTOOPT: + case EFAULT: + syslog (LOG_ERR, "setsockopt() to spamd failed: %m"); + close (mysock); + return EX_SOFTWARE; + + default: + break; /* ignored */ + } + } +#endif + + for (numloops=0; numloops < MAX_CONNECT_RETRIES; numloops++) { + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: connect() to spamd %d", numloops); +#endif + + if (argaddr != NULL) { + addr = (struct sockaddr_in *) argaddr; /* use the one provided */ + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: using argaddr"); +#endif + + } else { + /* cycle through the addrs in hent */ + memset(&addrbuf, 0, sizeof(addrbuf)); + addrbuf.sin_family=AF_INET; + addrbuf.sin_port=htons(hent_port); + + if (sizeof(addrbuf.sin_addr) != sizeof(struct in_addr)) { /* shouldn't happen */ + syslog (LOG_ERR, + "foo! sizeof(sockaddr.sin_addr) != sizeof(struct in_addr)"); + return EX_SOFTWARE; + } + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: cpy addr %d/%d at %lx", + numloops%hostnum, hostnum, &(inaddrlist[numloops % hostnum])); +#endif + + memcpy (&addrbuf.sin_addr, &(inaddrlist[numloops % hostnum]), + sizeof(addrbuf.sin_addr)); + addr = &addrbuf; + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: conn addr %d/%d = %lx", + numloops%hostnum, hostnum, addrbuf.sin_addr.s_addr); +#endif + + } + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: connect() to spamd at %s", + inet_ntoa(((struct sockaddr_in *)addr)->sin_addr)); +#endif + status = connect(mysock,(const struct sockaddr *) addr, sizeof(*addr)); + +#ifdef DO_CONNECT_DEBUG_SYSLOGS + syslog (LOG_DEBUG, "dbg: connect() to spamd at %s done", + inet_ntoa(((struct sockaddr_in *)addr)->sin_addr)); +#endif + + if (status < 0) + { + origerr = errno; /* take a copy before syslog() */ + syslog (LOG_ERR, "connect() to spamd at %s failed, retrying (%d/%d): %m", + inet_ntoa(((struct sockaddr_in *)addr)->sin_addr), + numloops+1, MAX_CONNECT_RETRIES); + sleep(CONNECT_RETRY_SLEEP); + + } else { + *sockptr = mysock; + return EX_OK; + } + } + + /* failed, even with a few retries */ + close (mysock); + syslog (LOG_ERR, "connection attempt to spamd aborted after %d retries", + MAX_CONNECT_RETRIES); + + switch(origerr) + { + case EBADF: + case EFAULT: + case ENOTSOCK: + case EISCONN: + case EADDRINUSE: + case EINPROGRESS: + case EALREADY: + case EAFNOSUPPORT: + return EX_SOFTWARE; + case ECONNREFUSED: + case ETIMEDOUT: + case ENETUNREACH: + return EX_UNAVAILABLE; + case EACCES: + return EX_NOPERM; + default: + return EX_SOFTWARE; + } +} + +/* Aug 14, 2002 bj: Reworked things. Now we have message_read, message_write, + * message_dump, lookup_host, message_filter, and message_process, and a bunch + * of helper functions. + */ + +static void clear_message(struct message *m){ + m->type=MESSAGE_NONE; + m->raw=NULL; m->raw_len=0; + m->pre=NULL; m->pre_len=0; + m->msg=NULL; m->msg_len=0; + m->post=NULL; m->post_len=0; + m->is_spam=EX_TOOBIG; + m->score=0.0; m->threshold=0.0; + m->out=NULL; m->out_len=0; + m->content_length=-1; +} + +static int +message_read_raw(int fd, struct message *m){ + clear_message(m); + if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR; + m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1); + if(m->raw_len<=0){ + free(m->raw); m->raw=NULL; m->raw_len=0; + return EX_IOERR; + } + m->type=MESSAGE_ERROR; + if(m->raw_len>m->max_len) return EX_TOOBIG; + m->type=MESSAGE_RAW; + m->msg=m->raw; + m->msg_len=m->raw_len; + m->out=m->msg; + m->out_len=m->msg_len; + return EX_OK; +} + +static int message_read_bsmtp(int fd, struct message *m){ + off_t i, j; + char prev; + + clear_message(m); + if((m->raw=malloc(m->max_len+1))==NULL) return EX_OSERR; + + /* Find the DATA line */ + m->raw_len=full_read(fd, (unsigned char *) m->raw, m->max_len+1, m->max_len+1); + if(m->raw_len<=0){ + free(m->raw); m->raw=NULL; m->raw_len=0; + return EX_IOERR; + } + m->type=MESSAGE_ERROR; + if(m->raw_len>m->max_len) return EX_TOOBIG; + m->pre=m->raw; + for(i=0; iraw_len-6; i++){ + if((m->raw[i]=='\n') && + (m->raw[i+1]=='D' || m->raw[i+1]=='d') && + (m->raw[i+2]=='A' || m->raw[i+2]=='a') && + (m->raw[i+3]=='T' || m->raw[i+3]=='t') && + (m->raw[i+4]=='A' || m->raw[i+4]=='a') && + ((m->raw[i+5]=='\r' && m->raw[i+6]=='\n') || m->raw[i+5]=='\n')){ + /* Found it! */ + i+=6; + if(m->raw[i-1]=='\r') i++; + m->pre_len=i; + m->msg=m->raw+i; + m->msg_len=m->raw_len-i; + break; + } + } + if(m->msg==NULL) return EX_DATAERR; + + /* Find the end-of-DATA line */ + prev='\n'; + for(i=j=0; imsg_len; i++){ + if(prev=='\n' && m->msg[i]=='.'){ + /* Dot at the beginning of a line */ + if((m->msg[i+1]=='\r' && m->msg[i+2]=='\n') || m->msg[i+1]=='\n'){ + /* Lone dot! That's all, folks */ + m->post=m->msg+i; + m->post_len=m->msg_len-i; + m->msg_len=j; + break; + } else if(m->msg[i+1]=='.'){ + /* Escaping dot, eliminate. */ + prev='.'; + continue; + } /* Else an ordinary dot, drop down to ordinary char handler */ + } + prev=m->msg[i]; + m->msg[j++]=m->msg[i]; + } + + m->type=MESSAGE_BSMTP; + m->out=m->msg; + m->out_len=m->msg_len; + return EX_OK; +} + +int message_read(int fd, int flags, struct message *m){ + libspamc_timeout = 0; + + switch(flags&SPAMC_MODE_MASK){ + case SPAMC_RAW_MODE: + return message_read_raw(fd, m); + + case SPAMC_BSMTP_MODE: + return message_read_bsmtp(fd, m); + + default: + syslog(LOG_ERR, "message_read: Unknown mode %d\n", flags&SPAMC_MODE_MASK); + return EX_USAGE; + } +} + +long message_write(int fd, struct message *m){ + long total=0; + off_t i, j; + off_t jlimit; + char buffer[1024]; + + /* if we're to output a message, m->is_spam will be EX_OUTPUTMESSAGE */ + if(m->is_spam==EX_ISSPAM || m->is_spam==EX_NOTSPAM){ + return full_write(fd, (unsigned char *) m->out, m->out_len); + } + + if (m->is_spam != EX_OUTPUTMESSAGE && m->is_spam != EX_TOOBIG) { + syslog(LOG_ERR, + "Cannot write this message, is_spam = %d!\n", m->is_spam); + return -1; + } + + switch(m->type){ + case MESSAGE_NONE: + syslog(LOG_ERR, "Cannot write this message, it's MESSAGE_NONE!\n"); + return -1; + + case MESSAGE_ERROR: + return full_write(fd, (unsigned char *) m->raw, m->raw_len); + + case MESSAGE_RAW: + return full_write(fd, (unsigned char *) m->out, m->out_len); + + case MESSAGE_BSMTP: + total=full_write(fd, (unsigned char *) m->pre, m->pre_len); + for(i=0; iout_len; ){ + jlimit = (off_t) (sizeof(buffer)/sizeof(*buffer)-4); + for(j=0; i < (off_t) m->out_len && + j < jlimit;) + { + if(i+1out_len && m->out[i]=='\n' && m->out[i+1]=='.'){ + if (j > jlimit - 4) { + break; /* avoid overflow */ + } + buffer[j++]=m->out[i++]; + buffer[j++]=m->out[i++]; + buffer[j++]='.'; + } else { + buffer[j++]=m->out[i++]; + } + } + total+=full_write(fd, (unsigned char *) buffer, j); + } + return total+full_write(fd, (unsigned char *) m->post, m->post_len); + + default: + syslog(LOG_ERR, "Unknown message type %d\n", m->type); + return -1; + } +} + +void message_dump(int in_fd, int out_fd, struct message *m){ + char buf[8196]; + int bytes; + + if(m!=NULL && m->type!=MESSAGE_NONE) { + message_write(out_fd, m); + } + while((bytes=full_read(in_fd, (unsigned char *) buf, 8192, 8192))>0){ + if (bytes!=full_write(out_fd, (unsigned char *) buf, bytes)) { + syslog(LOG_ERR, "oops! message_dump of %d returned different", bytes); + } + } +} + +static int +_spamc_read_full_line (struct message *m, int flags, SSL *ssl, int sock, + char *buf, int *lenp, int bufsiz) +{ + int failureval; + int bytesread = 0; + int len; + + /* Now, read from spamd */ + for(len=0; len 0 && buf[len-1] == '\r') { + len--; + buf[len]='\0'; + } + *lenp = len; + return EX_OK; + } + + if(bytesread<=0){ + failureval = EX_IOERR; goto failure; + } + } + + syslog(LOG_ERR, "spamd responded with line of %d bytes, dying", len); + failureval = EX_TOOBIG; + +failure: + return failureval; +} + +/* + * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale. + * work around using our own locale-independent float-parser code. + */ +static float +_locale_safe_string_to_float (char *buf, int siz) +{ + int is_neg; + char *cp, *dot; + int divider; + float ret, postdot; + + buf[siz-1] = '\0'; /* ensure termination */ + + /* ok, let's illustrate using "100.033" as an example... */ + + is_neg = 0; + if (*buf == '-') { is_neg = 1; } + + ret = (float) (strtol (buf, &dot, 10)); + if (dot == NULL) { return 0.0; } + if (dot != NULL && *dot != '.') { return ret; } + + /* ex: ret == 100.0 */ + + cp = (dot + 1); + postdot = (float) (strtol (cp, NULL, 10)); + if (postdot == 0.0) { return ret; } + + /* ex: postdot == 33.0, cp="033" */ + + /* now count the number of decimal places and figure out what power of 10 to use */ + divider = 1; + while (*cp != '\0') { + divider *= 10; cp++; + } + + /* ex: + * cp="033", divider=1 + * cp="33", divider=10 + * cp="3", divider=100 + * cp="", divider=1000 + */ + + if (is_neg) { + ret -= (postdot / ((float) divider)); + } else { + ret += (postdot / ((float) divider)); + } + /* ex: ret == 100.033, tada! ... hopefully */ + + return ret; +} + +static int +_handle_spamd_header (struct message *m, int flags, char *buf, int len) +{ + char is_spam[6]; + char s_str[20], t_str[20]; + + /* Feb 12 2003 jm: actually, I think sccanf is working fine here ;) + * let's stick with it for this parser. + * May 7 2003 jm: using %f is bad where LC_NUMERIC is "," in the locale. + * work around using our own locale-independent float-parser code. + */ + if (sscanf(buf, "Spam: %5s ; %20s / %20s", is_spam, s_str, t_str) == 3) + { + m->score = _locale_safe_string_to_float (s_str, 20); + m->threshold = _locale_safe_string_to_float (t_str, 20); + + /* Format is "Spam: x; y / x" */ + m->is_spam=strcasecmp("true", is_spam) == 0 ? EX_ISSPAM: EX_NOTSPAM; + + if(flags&SPAMC_CHECK_ONLY) { + m->out_len=snprintf (m->out, m->max_len+EXPANSION_ALLOWANCE, + "%.1f/%.1f\n", m->score, m->threshold); + } + return EX_OK; + + } else if(sscanf(buf, "Content-length: %d", &m->content_length) == 1) { + if (m->content_length < 0) { + syslog(LOG_ERR, "spamd responded with bad Content-length '%s'", buf); + return EX_PROTOCOL; + } + return EX_OK; + } + + syslog(LOG_ERR, "spamd responded with bad header '%s'", buf); + return EX_PROTOCOL; +} + +static int _message_filter(const struct sockaddr *addr, + const struct hostent *hent, int hent_port, char *username, + int flags, struct message *m) +{ + char buf[8192]; + int bufsiz = (sizeof(buf) / sizeof(*buf)) - 4; /* bit of breathing room */ + int len, i; + int sock = -1; + char versbuf[20]; + float version; + int response; + int failureval; + SSL_CTX* ctx; + SSL* ssl; + SSL_METHOD *meth; + + if (flags&SPAMC_USE_SSL) { +#ifdef SPAMC_SSL + SSLeay_add_ssl_algorithms(); + meth = SSLv2_client_method(); + SSL_load_error_strings(); + ctx = SSL_CTX_new(meth); +#else + (void) ssl; (void) meth; (void) ctx; /* avoid "unused" warnings */ + syslog(LOG_ERR, "spamc not built with SSL support"); + return EX_SOFTWARE; +#endif + } + + m->is_spam=EX_TOOBIG; + if((m->out=malloc(m->max_len+EXPANSION_ALLOWANCE+1))==NULL){ + failureval = EX_OSERR; goto failure; + } + m->out_len=0; + + + /* Build spamd protocol header */ + if(flags & SPAMC_CHECK_ONLY) + len=snprintf(buf, bufsiz, "CHECK %s\r\n", PROTOCOL_VERSION); + else if(flags & SPAMC_REPORT_IFSPAM) + len=snprintf(buf, bufsiz, "REPORT_IFSPAM %s\r\n", PROTOCOL_VERSION); + else if(flags & SPAMC_REPORT) + len=snprintf(buf, bufsiz, "REPORT %s\r\n", PROTOCOL_VERSION); + else if(flags & SPAMC_SYMBOLS) + len=snprintf(buf, bufsiz, "SYMBOLS %s\r\n", PROTOCOL_VERSION); + else + len=snprintf(buf, bufsiz, "PROCESS %s\r\n", PROTOCOL_VERSION); + + if(len<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; } + if(username!=NULL){ + len+=i=snprintf(buf+len, bufsiz-len, "User: %s\r\n", username); + if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; } + } + len+=i=snprintf(buf+len, bufsiz-len, "Content-length: %d\r\n", m->msg_len); + if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; } + len+=i=snprintf(buf+len, bufsiz-len, "\r\n"); + if(i<0 || len >= bufsiz){ free(m->out); m->out=m->msg; m->out_len=m->msg_len; return EX_OSERR; } + + libspamc_timeout = m->timeout; + + if((i=try_to_connect(addr, (struct hostent *) hent, + hent_port, &sock)) != EX_OK) + { + free(m->out); m->out=m->msg; m->out_len=m->msg_len; + return i; + } + + if(flags&SPAMC_USE_SSL) { +#ifdef SPAMC_SSL + ssl = SSL_new(ctx); + SSL_set_fd(ssl, sock); + SSL_connect(ssl); +#endif + } + + /* Send to spamd */ + if(flags&SPAMC_USE_SSL) { +#ifdef SPAMC_SSL + SSL_write(ssl, buf, len); + SSL_write(ssl, m->msg, m->msg_len); +#endif + } else { + full_write(sock, (unsigned char *) buf, len); + full_write(sock, (unsigned char *) m->msg, m->msg_len); + shutdown(sock, SHUT_WR); + } + + /* ok, now read and parse it. SPAMD/1.2 line first... */ + failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz); + if (failureval != EX_OK) { goto failure; } + + if(sscanf(buf, "SPAMD/%s %d %*s", versbuf, &response)!=2) { + syslog(LOG_ERR, "spamd responded with bad string '%s'", buf); + failureval = EX_PROTOCOL; goto failure; + } + + version = _locale_safe_string_to_float (versbuf, 20); + if (version < 1.0) { + syslog(LOG_ERR, "spamd responded with bad version string '%s'", versbuf); + failureval = EX_PROTOCOL; goto failure; + } + + m->score = 0; + m->threshold = 0; + m->is_spam = EX_TOOBIG; + while (1) { + failureval = _spamc_read_full_line (m, flags, ssl, sock, buf, &len, bufsiz); + if (failureval != EX_OK) { goto failure; } + + if (len == 0 && buf[0] == '\0') { + break; /* end of headers */ + } + + if (_handle_spamd_header(m, flags, buf, len) < 0) { + failureval = EX_PROTOCOL; goto failure; + } + } + + len = 0; /* overwrite those headers */ + + if (flags&SPAMC_CHECK_ONLY) { + close(sock); sock = -1; + if (m->is_spam == EX_TOOBIG) { + /* We should have gotten headers back... Damnit. */ + failureval = EX_PROTOCOL; goto failure; + } + return EX_OK; + } + else { + m->is_spam=EX_OUTPUTMESSAGE; + if (m->content_length < 0) { + /* should have got a length too. */ + failureval = EX_PROTOCOL; goto failure; + } + + if (flags&SPAMC_USE_SSL) { + len = ssl_timeout_read (ssl, m->out+m->out_len, + m->max_len+EXPANSION_ALLOWANCE+1-m->out_len); + } else{ + len = full_read (sock, (unsigned char *) m->out+m->out_len, + m->max_len+EXPANSION_ALLOWANCE+1-m->out_len, + m->max_len+EXPANSION_ALLOWANCE+1-m->out_len); + } + + + if(len+m->out_len>m->max_len+EXPANSION_ALLOWANCE){ + failureval = EX_TOOBIG; goto failure; + } + m->out_len+=len; + + shutdown(sock, SHUT_RD); + close(sock); sock = -1; + } + libspamc_timeout = 0; + + if(m->out_len!=m->content_length) { + syslog(LOG_ERR, "failed sanity check, %d bytes claimed, %d bytes seen", + m->content_length, m->out_len); + failureval = EX_PROTOCOL; goto failure; + } + + return EX_OK; + +failure: + free(m->out); m->out=m->msg; m->out_len=m->msg_len; + if (sock != -1) { + close(sock); + } + libspamc_timeout = 0; + + if(flags&SPAMC_USE_SSL) { +#ifdef SPAMC_SSL + SSL_free(ssl); + SSL_CTX_free(ctx); +#endif + } + return failureval; +} + +static int _lookup_host(const char *hostname, struct hostent *out_hent) +{ + struct hostent *hent = NULL; + int origherr; + + /* no need to try using inet_addr(), gethostbyname() will do that */ + + if (NULL == (hent = gethostbyname(hostname))) { + origherr = h_errno; /* take a copy before syslog() */ + syslog (LOG_ERR, "gethostbyname(%s) failed: h_errno=%d", + hostname, origherr); + switch(origherr) + { + case HOST_NOT_FOUND: + case NO_ADDRESS: + case NO_RECOVERY: + return EX_NOHOST; + case TRY_AGAIN: + return EX_TEMPFAIL; + default: + return EX_OSERR; + } + } + + memcpy (out_hent, hent, sizeof(struct hostent)); + + return EX_OK; +} + +int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags){ + struct hostent hent; + int ret; + struct message m; + + m.type=MESSAGE_NONE; + + ret=lookup_host_for_failover(hostname, &hent); + if(ret!=EX_OK) goto FAIL; + + m.max_len=max_size; + ret=message_read(in_fd, flags, &m); + if(ret!=EX_OK) goto FAIL; + ret=message_filter_with_failover(&hent, port, username, flags, &m); + if(ret!=EX_OK) goto FAIL; + if(message_write(out_fd, &m)<0) goto FAIL; + if(m.is_spam!=EX_TOOBIG) { + message_cleanup(&m); + return m.is_spam; + } + message_cleanup(&m); + return ret; + +FAIL: + if(flags&SPAMC_CHECK_ONLY){ + full_write(out_fd, (unsigned char *) "0/0\n", 4); + message_cleanup(&m); + return EX_NOTSPAM; + } else { + message_dump(in_fd, out_fd, &m); + message_cleanup(&m); + return ret; + } +} + +void message_cleanup(struct message *m) { + if (m->out != NULL && m->out != m->raw) free(m->out); + if (m->raw != NULL) free(m->raw); + clear_message(m); +} + +/* Aug 14, 2002 bj: Obsolete! */ +int process_message(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int my_check_only, const int my_safe_fallback){ + int flags; + + flags=SPAMC_RAW_MODE; + if(my_check_only) flags|=SPAMC_CHECK_ONLY; + if(my_safe_fallback) flags|=SPAMC_SAFE_FALLBACK; + + return message_process(hostname, port, username, max_size, in_fd, out_fd, flags); +} + +/* public APIs, which call into the static code and enforce sockaddr-OR-hostent + * conventions */ + +int lookup_host(const char *hostname, int port, struct sockaddr *out_addr) +{ + struct sockaddr_in *addr = (struct sockaddr_in *)out_addr; + struct hostent hent; + int ret; + + memset(&out_addr, 0, sizeof(out_addr)); + addr->sin_family=AF_INET; + addr->sin_port=htons(port); + ret = _lookup_host(hostname, &hent); + memcpy (&(addr->sin_addr), hent.h_addr, sizeof(addr->sin_addr)); + return ret; +} + +int lookup_host_for_failover(const char *hostname, struct hostent *hent) { + return _lookup_host(hostname, hent); +} + +int message_filter(const struct sockaddr *addr, char *username, int flags, + struct message *m) +{ return _message_filter (addr, NULL, 0, username, flags, m); } + +int message_filter_with_failover (const struct hostent *hent, int port, + char *username, int flags, struct message *m) +{ return _message_filter (NULL, hent, port, username, flags, m); } + diff --git a/libspamc.h b/libspamc.h new file mode 100644 index 0000000..4441676 --- /dev/null +++ b/libspamc.h @@ -0,0 +1,131 @@ +/* + * This code is copyright 2001 by Craig Hughes + * Conversion to a thread-safe shared library copyright 2002 Liam Widdowson + * Portions copyright 2002 by Brad Jorsch + * It is licensed under the same license as Perl itself. The text of this + * license is included in the SpamAssassin distribution in the file named + * "License". + */ +#ifndef LIBSPAMC_H +#define LIBSPAMC_H 1 + +#include +#include +#include +#include + +#define EX_NOTSPAM 0 +#define EX_ISSPAM 1 +#define EX_TOOBIG 866 +#define EX_OUTPUTMESSAGE 867 + +/* Aug 14, 2002 bj: Bitflags instead of lots of bool parameters */ +#define SPAMC_MODE_MASK 1 +#define SPAMC_RAW_MODE 0 +#define SPAMC_BSMTP_MODE 1 + +#define SPAMC_USE_SSL (1<<27) +#define SPAMC_SAFE_FALLBACK (1<<28) +#define SPAMC_CHECK_ONLY (1<<29) + +/* Jan 30, 2003 ym: added reporting options */ +#define SPAMC_REPORT (1<<26) +#define SPAMC_REPORT_IFSPAM (1<<25) + +/* Feb 1 2003 jm: might as well fix bug 191 as well */ +#define SPAMC_SYMBOLS (1<<24) + + +/* Aug 14, 2002 bj: A struct for storing a message-in-progress */ +typedef enum { + MESSAGE_NONE, + MESSAGE_ERROR, + MESSAGE_RAW, + MESSAGE_BSMTP, + MAX_MESSAGE_TYPE +} message_type_t; + +struct message { + /* Set before passing the struct on! */ + int max_len; /* messages larger than this will return EX_TOOBIG */ + int timeout; /* timeout for read() system calls */ + + /* Filled in by message_read */ + message_type_t type; + char *raw; int raw_len; /* Raw message buffer */ + char *pre; int pre_len; /* Pre-message data (e.g. SMTP commands) */ + char *msg; int msg_len; /* The message */ + char *post; int post_len; /* Post-message data (e.g. SMTP commands) */ + int content_length; + + /* Filled in by filter_message */ + int is_spam; /* EX_ISSPAM if the message is spam, EX_NOTSPAM + if not, EX_OUTPUTMESSAGE if a filtered message + is returned in "out" below. */ + float score, threshold; /* score and threshold */ + char *out; int out_len; /* Output from spamd. Either the filtered + message, or the check-only response. Or else, + a pointer to msg above. */ +}; + +/* Aug 14, 2002 bj: New interface functions */ + +/* Read in a message from the fd, with the mode specified in the flags. + * Returns EX_OK on success, EX_otherwise on failure. On failure, m may be + * either MESSAGE_NONE or MESSAGE_ERROR. */ +int message_read(int in_fd, int flags, struct message *m); + +/* Write out a message to the fd, as specified by m->type. Note that + * MESSAGE_NONE messages have nothing to write. Also note that if you ran the + * message through message_filter with SPAMC_CHECK_ONLY, it will only output + * the "score/threshold" line. */ +long message_write(int out_fd, struct message *m); + +/* Pass the message through spamd (at addr) as the specified user, with the + * given flags. Returns EX_OK on success, or various errors on error. If it was + * successful, message_write will print either the CHECK_ONLY output, or the + * filtered message in the appropriate output format. */ +int message_filter(const struct sockaddr *addr, char *username, int flags, struct message *m); + +/* Convert the host/port into a struct sockaddr. Returns EX_OK on success, or + * else an error EX. */ +int lookup_host(const char *hostname, int port, struct sockaddr *a); + +/* Pass the message through one of a set of spamd's. This variant will handle + * multiple spamd machines; if a connect failure occurs, it will fail-over to + * the next one in the struct hostent. Otherwise identical to message_filter(). + */ +int message_filter_with_failover (const struct hostent *hent, int port, char + *username, int flags, struct message *m); + +/* Convert the host into a struct hostent, for use with + * message_filter_with_failover() above. Returns EX_OK on success, or else an + * error EX. Note that the data filled into hent is from gethostbyname()'s + * static storage, so any call to gethostbyname() between + * lookup_host_for_failover() and message_filter_with_failover() will overwrite + * this. Take a copy, and use that instead, if you think a call may occur in + * your code, or library code that you use (such as syslog()). */ +int lookup_host_for_failover(const char *hostname, struct hostent *hent); + +/* Dump the message. If there is any data in the message (typically, m->type + * will be MESSAGE_ERROR) it will be message_writed. Then, fd_in will be piped + * to fd_out intol EOF. This is particularly useful if you get back an + * EX_TOOBIG. */ +void message_dump(int in_fd, int out_fd, struct message *m); + +/* Do a message_read->message_filter->message_write sequence, handling errors + * appropriately with dump_message or appropriate CHECK_ONLY output. Returns + * EX_OK or EX_ISSPAM/EX_NOTSPAM on success, some error EX on error. */ +int message_process(const char *hostname, int port, char *username, int max_size, int in_fd, int out_fd, const int flags); + +/* Cleanup the resources we allocated for storing the message. Call after + * you're done processing. */ +void message_cleanup(struct message *m); + +/* Aug 14, 2002 bj: This is now legacy, don't use it. */ +int process_message(const char *hostname, int port, char *username, + int max_size, int in_fd, int out_fd, + const int check_only, const int safe_fallback); + +#endif + diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..a4bb891 --- /dev/null +++ b/utils.c @@ -0,0 +1,144 @@ +/* + * This code is copyright 2001 by Craig Hughes + * Portions copyright 2002 by Brad Jorsch + * It is licensed under the same license as Perl itself. The text of this + * license is included in the SpamAssassin distribution in the file named + * "License". + */ + +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" + +/* Dec 13 2001 jm: added safe full-read and full-write functions. These + * can cope with networks etc., where a write or read may not read all + * the data that's there, in one call. + */ +/* Aug 14, 2002 bj: EINTR and EAGAIN aren't fatal, are they? */ +/* Aug 14, 2002 bj: moved these to utils.c */ +/* Jan 13, 2003 ym: added timeout functionality */ + +/* -------------------------------------------------------------------------- */ + +typedef void sigfunc(int); /* for signal handlers */ + +sigfunc* sig_catch(int sig, void (*f)(int)) +{ + struct sigaction act, oact; + act.sa_handler = f; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(sig, &act, &oact); + return oact.sa_handler; +} + +static void catch_alrm(int x) { + /* dummy */ +} + +ssize_t +fd_timeout_read (int fd, void *buf, size_t nbytes) +{ + ssize_t nred; + sigfunc* sig; + + sig = sig_catch(SIGALRM, catch_alrm); + if (libspamc_timeout > 0) { + alarm(libspamc_timeout); + } + + do { + nred = read (fd, buf, nbytes); + } while(nred < 0 && errno == EAGAIN); + + if(nred < 0 && errno == EINTR) + errno = ETIMEDOUT; + + if (libspamc_timeout > 0) { + alarm(0); + } + + /* restore old signal handler */ + sig_catch(SIGALRM, sig); + + return nred; +} + +int +ssl_timeout_read (SSL *ssl, void *buf, int nbytes) +{ + int nred; + sigfunc* sig; + + sig = sig_catch(SIGALRM, catch_alrm); + if (libspamc_timeout > 0) { + alarm(libspamc_timeout); + } + + do { +#ifdef SPAMC_SSL + nred = SSL_read (ssl, buf, nbytes); +#else + nred = 0; /* never used */ +#endif + } while(nred < 0 && errno == EAGAIN); + + if(nred < 0 && errno == EINTR) + errno = ETIMEDOUT; + + if (libspamc_timeout > 0) { + alarm(0); + } + + /* restore old signal handler */ + sig_catch(SIGALRM, sig); + + return nred; +} + +/* -------------------------------------------------------------------------- */ + +int +full_read (int fd, unsigned char *buf, int min, int len) +{ + int total; + int thistime; + + for (total = 0; total < min; ) { + thistime = fd_timeout_read (fd, buf+total, len-total); + + if (thistime < 0) { + return -1; + } else if (thistime == 0) { + /* EOF, but we didn't read the minimum. return what we've read + * so far and next read (if there is one) will return 0. */ + return total; + } + + total += thistime; + } + return total; +} + +int +full_write (int fd, const unsigned char *buf, int len) +{ + int total; + int thistime; + + for (total = 0; total < len; ) { + thistime = write (fd, buf+total, len-total); + + if (thistime < 0) { + if(EINTR == errno || EAGAIN == errno) continue; + return thistime; /* always an error for writes */ + } + total += thistime; + } + return total; +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..9522226 --- /dev/null +++ b/utils.h @@ -0,0 +1,24 @@ +#ifndef UTILS_H +#define UTILS_H + +extern int libspamc_timeout; /* default timeout in seconds */ + +#ifdef SPAMC_SSL +#include +#include +#include +#include +#else +typedef int SSL; /* fake type to avoid conditional compilation */ +typedef int SSL_CTX; +typedef int SSL_METHOD; +#endif + +ssize_t fd_timeout_read (int fd, void *, size_t ); +int ssl_timeout_read (SSL *ssl, void *, int ); + +/* these are fd-only, no SSL support */ +int full_read(int fd, unsigned char *buf, int min, int len); +int full_write(int fd, const unsigned char *buf, int len); + +#endif -- [mdw]